当多个线程访问某个类时,不管运行环境采用何种调度方式或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或协调,这个类都能表现出正确的行为,那么这个类是线程安全的。
1、原子性:提供互斥访问,同一时刻只能有一个线程对它进行操作;
2、可见性:一个线程对主内存的修改可以及时的被其他线程观察到;
3、有序性:即程序的执行顺序按照代码的先后顺序执行(实际会发生指令重排,但不影响最终结果)
分两个方面:CAS atomic包、锁sychronized和lock
AtomicXXX:CAS、Unsafe.compareAndSwapInt;
AtomicLong、LongAdder;
AtomicReference、AtomicReferenceFieldUpdater;
AtomicStampReference;
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。
CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作。
锁机制保证了只有获得锁的线程才能够操作锁定的内存区域。
synchronized:不可中断锁,依赖JVM,不需要自己释放锁,值得注意的是synchronized不那么重了,竞争不激烈的情况他都能解决,甚至比lock性能还好;
对于普通方法,锁的是当前实例对象,作用范围为当前方法内;
对于静态同步方法,锁的是当前类的Class对象,作用范围为整个静态方法;
对于同步方法块,锁是Synchronized括号里的对象,作用范围为代码块的范围。
lock:可中断锁,需要手动解锁,功能强大灵活,竞争激烈时也能维持常态;
Atomic:竞争激烈也是维持常态,比lock性能好,但只能同步一个值。
一个线程对主内存的修改可以及时的被其他线程观察到;
导致共享变量在线程间不可见的原因:
1、线程交叉执行
2、重排序结合线程交叉执行
3、共享变量更新后的值没有及时工作内存和主内存之间更新
可见性-----synchronized
jvm关于synchronized的两条规定:
1、线程解锁前,必须把共享变量的最新值刷新到主内存;
2、线程加锁时,将清空工作内存中共享变量中的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意加锁和解锁是同一把锁)
volatile 需注意:进行加操作不具有线程安全性。适合做标记量;
Java内存模型中,允许编译器和处理器对指令进行重排序,但重排序过程不会影响单线程程序的执行,却会影响多线程并发执行的正确性。
保证有序性:volatile、synchronized、lock
happen-before原则:
1、程序次序原则:按照代码书写顺序执行;虚拟机可能会对 不影响执行结果的指令 重排。
2、锁定规则:unlock操作先行发生于后面对同一个锁的lock操作,必须对前一个锁解锁才能加锁。
3、volatile变量规则:对一个数量的写操作先行发生于后面对这个变量的读操作。
4、传递规则:如果操作A先行发生于操作B,操作B先行发生于操作C,则操作A先行发生于操作C;
5、 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
6、线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
7、线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
8、 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;