JUC并发包

为什么多线程很重要?

  • 硬件:摩尔定律失效,电路上的元器件数目每隔18个月增加一倍,可从2003年开始CPU主频不再翻倍,而是采用多核而不是更快的主频。在核数不断增加情况下,要想程序更快就要用到并行
  • 软件:高并发系统,异步执行需要用到多个线程

什么是进程与线程、管程?
每个进程代表一个程序,拥有自己的内存空间和系统资源
一个进程里有多个线程,可以执行多个任务
Monitor管程对象,每个对象实例都会有一个Monitor对象

什么是用户线程与守护线程?
守护线程在后台默默完成一些服务,例如垃圾回收器,当用户线程结束了,不管守护线程是否执行结束,守护线程也会随之退出。Thread.setDaemon(true),但应该在start()之前设置才能生效
用户线程是工作线程,完成分配的业务操作。

Future
        FutureTask<Integer> futureTask = new FutureTask(()->{
            Thread.sleep(1000);
            System.out.println("子任务执行结束");
            return 1;
        });
        new Thread(futureTask).start();

        System.out.println(futureTask.get(2, TimeUnit.SECONDS));

        System.out.println("----main执行结束----");

子任务执行结束
1
----main执行结束----

Future.get()会进行阻塞等待结果

CompletableFuture(异步任务编排) api文档
语法示例:

public static void main(String[] args) {
         CompletableFuture.supplyAsync(() -> {
            //业务处理
            int num = 1 + 1;
             return num;
        }).thenApply(num -> {
            //根据上面的结果,进行后续的处理
            int num2 = num * 2;
            return num2;
        }).thenApplyAsync(num2 -> {
            //拿到上面的结果后,又开启新的异步线程开始执行了
            int num3 = num2 + 1;
            return num3;
        }).whenComplete((num3, e) -> {
            if (e == null) {
                System.out.println(MessageFormat.format("计算结果:{0}", num3.toString()));
            }
        }).exceptionally(e -> {
            e.printStackTrace();
            return null;
        }).join();

        System.out.println("---main---");
    }


计算结果:5
---main---

CompletableFuture.join()不抛异常
CompletableFuture.get()抛异常

通过一个案例实测一下,假设我需要根据一个文件地址,模拟计算出所有子目录或文件的总大小

import cn.hutool.core.date.StopWatch;
import cn.hutool.core.io.FileUtil;
import com.google.common.collect.Lists;
import lombok.SneakyThrows;
import java.io.File;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CompletableFutureTest {

    public static void main(String[] args){

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), 10, 1, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(50));

        StopWatch time = new StopWatch();
        time.start("获取所有文件");
        List<File> files = getFiles("C:\\Program Files\\Java\\jdk1.8.0_181\\jre");
        time.stop();

        time.start("计算所有文件");
        Long size = calcFile(files);
        time.stop();
        System.out.println(MessageFormat.format("找到文件:{0}个,计算结果:{1}B",files.size(),size.toString()));

        time.start("文件分批");
        List<List<File>> lists = listSplitBySize(files, 20);
        time.stop();

        time.start("CompletableFuture计算所有文件");
        Long size1 = calcFileWithCompletableFuture(lists,threadPoolExecutor);
        System.out.println(MessageFormat.format("找到文件:{0}个,CompletableFuture计算结果:{1}B",files.size(),size1.toString()));
        time.stop();

        System.out.println(time.prettyPrint(TimeUnit.MILLISECONDS));
        threadPoolExecutor.shutdown();
    }


    @SneakyThrows
    private static List<File> getFiles(String path){
        File file = FileUtil.file(path);
        List<File> result = Lists.newArrayList();
        if(file.isDirectory()){
            File[] files = FileUtil.ls(FileUtil.getAbsolutePath(file));
            Arrays.stream(files).forEach(f->result.addAll(getFiles(FileUtil.getAbsolutePath(f))));
        }else {
            result.add(file);
        }
        return result;
    }

    private static Long calcFileWithCompletableFuture(List<List<File>> fileList,Executor executor){
         return fileList.stream().map(files -> CompletableFuture.supplyAsync(() -> {
            Long calcResult = calcFile(files);
            return calcResult;
        },executor)).collect(Collectors.toList()).stream().mapToLong(CompletableFuture::join).sum();
    }

    private static Long calcFile(List<File> files){
        return files.stream().mapToLong(file -> {

            //假设这里对每个文件进行其他读取或处理耗时
            try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}

            return FileUtil.size(file);
        }).sum();
    }

    public static <T>  List<List<T>> listSplitBySize(List<T> data,Integer size){
        long step = (data.size() + size - 1) / size;
        return Stream.iterate(0, n -> n + 1).limit(step).parallel().map( n -> data.stream().skip(n * size).limit(size).parallel().collect(Collectors.toList())).collect(Collectors.toList());
    }
}

