这是一个系列文章:
本篇主要的内容是:
- volatile关键字
- LockSupport类使用
- Lock类使用
volatile关键字
要动volatile
关键字的使用,需要对java的内存模型&共享变量的可见性有一定的了解。在JVM中,共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存保存了该线程使用到的主内存的副本拷贝,线程对该变量的所有操作都必须在私有存中进行,而不能直接读写主内存中的变量。
在线程A中将共享变量修改为了某个值,这时候还没有同步到主内存中,然后线程B中保留了共享内存的旧值,就会导致共享变量值不一致,要解决这种方式最简单的就是加锁,但是使用synchronized
或者是Lock
等的方式会太重量了,比较合理的方式就是实用volatile
。
volatile
的特性:
- 保证可见性,不保证原子性。当些一个
volatile
变量的时候,JVM会把线程本地内存也同步到主内存中,这个写操作会导致其他线程中的volatile
变量缓存无效。 - 禁止指令重排
一个简单的例子,我们可以不使用synchronized
那么重量级的来实现:
1 | static class JmmDemo implements Runnable { |
我们就可以不用synchronized
去实现这样的一个功能,同时synchronized
也无法避免指令的重排,它能做的是原子性,可见性和有序性。
参考文章:
LockSupport
LockSupport
是java.util.concurrent.locks
包下的一个类,可以用于暂停线程和恢复线程,它是使用Unsafe
实现的,它的方法如下:
- park() : 暂停某个线程
- LockSupport.unpark(t1) :唤起某个线程
例子:
1 | // 打印的时间会>=2000 |
参考文章:LockSupport详解
Lock
Lock
与synchronized
是租用是比较类似的,但是它的使用范围会更广更优雅,Lock
可以手动的释放锁,获取锁,可中断的获取锁,超时获取锁,相对而言会更加的灵活,同时它也可以用于替换wait/notify
的那一套,避免可能出现的死锁(没有唤起wait()
),比如多个生产者1个消费者的这种情况。
Lock
是一个接口,定义如下
1 | public interface Lock { |
Lock的实现代表类是ReentrantLock
(可重入锁),我们先看下ReentrantLock
,它是依靠CAS和LockSupport实现的,先看下面一个简单的例子(用于替换synchronized
):
1 | // lock()方法会阻塞住,直到能获取锁 |
只会打印100000,0
或者是-100000,0
,作用是与synchronized
类似,需要注意的是我们都需要在finally
中调用unlock()
方法,避免程序运行出现了异常没有释放锁。
它的构造函数:
1 | // 非公平锁 |
非公平与公平锁的不同
- 非公平锁在调用
lock
之后,首先会调用CAS进行一次抢夺,假如锁没有被占用,就直接获取锁返回 - 非公平锁在CSA失败后,和公平锁一样进入
tryAcquire
,假如发现这时候锁被释放了,非公平锁就会直接CAS枪锁,但是公平锁会判断等待队列是由有线程处于等待状态,有就不会去抢夺,乖乖排在后面。 - 假如在
tryAcquire
抢夺失败,则线程进入阻塞状态。
相对而言,非公平锁性能更好,当然非公平锁让获取锁的时间变得更加不确定,可能导致阻塞队列中的线程长期处于饥饿状态。文章来源:理解ReentrantLock的公平锁和非公平锁
使用了更多API的例子:
1 | // trylock() 例子,返回false的时候不阻塞,可以向下继续运行 |
synchronized
与ReentrantLock
的比较
synchronized
是独占锁,加锁和解锁过程自动进行,易于操作。ReentrantLock
也是独占锁,加锁和解锁需要手动,不易操作,但是非常灵活synchronized
可重入,因为加锁和解锁自动运行,不必担心最后锁是否释放。ReentrantLock
也可以重入,但是加锁和解锁需要手动进行,而且次数需要一样。synchronized
不可响应中断,一个线程获取不到锁就只能一直等待,ReentrantLock
可以响应中断。
ReentrantLock
的lock
方法调用需要再try块之外,假如是获取锁成功了的,则需要再finally
中释放锁。
ReentrantLock
还有一个Condition newCondition()
方法,可以用于平替wait/notify
。Condition
是一个接口,它的定义是:
1 | public interface Condition { |
它大体与Object的wait/notify
机制差不多,其中await()
对应的wait()
,signal()
对应的notify()
。Condition
的await()
与signal()
都必须在lock()
和unlock()
之间才能使用。
我们使用wait/notify
在设计生产者与消费者的时候,假如是一对多的情况下,则可能会有死锁或者是多活的情况,就要么就完全的阻塞住了,不能做到一个时刻只有一个线程执行。我们每一个ReentrantLock
可以有多个Condition
,它们可以共用一个锁,但是可以自己去调用await()/signal()
而不影响别的Condition
,但是wait/notify
是只能同一个锁对象,都得调用这个锁的wait/notify
。例如一对多的生产-消费者,例子如下:
1 | private static List<String> data = new ArrayList<>(); |
参考文章: