1. 当一个线程正在访问一个对象的 synchronized 实例方法,那么其他线程不能访问该对象的其他 synchronized 方法
对于每一个实例方法,锁是加在对象上的,一个线程访问其中一个 synchronized 修饰的实例方法时,这个线程就拿到了对象的锁,所以其他线程无法拿到该对象的锁,也就无法访问该对象的其他 synchronized 方法
2. synchronized 修饰实例方法,两个(或多个)线程拿到同一个对象的锁,实现正确并发。
一个简单的 +1 线程:
1 class MyThread implements Runnable { 2 static int i; 3 4 public int getI() { 5 return i; 6 } 7 8 public synchronized void incrI() { 9 i++; 10 } 11 12 @Override 13 public void run() { 14 for (int j = 0; j < 1000000; j++) { 15 incrI(); 16 } 17 } 18 }
两个线程拿到同一个对象(myThread)的锁:
1 public class SynTest { 2 public static void main(String[] args) throws InterruptedException { 3 MyThread myThread = new MyThread(); 4 Thread t1 = new Thread(myThread); 5 Thread t2 = new Thread(myThread); 6 t1.start(); 7 t2.start(); 8 t1.join(); 9 t2.join(); 10 11 System.out.println(MyThread.i); 12 } 13 }
3. 当 synchronized 作用于静态方法时,其锁就是当前类的 class 对象锁。所以可以用两个不同的对象构造两个线程,但是这两个线程拿到的都是同一把锁(class 对象锁),所以仍然可以实现正确的并发。
1 public synchronized static void incrI() { 2 i++; 3 }
1 public class SynTest { 2 public static void main(String[] args) throws InterruptedException { 3 MyThread myThread = new MyThread(); 4 Thread t1 = new Thread(myThread); 5 Thread t2 = new Thread(new MyThread()); 6 t1.start(); 7 t2.start(); 8 t1.join(); 9 t2.join(); 10 11 System.out.println(MyThread.i); 12 } 13 }
4. synchronid 底层实现
修饰代码块:monitorenter 和 monitorexit 指令。
测试代码:
1 public class SynBlock { 2 private int i; 3 4 public void incr() { 5 synchronized (this) { 6 i++; 7 } 8 } 9 }
javap 反编译之后:会有两个 exit,第一个是正常退出时执行,第二个是自动产生的,用于异常结束时执行
修饰方法:使用 ACC_SYNCHRONIZED 标志
5. synchronized 优化:偏向锁、轻量级锁
偏向锁:同一个线程多次获取同一把锁时,无需申请,直接获取,有利于提高性能。适用于锁竞争不激烈的情况。
轻量级锁:偏向锁失败之后,变为轻量级锁,适用于多个线程交替执行的情况,竞争少。
自旋锁:会假设在不远的将来线程很快获得锁,让当前线程做空循环,如果可以获得锁,直接拿到,否则膨胀为重量级锁。
6 . 可重入的含义:一个线程请求自己持有锁的资源,直接获取锁,无需再次申请。
7. wait/notify/notifyAll 必须在 synchronized 中,因为这三个方法必须拿到 monitor 对象,而 synchronized 可以保证拿到 monitor 对象。
参考:
https://blog.csdn.net/javazejian/article/details/72828483
原文:https://www.cnblogs.com/ainsliaea/p/11370018.html