找到文件:241个,计算结果:209036999B
找到文件:241个,CompletableFuture计算结果:209036999B
StopWatch '': running time = 4631 ms
---------------------------------------------
ms         %     Task name
---------------------------------------------
000000233  05%   获取所有文件
000003730  81%   计算所有文件
000000014  00%   文件分批
000000653  14%   CompletableFuture计算所有文件

  • 1.悲观锁:先加锁,再读写数据。例:synchronized和Lock实现类
  • 2.乐观锁:认为自己是使用数据时,别的线程不会对其修改,只是在更新数据的时候判断有没有别的线程更新了这个数据。例:CAS
public void test(){
        Object obj = new Object();
        synchronized (obj){
            System.out.println("执行中...");
        }
}

synchronized范围锁字节码.png

如果synchronized是代码块形式,在字节码中会出现1次monitorenter和2次monitorexit指令,但是如果是synchronized代码块中出现异常,字节码只会出现1次monitorenter和1次monitorexit指令.

public void test(){
        Object obj = new Object();
        synchronized (obj){
            System.out.println("执行中...");
            throw new RuntimeException("出现异常");
        }
}
synchronized范围锁出现异常字节码.png
  • 3.公平锁: 线程判断队列中是否有线程排队,如果有插入队尾,没有就CAS设置成功标识的视为拿到锁。 (雨露均沾)
  • 4.非公平锁: 线程通过CAS先设置成功标识的视为拿到锁。获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。 (效率优先)
  • 5.可重入锁: 如果是当前线程持有,则对次数进行+1,解锁-1,有效避免了死锁。
死锁示例:

public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        
        new Thread(()->{
            synchronized (o1){
                System.out.println("1-拿到了o1锁");
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2){
                    System.out.println("1-拿到了o2锁");
                }
            }
        }).start();


        new Thread(()->{
            synchronized (o2){
                System.out.println("2-拿到了o2锁");
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1){
                    System.out.println("2-拿到了o1锁");
                }
            }
        }).start();
    }

1-拿到了o1锁
2-拿到了o2锁

怎么排查证明发生了死锁?
jstack <pid>


deadlock.png
  • 6.读写锁:能被多个读线程访问,或者被一个写线程访问,不能同时读写访问。例如:ReentrantReadWriteLock
    缺点:锁饥饿,一直读,写抢占不到锁。
    ReentrantReadWriteLock的锁降级:同一线程中,写锁中获得读锁,然后再释放写锁;写锁降级为---->读锁, 其他线程就可以立即读取,而其他线程的写锁只能等待读完。 但是读锁不能变成写锁!
  • 7.邮戳锁:StampedLock,不可重入锁,它是对ReentrantReadWriteLock的优化,读的过程中也可以写。
    尝试乐观读,返回一个版本号,最后验证版本号是否匹配,如果不匹配就会切换到悲观读的模式。
        StampedLock stampedLock = new StampedLock();
        long version = stampedLock.tryOptimisticRead();
        System.out.println("进行乐观读");
        if(!stampedLock.validate(version)){
            long readLock = stampedLock.readLock();
            try{
              System.out.println("切换悲观读模式,重新获取数据");
            }finally{
              stampedLock.unlockRead(readLock);
            }
        }
