线程的同步与通信
一、线程的同步
上一篇手记《多线程的创建与使用》中有个练习题:
模拟火车站售票窗口,开启三个窗口售票,总票数为100
当时我没有考虑线程安全问题,今天我再把它拿出来,用线程的同步机制来实现线程的安全。
在之前那段程序中存在线程安全问题,打印车票时可能出现重复车票以及错票。
那么线程安全问题存在的原因?
由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在安全问题。
2、如何解决线程的安全问题?
必须让一个线程操作共享数据完毕以后,其他线程才有机会参与共享数据的操作
3、java如何实现线程的安全:线程的同步机制
synchronized是java中的一个关键字,也就是说是Java语言内置的特性。
(当然还有其他方式,我就只对synchronized展开来讲)
方式一:同步代码块
synchronized(需要一个任意的对象(锁)){
//需要被同步的代码块(即为操作共享数据的代码)
}
(1)共享数据:多个线程共同操作的同一个数据(变量)
(2)同步监视器:由一个类的对象来充当,哪个线程获取此监视器,谁就执行大括号里被同步的代码。俗称:锁。
要求:所有线程必须共用同一把锁。
注:在实现的方式中,考虑同步的话,可以使用this来充当锁,但在继承的方式中,慎用。
实例:模拟火车站售票窗口,开启三个窗口售票,总票数为100
class Window2 implements Runnable{ int ticket=100;//共享数据 //Object obj=new Object(); public void run() { while(true){ synchronized(this){//this表示当前对象,即为w,只有一个 if(ticket>0){ System.out.println(Thread.currentThread().getName()+"售出的票号为:"+ticket--); } } } } } public class TestWindow2 { public static void main(String[] args) { Window2 w=new Window2(); Thread t1=new Thread(w); Thread t2=new Thread(w); Thread t3=new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
方式二:同步方法
将操作共享数据的方法声明为synchronized,即此方法为同步方法,能够保证当其中一个线程执行
此方法时,其他线程在外等待直至此线程执行完此方法。
同步方法的锁:this
实例:模拟火车站售票窗口,开启三个窗口售票,总票数为100
class Window3 extends Thread{ static int ticket=100;//共享数据 Object obj=new Object(); public void run(){ while(true){ show(); } } public synchronized void show(){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"售出的票号为:"+ticket--); } } } public class TestWindow3 { public static void main(String[] args) { Window3 w1=new Window3(); Window3 w2=new Window3(); Window3 w3=new Window3(); w1.setName("窗口1"); w2.setName("窗口2"); w3.setName("窗口3"); w1.start(); w2.start(); w3.start(); } }
4、由于同一个时间只能有一个线程访问共享数据,效率变低了
5、释放锁的操作:
(1)当前线程的同步方法、同步代码块执行结束;
(2)当前线程的同步方法、同步代码块中遇到break、return终止了该方法、该代码块的继续执行;
(3)当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束;
(4)当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停并释放锁。
6、不会释放锁的操作:线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂时停止当前线程的执行。
二、线程的通信
1、wait()与notify()和notifyAll()
(1)wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问;
(2)notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待;
(3)notifyAll():唤醒正在排队等待的所有线程结束等待。
这三个方法只有在synchronized方法或synchronized代码块中才能使用。
实例:
package com.TestThread; //线程通信 //使用两个线程打印1-100,线程1,线程2交替打印 class PrintNum implements Runnable{ int num=1; public void run() { while(true){ synchronized (this) { notify(); if (num <= 100) { System.out.println(Thread.currentThread().getName() + ":" + num); num++; } else { break; } try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } public class TestCommunication { public static void main(String[] args) { PrintNum p=new PrintNum(); Thread t1=new Thread(p); Thread t2=new Thread(p); t1.setName("线程1"); t2.setName("线程2"); t1.start(); t2.start(); } }
三、练习
生产者消费者问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,
如果店中有空位放产品了再通知生产者继续生产,如果店中没有产品,店员会告诉消费者等一下,
如果店中有产品了再通知消费者来取走产品。
分析:
- 1、是否涉及到多线程问题?是,生产者,消费者
- 2、是否涉及到共享数据?有,产品数量
- 3、是否涉及到线程的通信?有,存在生产者与消费者的通信
示例:
public class TestProduceConsume{ public static void main(String[] args) { Clerk clerk=new Clerk(); Producer p1=new Producer(clerk); Consumer c1=new Consumer(clerk); Thread t1=new Thread(p1);//生产者1 Thread t3=new Thread(p1);//生产者2 Thread t2=new Thread(c1);//消费者 t1.setName("生产者1"); t3.setName("生产者2"); t2.setName("消费者"); t1.start(); t2.start(); t3.start(); } } class Clerk{ int product; public synchronized void addProduct(){//生产产品 if(product>=20){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ product++; System.out.println(Thread.currentThread().getName()+":生产了第"+product+"个产品"); notifyAll(); } } public synchronized void consumeProduct(){//消费产品 if(product<=0){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ System.out.println(Thread.currentThread().getName()+"消费了第"+product+"个产品"); product--; notifyAll(); } } } class Producer implements Runnable{//生产者 Clerk clerk; public Producer(Clerk clerk){ this.clerk=clerk; } public void run(){ System.out.println("生产者开始生产产品"); while(true){ try { Thread.currentThread().sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } clerk.addProduct(); } } } class Consumer implements Runnable{ Clerk clerk; public Consumer(Clerk clerk){ this.clerk=clerk; } public void run(){ System.out.println("消费者消费产品"); while(true){ try { Thread.currentThread().sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } clerk.consumeProduct(); } } }
作者: Aquamarine
链接:https://www.imooc.com/article/22456
来源:慕课网