synchronize
synchronized锁什么?锁对象。
可能锁对象包括: this, 临界资源对象(Object),Class类对象。同步 - 原子性 加锁的目的: 就是为了保证操作的原子性
同步方法
synchronized T methodName(){}
同步方法锁定的是当前对象。当多线程通过同一个对象引用多次调用当前同步方法时,需同步执行。
静态同步方法锁住的是类对象,与普通对象的同步方法不会发生互斥。
同步代码块
同步代码块的同步粒度更加细致,是商业开发中推荐的编程方式。可以定位到具体的同步位置,而不是简单的将方法整体实现同步逻辑。在效率上,相对更高。
静态同步代码块锁住的也是类对象,取决于锁的是哪个类对象。( synchronized(XXX.class){ } )
锁定临界对象
T methodName(){ synchronized(object){} }
同步代码块在执行时,是锁定object对象。当多个线程调用同一个方法时,锁定对象不变的情况下,需同步执行。
锁定当前对象
T methodName(){ synchronized(this){} }
当锁定对象为this时,类似于同步方法。
PS
同步方法只影响锁定同一个锁对象的同步方法。不影响其他线程调用非同步方法,或调用其他锁资源的同步方法。
子类同步方法覆盖父类同步方法。可以指定调用父类的同步方法。相当于锁的重入。
当同步方法中发生异常的时候,自动释放锁资源。不会影响其他线程的执行。 注意,同步业务逻辑中,如果发生异常如何处理。
volatile
变量的线程可见性。在线程运行的过程中,如果某变量被其他线程修改,可能造成数据不一致的情况,从而导致结果错误。而volatile修饰的变量是线程可见的,当JVM解释volatile修饰的变量时,会通知CPU,在计算过程中,每次使用变量参与计算时,都会检查内存中的数据是否发生变化,而不是一直使用CPU缓存中的数据,可以保证计算结果的正确。
原理:
其实JVM会向处理器发送一条LOCK前缀的指令,在多核的情况下会做两件事:
1、将当前处理器缓存行的数据写回到系统内存 2、写回内存的操作会使其他CPU里缓存了该内存地址的数据无效。在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致协议(每个处理器通过嗅探在总线上传播的数据来检查自己的缓存是否过期了,处理器发现自己的缓存行对应的内存地址被修改了,就会将当前处理器的缓存设置为无效状态,那么下次要再对数据进行操作的时候就会从内存再次读取。)volatile只是通知底层计算时,CPU检查内存数据,而不是让一个变量在多个线程中同步。PS
volatile的非原子性问题,volatile, 只能保证可见性,不能保证原子性。不是枷锁问题,只是内存数据可见。
原子操作类型
AtomicXxx,其中的每个方法都是原子操作。可以保证线程安全。
在concurrent.atomic包中定义了若干原子类型,这些类型中的每个方法都是保证了原子操作的。多线程并发访问原子类型对象中的方法,不会出现数据错误。在多线程开发中,如果某数据需要多个线程同时操作,且要求计算原子性,可以考虑使用原子类型对象。
注意:
原子类型中的方法是保证了原子操作,但多个方法之间是没有原子性的。如:
AtomicInteger i = new AtomicInteger(0);if(i.get() != 5) i.incrementAndGet();
在上述代码中,get方法 和 incrementAndGet方法 都是原子操作,但复合使用时,无法保证原子性,仍旧可能出现数据错误。
其他同步类
CountDownLatch 门闩
门闩是concurrent包中定义的一个类型,是用于多线程通讯的一个辅助类型。
门闩相当于在一个门上加多个锁,当线程调用await方法时,会检查门闩数量,如果门闩数量大于0,线程会阻塞等待。当线程调用countDown时,会递减门闩的数量,当门闩数量为0时,await阻塞线程可执行。