线程的中断
  • Thread.stop():强制中断线程,相当于kill -9,不推荐,容易造成数据不一致
  • volatile+标记位:
static volatile boolean stopFlag = false;

    public static void main(String[] args) {
        new Thread(() -> {
           while (true){
               if(stopFlag){
                   System.out.println(Thread.currentThread().getName()+"中断");
                   break;
               }else{
                   System.out.println(Thread.currentThread().getName()+"执行中...");
               }
           }
            System.out.println(Thread.currentThread().getName()+"执行完毕");
        },"线程1-").start();;


        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stopFlag = true;
            System.out.println(Thread.currentThread().getName()+"执行完毕");
        },"线程2-").start();

    }

线程1-执行中...
线程1-执行中...
线程1-执行中...
线程1-执行中...
线程2-执行完毕
线程1-中断
线程1-执行完毕
  • AtomicBoolean原子类:
static AtomicBoolean stopFlag = new AtomicBoolean(false);

    public static void main(String[] args) {
        new Thread(() -> {
           while (true){
               if(stopFlag.get()){
                   System.out.println(Thread.currentThread().getName()+"中断");
                   break;
               }else{
                   System.out.println(Thread.currentThread().getName()+"执行中...");
               }
           }
            System.out.println(Thread.currentThread().getName()+"执行完毕");
        },"线程1-").start();


        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stopFlag.set(true);
            System.out.println(Thread.currentThread().getName()+"执行完毕");
        },"线程2-").start();

    }

线程1-执行中...
线程1-执行中...
线程1-执行中...
线程1-执行中...
线程2-执行完毕
线程1-中断
线程1-执行完毕
  • Thread.isInterrupted() + Thread.interrupt();
  public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            while (true) {
                //isInterrupted()判断当前线程是否被中断,默认是false。
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + "中断");
                    break;
                } else {
                    System.out.println(Thread.currentThread().getName() + "执行中...");
                }
            }
            System.out.println(Thread.currentThread().getName() + "执行完毕");
        }, "线程1-");
        thread1.start();


        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //将isInterrupted()标记置为true。
            thread1.interrupt();
            System.out.println(Thread.currentThread().getName()+"执行完毕");
        },"线程2-").start();

    }

线程1-执行中...
线程1-执行中...
线程1-执行中...
线程1-执行中...
线程2-执行完毕
线程1-中断
线程1-执行完毕

interrupt()不能真正的中断线程,只是设置了中断标记,真正的中断交给线程自己去控制。
如果线程处于阻塞状态下(sleep、wait、join)会抛出InterruptedException,线程中断标记依旧是false,需要在异常块中再次Thread.currentThread().interrupt();标记才会置为true。

try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
线程的等待与唤醒
  • 1.wait()与notify()。必须要在synchronized代码块中,并且对顺序有要求。
    public static void main(String[] args) {
        Object obj = new Object();

        new Thread(()->{
            synchronized (obj){
                System.out.println(Thread.currentThread().getName()+":开始了");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":结束了");
            }
        },"线程1").start();

        new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (obj){
                System.out.println(Thread.currentThread().getName()+":开始了");
                obj.notify();
                System.out.println(Thread.currentThread().getName()+":结束了");
            }
        },"线程2").start();

    }

线程1:开始了
线程2:开始了
线程2:结束了
线程1:结束了
  • 2.Condition.await()与signal(),必须要在Lock.lock()代码块中,同样有顺序要求。
 public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(()->{
            lock.lock();
            try {

                System.out.println(Thread.currentThread().getName()+":开始了");
                condition.await();
                System.out.println(Thread.currentThread().getName()+":结束了");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        },"线程1").start();

        new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            lock.lock();
            try {

                System.out.println(Thread.currentThread().getName()+":开始了");
                condition.signal();
                System.out.println(Thread.currentThread().getName()+":结束了");

            } finally {
                lock.unlock();
            }
        },"线程2").start();

    }

