synchronized锁和lock锁的区别
- synchronized是Java内置的关键字,lock是一个Java类(JUC下的接口 )
- synchronized是无法判断获取锁的状态,lock可以判断获取锁的状态
- synchronized是全自动的,会自动释放锁,lock是手动的,必须手动释放锁(unLock),如果不释放锁,会造成死锁
- synchronized比如有两个线程,线程1获得锁,阻塞,线程2会一直等,lock锁就不一定会一直等待, 可以用tryLock()方法尝试获取锁
- synchronized默认是可重入锁,不可以中断的,非公平,由于它是Java关键字,不能进行修改,lock锁也是可重入锁,可以判断锁是否中断,可以自己设置公平锁或者不公平锁(参数为true即可变为公平锁,默认不传参且为不公平锁),使用起来比synchronized更加灵活方便
- synchronized适合少量的代码同步问题.lock锁适合大量的同步代码
- 传统的生产者消费者问题,防止虚假唤醒
线程之间的通信问题 生产者和消费者问题
生产者和消费者代码编写思路:
判断是否等待 进行业务处理 通知其他线程
判断是否需要等待,判断完之后就干活,如果需要等待就等待,干完活就通知其他线程
传统(synchronized)线程通信代码简单实现,线程交替执行 A B同时操作同一个变量
package com.wyh.pc; /** * @program: JUC * @description: 线程通信 * @author: 魏一鹤 * @createDate: 2022-02-12 21:12 **/ /** *线程之间通信问题:生产者和消费者 等待唤醒,通知唤醒 * 线程交替执行 A B同时操作同一个变量 **/ //生产者和消费者代码编写思路:判断是否等待 进行业务处理 通知其他线程 //判断是否需要等待,判断完之后就干活,如果需要等待就等待,干完活就通知其他线程 public class A { public static void main(String[] args){ //线程操作资源类 //创建资源类 Data data=new Data(); //多线程处理 把资源抛给线程去执行 这里采用lambda表达式简化代码 ,第二个参数是现成名称 new Thread(()->{ //从10循环+1 //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常 for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"线程A").start(); new Thread(()->{ //从10循环-1 //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常 for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"线程B").start(); } } //静态资源 独立耦合的 必须降低耦合性 class Data{ // 属性 数字变量 private int num=0; //方法 数字+1 自增1 //用synchronized修饰方法保证多线程操作同步 public synchronized void increment() throws InterruptedException { if(num!=0){ //值为0的时候等待,值为1的时候操作 //等待操作 wait需要抛出异常 this.wait(); } num++; System.out.println(Thread.currentThread().getName() + "-->"+num); //操作完之后通知其他线程,我+1完毕了 this.notifyAll(); } //方法 数字-1 自减1 //用synchronized修饰方法保证多线程操作同步 public synchronized void decrement() throws InterruptedException { if(num==0){ //值为1的时候等待,值为0的时候操作 //等待操作 wait需要抛出异常 this.wait(); } num--; System.out.println(Thread.currentThread().getName() + "-->"+num); //操作完之后通知其他线程,我-1完毕了 this.notifyAll(); } }
通过打印发现,实现了简单的线程通信交互执行
不过,以上代码是有问题的,现在A,B两个线程可以正常执行,那么如果有更多线程呢?
现在再加上两个线程执行,也就是两个线程加两个线减,执行代码还会和最开始两个线程执行的结果一样吗?
package com.wyh.pc; /** * @program: JUC * @description: 线程通信 * @author: 魏一鹤 * @createDate: 2022-02-12 21:12 **/ /** *线程之间通信问题:生产者和消费者 等待唤醒,通知唤醒 * 线程交替执行 A B同时操作同一个变量 **/ //生产者和消费者代码编写思路:判断是否等待 进行业务处理 通知其他线程 //判断是否需要等待,判断完之后就干活,如果需要等待就等待,干完活就通知其他线程 public class A { public static void main(String[] args){ //线程操作资源类 //创建资源类 Data data=new Data(); //多线程处理 把资源抛给线程去执行 这里采用lambda表达式简化代码 ,第二个参数是现成名称 new Thread(()->{ //从10循环+1 //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常 for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"线程A").start(); new Thread(()->{ //从10循环-1 //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常 for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"线程B").start(); //新加入两个线程 new Thread(()->{ //从10循环-1 //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常 for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"线程C").start(); new Thread(()->{ //从10循环-1 //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常 for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"线程D").start(); } } //静态资源 独立耦合的 必须降低耦合性 class Data{ // 属性 数字变量 private int num=0; //方法 数字+1 自增1 //用synchronized修饰方法保证多线程操作同步 public synchronized void increment() throws InterruptedException { if(num!=0){ //值为0的时候等待,值为1的时候操作 //等待操作 wait需要抛出异常 this.wait(); } num++; System.out.println(Thread.currentThread().getName() + "-->"+num); //操作完之后通知其他线程,我+1完毕了 this.notifyAll(); } //方法 数字-1 自减1 //用synchronized修饰方法保证多线程操作同步 public synchronized void decrement() throws InterruptedException { if(num==0){ //值为1的时候等待,值为0的时候操作 //等待操作 wait需要抛出异常 this.wait(); } num--; System.out.println(Thread.currentThread().getName() + "-->"+num); //操作完之后通知其他线程,我-1完毕了 this.notifyAll(); } }
这个问题也叫虚假唤醒因为在多线程中if只会判断一次,一般判断等待应该使用while循环判断,
如何解决虚假唤醒呢? 把if判断改为while循环判断即可 修改后代码如下
package com.wyh.pc; /** * @program: JUC * @description: 线程通信 * @author: 魏一鹤 * @createDate: 2022-02-12 21:12 **/ /** *线程之间通信问题:生产者和消费者 等待唤醒,通知唤醒 * 线程交替执行 A B同时操作同一个变量 **/ //生产者和消费者代码编写思路:判断是否等待 进行业务处理 通知其他线程 //判断是否需要等待,判断完之后就干活,如果需要等待就等待,干完活就通知其他线程 public class A { public static void main(String[] args){ //线程操作资源类 //创建资源类 Data data=new Data(); //多线程处理 把资源抛给线程去执行 这里采用lambda表达式简化代码 ,第二个参数是现成名称 new Thread(()->{ //从10循环+1 //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常 for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"线程A").start(); new Thread(()->{ //从10循环-1 //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常 for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"线程B").start(); new Thread(()->{ //从10循环-1 //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常 for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"线程C").start(); new Thread(()->{ //从10循环-1 //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常 for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"线程D").start(); } } //静态资源 独立耦合的 必须降低耦合性 class Data{ // 属性 数字变量 private int num=0; //方法 数字+1 自增1 //用synchronized修饰方法保证多线程操作同步 public synchronized void increment() throws InterruptedException { //为了防止虚假唤醒,应该用while进行判断等待 while (num!=0){ //值为0的时候等待,值为1的时候操作 //等待操作 wait需要抛出异常 this.wait(); } num++; System.out.println(Thread.currentThread().getName() + "-->"+num); //操作完之后通知其他线程,我+1完毕了 this.notifyAll(); } //方法 数字-1 自减1 //用synchronized修饰方法保证多线程操作同步 public synchronized void decrement() throws InterruptedException { //为了防止虚假唤醒,应该用while进行判断等待 while(num==0){ //值为1的时候等待,值为0的时候操作 //等待操作 wait需要抛出异常 this.wait(); } num--; System.out.println(Thread.currentThread().getName() + "-->"+num); //操作完之后通知其他线程,我-1完毕了 this.notifyAll(); } }
再次运行发现结果是正常的
线程A-->1
线程B-->0
线程A-->1
线程B-->0
线程A-->1
线程B-->0
线程A-->1
线程B-->0
线程A-->1
线程B-->0
线程A-->1
线程B-->0
线程C-->1
线程B-->0
线程A-->1
线程B-->0
线程C-->1
线程B-->0
线程A-->1
线程B-->0
线程C-->1
线程D-->0
线程C-->1
线程D-->0
线程A-->1
线程D-->0
线程C-->1
线程D-->0
线程A-->1
线程D-->0
线程C-->1
线程D-->0
线程C-->1
线程D-->0
线程C-->1
线程D-->0
线程C-->1
线程D-->0
线程C-->1
线程D-->0