synchronized
synchronized关键字在javac编译之后会在同步块的前后分别形成monitorenter,monitorexit两个字节码指令。都需要reference对象来指明锁定和解锁的对象,如果没有明确指定,根据synchronized修饰的方法类型(实例方法或者类方法),来决定取代码所在的对象实例还是取类型对应的Class对象来作为线程要持有的锁。
在执行monitorenter指令时,首先要尝试获取对象的锁,如果对象没有被锁定,或者当前线程已经持有了那个对象的锁,就把锁的计数器的值加一,而在执行monitorexit指令时,是对计数器的值减一。计数器的值为零时,锁被释放。如果获取对象锁失败,那当前线程就应当被阻塞等待,直到请求锁定的对象被持有它的线程释放为止。
关于synchronized的直接推论:
- 被synchronized修饰的同步块对同一条线程来说是可重入的。同一线程反复进入同步块也不会出现死锁情况。
- 被synchronized修饰的同步块在持有锁的线程执行完毕并释放锁之前,会无条件的阻塞后面的其他线程的进入。无法强制让其释放锁,也无法中断等待锁的线程。
JUC
JDK5之后,Java类库中新提供了 java.util.concurrent 包(J.U.C), 其中的java.util.locks.Lock接口成为了Java另一种互斥同步手段。
空重入锁(ReentrantLock)是Lock接口常见的一个实现类,它比synchronized增加了一些功能:
- 等待可中断:当持有锁的线程长时间不释放时,正在等待的线程可选择放弃等待。
- 公平锁:多个线程在等待同一个锁的时候,必须按照申请锁的时间的先后来依次获得锁;非公平锁:任何一个在等待的线程都有机会占用锁。ReentrantLock默认为非公平锁,通过构造函数可以转为公平锁(boolean值),使用了公平锁会使ReentrantLock的性能下降,影响吞吐量。
- 锁绑定多个条件:一个ReentrantLock对象可以同时绑定多个Condition对象。
JDK6中对synchronized进行了优化后synchronized与ReentrantLock的性能持平。
sychronized与Lock的不同
- synchronized是在Java语法层面的同步,Lock是个接口。
- Lock需要自己手动释放锁,不然会造成死锁,而synchronized会自动释放
- Java虚拟机可以在线程和对象的元数据中记录synchronized中锁的相关信息,而Lock很难得知。
CAS 保证操作的原子性
CAS(Compare and Swap):CAS指令需要三个操作数,分别是内存地址V,旧的预期值A,和准备设置的新值B。
CAS指令执行时,当且仅当V符合A时,处理器才会用B更新V的值,否则它就不执行更新,执行期间不会被其他线程打断。
CAS漏洞:ABA问题
ABA问题:检测V是否符合A时,A可能被其他线程所修改(从A改为B然后从B再改回A),但是CAS不能发现,还是会继续执行。大部分情况下,ABA问题不会影响程序并发的正确性。
ABA问题的解决
如果变量的值只能朝着一个方向转换,比如从A到B,B到C,不构成环形,就不会出现ABA问题。JDK中的AtomicStampedReference类给每个变量的状态值都配置了一个时间戳,从而避免了ABA问题的产生。
private static AtomicStampedReference atoStaRef = new AtomicStampedReference(100, 0);
Thread threadC = new Thread(()->{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
// 每次操作时间戳加一
atoStaRef.compareAndSet(100,101,atoStaRef.getStamp(),atoStaRef.getStamp()+1);
atoStaRef.compareAndSet(101,100,atoStaRef.getStamp(),atoStaRef.getStamp()+1);
});
Thread threadD = new Thread(()->{
// 获得最初版本的时间戳
int stamp = atoStaRef.getStamp();
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
boolean flag = atoStaRef.compareAndSet(100,101,stamp,stamp+1);
System.out.println(flag);
});
threadC.start();
threadD.start();
结果为false
AtomicInteger 没有解决ABA问题例子
private static AtomicInteger atomicInteger = new AtomicInteger(100);
public static void main(String[] args) throws InterruptedException
{
Thread threadA = new Thread(()->{
atomicInteger.compareAndSet(100,101);
atomicInteger.compareAndSet(101,100);
});
Thread threadB = new Thread(()->{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
boolean flag = atomicInteger.compareAndSet(100,101);
System.out.println(flag);
});
threadA.start();
threadB.start();
threadA.join();
threadB.join();
}
结果为:true