线程1:开始了
线程2:开始了
线程2:结束了
线程1:结束了
  • 3.LockSupport.park()与LockSupport.unpark(Thread)。 unpark会释放一个许可,多次调用unpark最高也只会是一个许可. park()获取一个许可; 并且没有顺序要求。
JMM(Java内存模型)

CPU的运算速度超过物理主内存的速度,所以CPU中设计了多级缓存;CPU的运行并不是直接操作物理内存,而是先把物理内存的数据读取到CPU高速缓存中,内存的读写就会造成数据不一致问题。
Java内存模式(java memory model)就是用于屏蔽硬件和操作系统的内存访问差异,仅仅是一种约定,围绕多线程的原子性,可见性,有序性。

  • 可见性:当一个线程修改了某个共享变量,其他线程是否能立即知道该变更,JMM规定所有变量都存储在主内存中。
  • 有序性:为了提升性能,编译器和处理器会对指令进行重排序,指令重排可以保证串行语义一致,但无法保证多线程间的语义也一致。
  • 原子性:一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰。
  • happens-before原则:一个操作先行发生另一个操作,那么第一个操作的执行结果将对第二个操作可见,且第一个操作的执行顺序排在第二个操作之前;如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
volatile

保证了可见性和有序性,但是不保证原子性,写内存会立即刷新回主内存中,读内存直接从主内存中读取;它是通过内存屏障指令达到的;对于一写多读可以解决变量的同步,多写就无法保证线程安全
内存屏障之前所有写操作都要写回主内存
内存屏障之后所有读操作都能获取写操作的最新结果
CPU内存屏障指令:loadload、storestore、loadstore、storeload

volatile.png

CAS

compare and swap:比较并替换,有3个重要操作参数(内存地址、旧值、新值),实现由CPU指令cmpxchg

public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
}
//CAS自旋直到修改成功
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
}

缺点:①CAS失败了会一直重试,造成CPU空转。
②ABA问题,值没变,但是被替换操作过;可以使用版本号,例如:AtomicStampedReference<V>

AtomicIntegerFieldUpdater

原子更新对象中int类型字段的值,缩小锁的范围,需要更新的属性字段必须添加volatile关键字

@Data
class User{
    private String name;
    private volatile int age;

    private AtomicIntegerFieldUpdater ageFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");

    public void birthday(User user){
        ageFieldUpdater.getAndIncrement(user);
    }
}

public class AtomicIntegerFieldUpdaterTest {
    public static void main(String[] args) throws InterruptedException {
        User user = new User();
        user.setName("张三");
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                user.birthday(user);
            }).start();
        }

        Thread.sleep(1000);

        System.out.println(String.format("user:%s,age:%d",user.getName(),user.getAge()));
    }
}

user:张三,age:30
AtomicReferenceFieldUpdater

原子更新对象中引用类型字段的值
例如:多线程调用某个对象的初始化方法,该方法只能执行一次

@Data
class User{
    private String name;
    private volatile  Boolean initFlag = false;
    private AtomicReferenceFieldUpdater<User,Boolean> initFlagFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(User.class,Boolean.class,"initFlag");

    public void init(User user){
        if(initFlagFieldUpdater.compareAndSet(user,false,true)){
            System.out.println("初始化完成");
        }
    }
}

public class AtomicReferenceFieldUpdaterTest {
    public static void main(String[] args) throws InterruptedException {
        User user = new User();

        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                user.init(user);
            }).start();
        }

        Thread.sleep(1000);
    }
}

初始化完成
LongAdder

优点:对高并发场景下,吞吐量明显提升
缺点:只能从0开始加法运算

为什么比AtomicLong快?

class LongAdder extends Striped64

abstract class Striped64{
//cpu的数量,Cell[]的最大长度
static final int NCPU = Runtime.getRuntime().availableProcessors();
//2的幂等
transient volatile Cell[] cells;
//基础value值,并发低时CAS更新该值
transient volatile long base;
//新建/扩容Cell[]时使用的锁标记
transient volatile int cellsBusy;
}

思想:先casBase,修改失败了,创建长度为2的Cell[]数组,通过线程id进行hash,通过hash值位运算得到数组下标, 将value值分散到数组下标中,不同的线程命中数组的不同槽中进行自旋CAS操作,降低冲突的概率,如果所有Cell也CAS修改失败则进行2的幂次扩容,但不会超过CPU核心数,最后需要获取值就将(槽中的值汇总+base)返回。(返回的值不是强一致性,而是最终一致性,sun()没有加锁,获取的同时有可能还有线程在访问)

LongAdder.png

LongAccumulator

对LongAdder的扩展,提供了自定义函数操作,可以加减乘除等运算。

原子操作性能对比
public class LongAdderTest {

    private static Long num = 0l;
    private static Lock lock = new ReentrantLock();
    private static AtomicLong atomicLong = new AtomicLong();
    private static LongAdder longAdder = new LongAdder();
    private static LongAccumulator longAccumulator = new LongAccumulator(((left, right) -> left+right),0);

    private static synchronized void  addSynchronized(){
        num++;
    }
    private static  void  addLock(){
        lock.lock();
        try {
            num++;
        }finally {
            lock.unlock();
        }
    }
    private static void  addAtomicLong(){
        atomicLong.incrementAndGet();
    }
    private static void  addLongAdder(){
        longAdder.add(1);
    }
    private static void  addLongAccumulator(){
        longAccumulator.accumulate(1);
    }

    private static CountDownLatch c1 = new CountDownLatch(10);
    private static CountDownLatch c2 = new CountDownLatch(10);
    private static CountDownLatch c3 = new CountDownLatch(10);
    private static CountDownLatch c4 = new CountDownLatch(10);
    private static CountDownLatch c5 = new CountDownLatch(10);


    public static void main(String[] args) throws InterruptedException {
        StopWatch time = new StopWatch();
        time.start("synchronized计数耗时");
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    for (int j = 0; j < 10000000; j++) {
                        addSynchronized();
                    }
                }finally {
                    c1.countDown();
                }
            }).start();
        }
        c1.await(1,TimeUnit.MINUTES);
        System.out.println("synchronized计数结果:"+num);
        num=0l;
        time.stop();

        time.start("ReentrantLock计数耗时");
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    for (int j = 0; j < 10000000; j++) {
                        addLock();
                    }
                }finally {
                    c2.countDown();
                }
            }).start();
        }
        c2.await(1,TimeUnit.MINUTES);
        System.out.println("ReentrantLock计数结果:"+num);
        time.stop();

        time.start("AtomicLong计数耗时");
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    for (int j = 0; j < 10000000; j++) {
                        addAtomicLong();
                    }
                }finally {
                    c3.countDown();
                }
            }).start();
        }
        c3.await(1,TimeUnit.MINUTES);
        System.out.println("AtomicLong计数结果:"+atomicLong.get());
        time.stop();

        time.start("LongAdder计数耗时");
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    for (int j = 0; j < 10000000; j++) {
                        addLongAdder();
                    }
                }finally {
                    c4.countDown();
                }
            }).start();
        }
        c4.await(1,TimeUnit.MINUTES);
        System.out.println("LongAdder计数结果:"+longAdder.longValue());
        time.stop();

        time.start("LongAccumulator计数耗时");
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    for (int j = 0; j < 10000000; j++) {
                        addLongAccumulator();
                    }
                }finally {
                    c5.countDown();
                }
            }).start();
        }
        c5.await(1,TimeUnit.MINUTES);
        System.out.println("LongAccumulator计数结果:"+longAccumulator.longValue());
        time.stop();


        System.out.println(time.prettyPrint(TimeUnit.MILLISECONDS));
    }
}

synchronized计数结果:100000000
ReentrantLock计数结果:100000000
AtomicLong计数结果:100000000
LongAdder计数结果:100000000
LongAccumulator计数结果:100000000
StopWatch '': running time = 9008 ms
---------------------------------------------
ms         %     Task name
---------------------------------------------
000003961  44%   synchronized计数耗时
000002639  29%   ReentrantLock计数耗时
000001921  21%   AtomicLong计数耗时
000000196  02%   LongAdder计数耗时
000000289  03%   LongAccumulator计数耗时
ThreadLocal

