实现线程安全的方式
1、使用synchronized关键字
Java中关键字synchronized修饰的方法或同步代码块,它保证多线程在同一时刻只有一个线程处于被修饰的方法或者同步代码块中,保证线程对变量访问的可见性和排他性。
2、使用volatile关键字
volatile关键字用来修饰共享变量。保证被修饰的变量在被一个线程修改后,都会通知其他线程,其他线程需要操作该变量时会重新获取,这样每个线程在操作该共享变量时获取到的值都是最新的,但是volatile关键字无法保证原子性(例如指令拆分)
3、使用原子类代替基本数据类型
java提供三种类型的原子类,当某个操作因为不是原子操作导致的线程安全问题的时候,可以使用原子类来替代。比如:多线程环境下执行a++,可以使用AtomicInteger类incrementAndGet()方法实现。相比synchronized,原子类是使用乐观锁来实现线程安全,synchronized使用悲观锁来实现线程安全。
4、使用ThreadLocal进行隔离
ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
synchronized关键字是怎么实现的?
Java中关键字synchronized修饰的方法或同步代码块,它保证多线程在同一时刻只有一个线程处于被修饰的方法或者同步代码块中,保证线程对变量访问的可见性和排他性。
底层实现原理
synchronized底层是用监视器(Monitor)机制实现的,任意一个对象都拥有自己的监视器,当这个对象在同步块被synchronized修饰时,执行该区域代码的线程必须先获取到该对象的监视器(Moniter Enter)才能进入同步方法或者同步代码块,而没有获取到该对象的监视器的线程会阻塞在同步块或者同步方法的入口处。此时线程进入同步队列,处于阻塞状态,当获取对象的监视器的线程释放了锁(Monitor Exit)则该释放操作会唤醒阻塞在同步队列中的线程,使其重新尝试对该对象监视器的获取。
- 进入时,执行 monitorenter,将计数器 +1,释放锁 monitorexit 时,计数器 -1
- 当一个线程判断到计数器为 0 时,则当前锁空闲,可以占用;反之,当前线程进入等待状态