Java多线程学习总结(四)
1.线程间的通信
wait()和notifyAll()的应用实例:
package com.Zhongger.Day05; /** * @Author Zhongger * @Description 有两个线程,可以操作初始值为0的一个变量,实现一个线程对该变量加1,一个对该变量减1,实现交替,来10轮 * @Date 2020.2.24 生产者消费者模式 */ public class TheadWaitNotifyTest { public static void main(String[] args) { AirConditioner airConditioner = new AirConditioner(); new Thread(()->{ for (int i = 1; i <= 10; i++) { try { airConditioner.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); new Thread(()->{ for (int i = 1; i <= 10; i++) { try { airConditioner.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); } } class AirConditioner { private int number=0; public synchronized void increment() throws InterruptedException { //1.判断 if (number!=0){ this.wait(); } //2.干活 number++; System.out.println(Thread.currentThread().getName()+"\t"+number); //3.通知 this.notifyAll(); } public synchronized void decrement() throws InterruptedException { if (number==0){ this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"\t"+number); this.notifyAll(); } }
运行结果为:
在这其中,用到了生产者消费者模式
生产者消费者模式说明:
生产者只在仓库未满时进行生产,仓库满时生产者进程被阻塞;
消费者只在仓库非空时进行消费,仓库为空时消费者进程被阻塞;
当生产者发现仓库满时会通知消费者消费;
A相当于一个生产者,B相当于一个消费者。
现在,如果多加入两个线程,看看程序的运行结果会怎么样?
可以发现,A,C作为生产者,生产的物品多于了1。如果在真正的生产环境中出现这样的问题,会造成难以挽回的后果。所以一定要避免这种情况。
即在多线程交互中,必须要防止多线程的虚假唤醒。解决方案是:判断的地方使用whlie循环
whlie(number!=0){ this.wait(); }
另外,使用ReentrantLock的方式也是可以的,在前两天的博客中有写到。
2.多线程间按顺序调用
package com.Zhongger.Day05; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @Author Zhongger * @Description 多线程间按顺序调用,实现A->B->C,A打印5次,B打印10次,C打印15次 * @Date 2020.2.24 */ public class ThreadOrderAccess { public static void main(String[] args) { ShareResource shareResource = new ShareResource(); new Thread(()->{ for (int i = 0; i < 3; i++) { shareResource.print5(); } },"A").start(); new Thread(()->{ for (int i = 0; i < 3; i++) { shareResource.print10(); } },"B").start(); new Thread(()->{ for (int i = 0; i < 3; i++) { shareResource.print15(); } },"C").start(); } } //资源类 class ShareResource{ private int number=1;//1:A 2:B 3:C private Lock lock = new ReentrantLock(); private Condition condition1=lock.newCondition(); private Condition condition2=lock.newCondition(); private Condition condition3=lock.newCondition(); public void print5(){ lock.lock(); try { //判断 while (number!=1){ condition1.await(); } //干活 for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName()+"\t"+i); } //通知 number=2; condition2.signal();//唤醒了B线程 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print10(){ lock.lock(); try { //判断 while (number!=2){ condition2.await(); } //干活 for (int i = 1; i <= 10; i++) { System.out.println(Thread.currentThread().getName()+"\t"+i); } //通知 number=3; condition3.signal();//唤醒了线程C } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print15(){ lock.lock(); try { //判断 while (number!=3){ condition3.await(); } //干活 for (int i = 1; i <= 15; i++) { System.out.println(Thread.currentThread().getName()+"\t"+i); } //通知 number=1; condition1.signal();//唤醒了线程A } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
使用多个Condition和Signal来精准地通知下一个线程工作。
3.多线程八锁
1.如果一个对象里面有多个synchronized方法,某一时刻内,只要有一个线程去调用其中的一个synchronized方法了,其他线程都只能等待,换句话说,某一时刻内,只能有一个线程去访问这些synchronized方法,synchronized锁的是一个当前对象this,被锁定后,其他的线程都不能进入到当前对象的其他synchronized方法。
2.类中如果有普通方法,和同步锁无关,其他线程可以访问。
3.若有两个资源类对象,锁是不同的,所以不同的线程可以访问不同的资源类对象中的synchronized方法,互不干扰。
4.所有的非静态同步方法都是同一把锁——类的实例。
5.synchronized实现同步的基础:Java中的每一个对象都可以作为锁,具体表现为以下三种形式:
对于普通的同步方法,锁是当前实例对象
对于静态同步方法,锁是当前类的Class对象
对于同步方法块,锁是synchronized括号里配置的对象
6.当一个线程试图访问同步代码块时,它必须得到锁,退出或抛出异常时必须释放锁。
7.所有的静态同步方法都是同一把锁——类的Class对象
8.静态同步方法和普通同步方法锁的是不同的对象,两个方法之间是不会有竞争条件的,但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们是同一个类的实例对象。
好了,今天的多线程学习就到这里,明天继续咯~