提供线程局部变量,每个线程都有自己独立的变量副本,避免了多线程争抢共享变量不安全问题。
使用try-finally进行remove()回收,尤其在线程池场景下,线程会被重复使用,不清理ThreadLocal变量会造成业务逻辑问题以及内存泄漏问题

public class Thread implements Runnable {
     ThreadLocal.ThreadLocalMap threadLocals = null;
}

public class ThreadLocal<T> {
    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
        }
    }
}

Thread.threadLocals = new ThreadLocal.ThreadLocalMap(ThreadLocal, value);

设置threadLocal变量,就是将threadLocal作为key,值为value的Entry对象放到了ThreadLocalMap中。

ThreadLocal.png

为什么ThreadLocalMap存放Entry的key是弱引用?
当ThreadLocal对象没有任何强引用的时候,触发GC后,ThreadLocal对象就会被回收掉,并且Entry对象的key为null。 如果变成强引用,那么发生GC时ThreadLocalMap还持有ThreadLocal的强引用,只要线程一直存在,ThreadLocalMap就一直不会被回收,从而导致ThreadLocal对象内存泄漏。

ThreadLocal的内存泄漏

public class ThreadLocalTest {

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    static class User{

        private String name;

        @Override
        protected void finalize() throws Throwable {
            System.out.println("User对象被GC回收了...");
        }
    }

    private static ThreadLocal<User> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {

        threadLocal.set(new User("张三"));
        System.out.println("对象:"+threadLocal.get());

        //threadLocal.remove();
        threadLocal = null;
        System.gc();
        Thread.sleep(1000);

        //Thread thread = Thread.currentThread();
        //System.out.println(thread);
    }
}

对象:ThreadLocalTest.User(name=张三)
内存泄漏.png

如果这个线程迟迟不结束(尤其在线程池场景下),就无法释放一系列强关联的Entry Value对象。所以如果不使用某个ThreadLocal对象一定要调用remove()删除,防止内存泄漏。

synchronized
对象结构.png
锁升级过程.png

jdk5只有重量级锁,jdk6开始优化了synchronized锁升级过程。

  • 偏向锁(默认开启):
    -XX:-UseBiasedLocking(关闭偏向锁,如果是高并发场景,可以选择关闭,直接升级到轻量级锁)
    通过CAS更新对象头中持有锁线程ID和锁标记,下一次进来,会判断当前线程是否与对象头持有锁线程ID相等,相等就直接进入同步代码块中,不需要每次都加锁,只有其他线程争抢偏向锁标记,持有偏向锁的线程才会释放,其他线程CAS锁标记失败,会等待全局安全点,检查持有偏向锁的线程是否在运行中,然后升级为轻量级锁。
  • 轻量级锁:
    就是通过CAS自旋进行争抢对象头锁标记,每次退出同步块都会释放锁,当自旋达到一定次数后(jdk6后是自适应动态的)升级为重量级锁。
  • 重量级锁:
    效率低下,monitor依赖底层操作系统的mutex lock(互斥锁)来实现的,挂起和恢复线程都需要操作系统内核态与用户态来回切换。
AQS

抽象了和锁交互的api,隐藏了实现细节,不需要关心锁等待和释放逻辑。

public abstract class AbstractQueuedSynchronizer{
    //头节点引用
    private transient volatile AbstractQueuedSynchronizer.Node head;
    //尾节点引用
    private transient volatile AbstractQueuedSynchronizer.Node tail;
    //锁标识 0表示没有线程使用,1表示有线程使用中
    private volatile int state;
    //当前正在使用中的线程对象
    private transient Thread exclusiveOwnerThread;

    static final class Node {
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        volatile int waitStatus;
    }
}
AQS.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,807评论 6 518
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,284评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 169,589评论 0 363
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,188评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,185评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,785评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,220评论 3 423
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,167评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,698评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,767评论 3 343
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,912评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,572评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,254评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,746评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,859评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,359评论 3 379
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,922评论 2 361

推荐阅读更多精彩内容