安徽建网站公司,网站开发文档管理工具,查询域名是否被注册,专业做家电的网站文章目录 1. synchronized与ReentrantLock的区别2. synchronized的作用3. synchronized的使用3.1 修饰实例方法#xff0c;作用于当前实例#xff0c;进入同步代码前需要先获取实例的锁3.2 修饰静态方法#xff0c;作用于类的Class对象#xff0c;进入修饰的静态方法前需要… 文章目录 1. synchronized与ReentrantLock的区别2. synchronized的作用3. synchronized的使用3.1 修饰实例方法作用于当前实例进入同步代码前需要先获取实例的锁3.2 修饰静态方法作用于类的Class对象进入修饰的静态方法前需要先获取类的Class对象的锁3.3 修饰代码块需要指定加锁对象(记做lockobj)在进入同步代码块前需要先获取lockobj的锁 4. 分析代码是否互斥5. synchronized的可重入性6. 发生异常synchronized会释放锁7. synchronized的实现原理与应用包含锁的升级过程 1. synchronized与ReentrantLock的区别
区别点synchronizedReentrantLock是什么关键字是 JVM 层面通过监视器实现的类基于 AQS 实现的公平锁与否非公平锁支持公平锁和非公平锁默认非公平锁获取当前线程是否上锁无可以isHeldByCurrentThread()条件变量无支持条件变量newCondition()异常处理在 synchronized 块中发生异常锁会自动释放在 ReentrantLock 中没有在 finally 块中正确地调用 unlock() 方法则可能会导致死锁灵活性1自动加锁和释放锁手动加锁和释放锁灵活性2无允许尝试去获取锁而不阻塞如 tryLock 方法并且可以指定获取锁等待的时间如 tryLock(long time, TimeUnit unit)。可中断性不可中断除非发生了异常允许线程中断另一个持有锁的线程这样持有锁的线程可以选择放弃锁并响应中断。1.tryLock(long timeout, TimeUnit unit)2.lockInterruptibly()和interrupt()配合使用锁的内容对象锁信息保存在对象头中int类型的变量来标识锁的状态private volatile int state;锁升级过程无锁-偏向锁-轻量级锁-重量级锁无使用位置普通方法、静态方法、代码块代码块方法里的代码初始化块都是代码块
2. synchronized的作用 在Java中使用synchronized关键字可以确保任何时刻只有一个线程可以执行特定的方法或者代码块。这有助于防止数据竞争条件race conditions和其他由于线程间共享资源而产生的问题。 当一个方法或代码块被声明为synchronized它意味着在该方法或代码块执行期间其他试图获得相同锁的线程将被阻塞直到持有锁的线程释放该锁。这个锁通常是对象的一个监视器monitor对于静态方法来说是类的Class对象对于实例方法则是拥有该方法的对象。 synchronized可以限制对共享资源的访问它锁定的并不是临界资源而是某个对象只有线程获取到这个对象的锁才能访问临界区进而访问临界区中的资源。 保证线程安全。 当多个线程去访问同一个类对象或方法的时候该类都能表现出正常的行为与自己预想的结果一致那我们就可以说这个类是线程安全的。 造成线程安全问题的主要诱因有两点 存在共享数据(也称临界资源)存在多条线程共同操作共享数据 当存在多个线程操作共享数据时需要保证同一时刻有且只有一个线程在操作共享数据其他线程必须等到该线程处理完数据后再进行这种方式有个高尚的名称叫互斥锁即能达到互斥访问目的的锁也就是说当一个共享数据被当前正在访问的线程加上互斥锁后在同一个时刻其他线程只能处于等待的状态直到当前线程处理完毕释放该锁。在 Java 中关键字 synchronized可以保证在同一个时刻只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作)同时我们还应该注意到synchronized另外一个重要的作用synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到保证可见性完全可以替代volatile功能。
3. synchronized的使用
下面三种本质上都是锁对象
3.1 修饰实例方法作用于当前实例进入同步代码前需要先获取实例的锁 示例代码 public class SynchronizedDemo2 {int num 0;public synchronized void add() {
// public void add() {for (int i 0; i 10000; i) {num;}}public static class AddDemo extends Thread {private SynchronizedDemo2 synchronizedDemo2;public AddDemo(SynchronizedDemo2 synchronizedDemo2) {this.synchronizedDemo2 synchronizedDemo2;}Overridepublic void run() {this.synchronizedDemo2.add();}}public static void main(String[] args) throws InterruptedException {// 要想拿到临界资源就必须先获得到这个对象的锁。SynchronizedDemo2 synchronizedDemo2 new SynchronizedDemo2();AddDemo addDemo1 new AddDemo(synchronizedDemo2);AddDemo addDemo2 new AddDemo(synchronizedDemo2);AddDemo addDemo3 new AddDemo(synchronizedDemo2);addDemo1.start();addDemo2.start();addDemo3.start();// 阻塞主线程addDemo1.join();addDemo2.join();addDemo3.join();// 打印结果System.out.println(synchronizedDemo2.num);}
}打印 期望结果30000 无synchronized结果23885 有synchronized结果30000 synchronize作用于实例方法需要注意 实例方法上加synchronized线程安全的前提是多个线程操作的是同一个实例如果多个线程作用于不同的实例那么线程安全是无法保证的 同一个实例的多个实例方法上有synchronized这些方法都是互斥的同一时间只允许一个线程操作同一个实例的其中的一个synchronized方法
3.2 修饰静态方法作用于类的Class对象进入修饰的静态方法前需要先获取类的Class对象的锁
锁定静态方法需要通过类.class或者直接在静态方法上加上关键字。但是类.class不能使用this来代替。注在同一个类加载器中class是单例的这也就能保证synchronized能够只让一个线程访问临界资源。 示例代码 public class SynchronizedDemo1 {static int num 0;// 加上synchronized保证线程安全public static synchronized void add() {// public static void add() {for (int i 0; i 10000; i) {num;}}// 同上public static void add1() {synchronized (SynchronizedDemo1.class) {for (int i 0; i 10000; i) {num;}}}public static class AddDemo extends Thread {Overridepublic void run() {SynchronizedDemo1.add();}}public static void main(String[] args) throws InterruptedException {AddDemo addDemo1 new AddDemo();AddDemo addDemo2 new AddDemo();AddDemo addDemo3 new AddDemo();addDemo1.start();addDemo2.start();addDemo3.start();// 阻塞主线程addDemo1.join();addDemo2.join();addDemo3.join();// 打印结果System.out.println(SynchronizedDemo1.num);}
}打印 期望结果30000 无synchronized结果14207 有synchronized结果30000 3.3 修饰代码块需要指定加锁对象(记做lockobj)在进入同步代码块前需要先获取lockobj的锁
若是this相当于修饰实例方法 示例代码 public class SynchronizedDemo3 {private static Object lockobj new Object();private static int num 0;public static void add() {synchronized (lockobj) {for (int i 0; i 10000; i) {num;}}}public static class AddDemo extends Thread {Overridepublic void run() {SynchronizedDemo3.add();}}public static void main(String[] args) throws InterruptedException {AddDemo addDemo1 new AddDemo();AddDemo addDemo2 new AddDemo();AddDemo addDemo3 new AddDemo();addDemo1.start();addDemo2.start();addDemo3.start();// 阻塞主线程addDemo1.join();addDemo2.join();addDemo3.join();// 打印结果System.out.println(SynchronizedDemo3.num);}
}打印 期望结果30000 无synchronized结果28278 有synchronized结果 示例代码 4. 分析代码是否互斥
分析代码是否互斥的方法先找出synchronized作用的对象是谁如果多个线程操作的方法中synchronized作用的锁对象一样那么这些线程同时异步执行这些方法就是互斥的。 示例代码 public class SynchronizedDemo4 {// 作用于当前类的实例对象public synchronized void m1() {}// 作用于当前类的实例对象public synchronized void m2() {}// 作用于当前类的实例对象public void m3() {synchronized (this) {}}// 作用于当前类Class对象public static synchronized void m4() {}// 作用于当前类Class对象public static void m5() {synchronized (SynchronizedDemo4.class) {}}public static class T extends Thread {SynchronizedDemo4 demo;public T(SynchronizedDemo4 demo) {this.demo demo;}Overridepublic void run() {super.run();}}public static void main(String[] args) {SynchronizedDemo4 d1 new SynchronizedDemo4();Thread t1 new Thread(() - {d1.m1();});Thread t2 new Thread(() - {d1.m2();});Thread t3 new Thread(() - {d1.m3();});SynchronizedDemo4 d2 new SynchronizedDemo4();Thread t4 new Thread(() - {d2.m2();});Thread t5 new Thread(() - {SynchronizedDemo4.m4();});Thread t6 new Thread(() - {SynchronizedDemo4.m5();});t1.start();t2.start();t3.start();t4.start();t5.start();t6.start();}
}结论 线程t1、t2、t3中调用的方法都需要获取d1的锁所以他们是互斥的t1/t2/t3这3个线程和t4不互斥他们可以同时运行因为前面三个线程依赖于d1的锁t4依赖于d2的锁t5、t6都作用于当前类的Class对象锁所以这两个线程是互斥的和其他几个线程不互斥 5. synchronized的可重入性 示例代码 public class SynchronizedDemo5 {synchronized void method1() {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}method2();System.out.println(method1 thread- Thread.currentThread().getName() end);}synchronized void method2() {try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(method2 thread- Thread.currentThread().getName() end);}public static void main(String[] args) {SynchronizedDemo5 t5 new SynchronizedDemo5();new Thread(t5::method1, 1).start();new Thread(t5::method1, 2).start();new Thread(t5::method1, 3).start();}
}打印 method2 thread-1 end method1 thread-1 end method2 thread-3 end method1 thread-3 end method2 thread-2 end method1 thread-2 end 结论 当线程启动的时候已经获取了对象的锁等method1调用method2方法的时候同样是拿到了这个对象的锁。所以synchronized是可重入的。 6. 发生异常synchronized会释放锁 示例代码 public class SynchronizedDemo6 {int num 0;synchronized void add() {System.out.println(thread Thread.currentThread().getName() start);while (num 7) {num;System.out.println(thread Thread.currentThread().getName() , num is num);if (num 3) {throw new NullPointerException();}}}public static void main(String[] args) throws InterruptedException {SynchronizedDemo6 synchronizedDemo6 new SynchronizedDemo6();new Thread(synchronizedDemo6::add, 1).start();Thread.sleep(1000);new Thread(synchronizedDemo6::add, 2).start();}
}打印 thread1 start thread1, num is 1 thread1, num is 2 thread1, num is 3 Exception in thread “1” java.lang.NullPointerException at com.xin.demo.threaddemo.lockdemo.synchronizeddemo.SynchronizedDemo6.add(SynchronizedDemo6.java:14) at java.lang.Thread.run(Thread.java:748) thread2 start thread2, num is 4 thread2, num is 5 thread2, num is 6 thread2, num is 7 thread2, num is 8 结论 发生异常synchronized会释放锁 7. synchronized的实现原理与应用包含锁的升级过程
我的另一篇读书笔记Java并发机制的底层实现原理 2.2节
锁的升级过程无锁-偏向锁-轻量级锁-重量级锁详细情况还是看上面这篇文章 无锁 偏向锁在锁对象的对象头中记录一下当前获取到该锁的线程ID该线程下次如果又来获取该锁就可以直接获取到了也就是支持锁重入 轻量级锁当两个或以上线程交替获取锁但并没有在对象上并发的获取锁时偏向锁升级为轻量级锁。在此阶段线程采取CAS的自旋方式尝试获取锁避免阻塞线程造成的CPU在用户态和内核态间转换的消耗。轻量级锁时CPU是用户态。 重量级锁两个或以上线程并发的在同一个对象上进行同步时为了避免无用自旋消耗CPU轻量级锁会升级成重量级锁。重量级锁时CPU是内核态。
参考1【多线程与高并发】- synchronized锁的认知
参考2线程安全和synchronized
建议阅读文章1【并发编程系列2】synchronized锁升级原理分析(偏向锁-轻量级锁-重量级锁)
建议阅读文章2Java 对象、对象头mark word、锁升级、对象占内存大小