synchronized
synchronized作为Java中的关键字,可以实现同步代码块。可用来给对象和方法或代码块进行加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。有两种方式对方法进行加锁操作,第一,在方法签名处加synchronized关键字;第二,使用synchronized(对象或类)进行同步。这里的原则是锁的范围尽可能小,锁的时间尽快能短,能锁对象,就不要锁类;能锁代码块,就不要锁方法。
synchronized修饰方法、代码块的区别
静态方法跟随类的生命周期,所以静态方法锁住的是对象,程序执行的之后会交替输出:
public class Data {
public static synchronized void test1(){
System.out.println("start.....");
try{
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
}
System.out.println("end......");
}
}
public class TestSynchronized {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 5; i++) {
Data.test1();
}
}).start();
}
}
输出:
start.....
end......
start.....
end......
start.....
end......
start.....
end......
start.....
end......
由于锁住的对象,即便test2()在主线程睡眠1s后执行,但是由于test1()和test2()锁定的对象不一致,所以test2()还是输出
public class Data {
public synchronized void test1(){
try{
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
}
System.out.println("test1.....");
}
public synchronized void test2(){
System.out.println("test2........");
}
}
public class TestSynchronized {
public static void main(String[] args) {
Data data = new Data();
Data data1 = new Data();
new Thread(()->{
data.test1();
}).start();
try{
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
}
new Thread(()->{
data1.test2();
}).start();
}
}
输出:
test2........
test1.....
由于锁住的是字节码文件,所以每次不管线程创建了多少个对象,仍然会交替打印输出
public class Data {
public void test(){
synchronized (Data.class){
System.out.println("start...");
try{
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
}
System.out.println("end....");
}
}
}
public class TestSynchronized {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(()->{
Data data = new Data();
data.test();
}).start();
}
}
}
输出:
start...
end....
start...
end....
start...
end....
start...
end....
start...
end....
由于只new了一个对象,并且该对象加锁,所以五个线程需要等待资源释放,所以仍然是交替执行
public class Data {
public void test(){
synchronized (this){
System.out.println("start...");
try{
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
}
System.out.println("end....");
}
}
}
public class TestSynchronized {
public static void main(String[] args) {
Data data = new Data();
for (int i = 0; i < 5; i++) {
new Thread(()->{
data.test();
}).start();
}
}
}
输出:
start...
end....
start...
end....
start...
end....
start...
end....
start...
end....
synchronized锁特性由JVM负责实现。在JDK的不断优化迭代中,synchronized锁的性能得到极大提升,特别是偏向锁的实现,使得synchronized已经不是昔日那个低性能且笨重的锁了。JVM底层通过监视锁来实现synchornized同步的。监视锁如monitor,是每个对象与生俱来的一个隐藏字段。使用synchronized时,JVM会根据synchronized的当前使用环境,找到对应对象得monitor,再根据monitor的状态进行加、解锁的判断。例如,线程在进入同步方法或代码块时,会根据该方法或代码块所属对象的monitor,进行加锁判断。如果成功枷锁就成为该monitor的唯一持有者。monitor在被释放前,不能再被其他线程获取。下面通过 javap -C xxxx.java 命令查看反编译的文件:
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void test();
Code:
0: ldc #2
2: dup
3: astore_1
4: monitorenter
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String start...
10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: getstatic #6 // Field java/util/concurrent/TimeUnit.SECONDS:Ljava/util/concurrent/TimeUnit;
16: ldc2_w #7 // long 2l
19: invokevirtual #9 // Method java/util/concurrent/TimeUnit.sleep:(J)V
22: goto 26
25: astore_2
26: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
29: ldc #11 // String end....
31: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
34: aload_1
35: monitorexit
36: goto 44
39: astore_3
40: aload_1
41: monitorexit
42: aload_3
43: athrow
44: return
Exception table:
from to target type
13 22 25 Class java/lang/Exception
5 36 39 any
39 42 39 any
}
方法元信息中会使用ACC_SYNCHRONIZED标识该方是一个同步方法。同步代码块中会使用monitorenter及monitorexit两个字节码指令获取和释放monitor。如果使用monitorenter进入时monitor为0,表示该线程可以持有monitor后续代码,并将monitor加1;如果当前线程已经有monitor,那么继续加1,如果monitor非0,其他线程就会进入阻塞状态。JVM对synchronized的优化主要在于对monitor的加锁,解锁上。JDK6后不断优化使得synchronized提供三种锁的实现,包括偏向锁、轻量级锁、重量级锁,还提供自动升级和降级机制。JVM就是利用CAS在对象头上设置线程ID,表示这个对象偏向于当前线程,这就是偏向锁。
总体流程:
synchronized和volatile的区别
volatile解决的是多线程共享变量的可见性问题,但不能保证原子性。 如果是一写多读的并发场景,使用volatile修饰变量则非常合适。
synchronized能保证可见性、原子性、有序性,但会造成线程阻塞
synchronized和Lock的区别
synchronized是关键字,属于JVM层面,自动释放锁,不可以中断
Lock是接口,需要手动释放,可以中断
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long var1, TimeUnit var3) throws InterruptedException;
void unlock();
Condition newCondition();
synchronized产生死锁例子
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
new Thread(()->{
synchronized (a){
System.out.println(Thread.currentThread().getName() + " a........");
try{
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
}
synchronized (b){
System.out.println(Thread.currentThread().getName() + " b........");
}
}
},"t1").start();
new Thread(()->{
synchronized (b){
System.out.println(Thread.currentThread().getName() + " b........");
try{
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
}
synchronized (a){
System.out.println(Thread.currentThread().getName() + " a........");
}
}
},"t2").start();
}