第一节 线程通信
1.1 线程通信引入
l 应用场景:生产者和消费者问题
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
- 分析
- 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件
- 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费
- 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费
- 在生产者消费者问题中,仅有synchronized是不够的
- synchronized可阻止并发更新同一个共享资源,实现了同步
- synchronized不能用来实现不同线程之间的消息传递(通信)
方法名
|
作 用
|
final void wait()
|
表示线程一直等待,直到其它线程通知
|
void wait(long timeout)
|
线程等待指定毫秒参数的时间
|
final void wait(long timeout,int nanos)
|
线程等待指定毫秒、微妙的时间
|
final void notify()
|
唤醒一个处于等待状态的线程
|
final void notifyAll()
|
唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先运行
|
注意事项
q 均是java.lang.Object类的方法
q 都只能在同步方法或者同步代码块中使用,否则会抛出异常
|
【示例1】 准备生产者消费者问题
/** * 商品类 */ public class Product { private String name;//馒头、玉米饼 private String color;//白色 黄色 public Product() { } public Product(String name, String color) { this.name = name; this.color = color; }
//…..省略getter和setter方法 public String toString() { return "Product{" + "name='" + name + '\'' + ", color='" + color + '\'' + '}'; } }
/** * 生产者线程 */ public class ProduceRunnable implements Runnable { //private Product product = new Product(); private Product product; public void setProduct(Product product) { this.product = product; } public void run() { int i = 0; while(true){ if(i%2==0){ product.setName("馒头"); product.setColor("白色"); }else{ product.setName("玉米饼"); product.setColor("黄色"); } System.out.println("生产者生产商品"+product.getName() +" "+product.getColor()); i++; } } }
/** * 消费者线程 */ public class ConsumeRunnable implements Runnable { //private Product product = new Product(); private Product product; public ConsumeRunnable() { } public ConsumeRunnable(Product product) { this.product = product; } public void setProduct(Product product) { this.product = product; } public void run() { while(true){ System.out.println("消费者消费商品"+product.getName() +" "+product.getColor()); } } }
public class Test { public static void main(String[] args) { Product product = new Product();
ProduceRunnable runnable1 = new ProduceRunnable(); runnable1.setProduct(product); Thread thread1 = new Thread(runnable1);
Runnable runnable2 = new ConsumeRunnable(product); Thread thread2 = new Thread(runnable2);
thread1.start(); thread2.start(); } }
|
注意:必须保证生产者消费者操作的是一个商品对象,否则就会出现消费者消费null的情况。
Product product = new Product();
ProduceRunnable runnable1 = new ProduceRunnable(); runnable1.setProduct(product); Runnable runnable2 = new ConsumeRunnable(product);
|
1.2使用同步代码块实现线程同步
【示例2】 使用同步代码块保证线程安全
public class ProduceRunnable implements Runnable { private Product product; public void setProduct(Product product) { this.product = product; } public void run() { int i = 0; while(true){ synchronized (product){ if(i%2==0){ product.setName("馒头"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } product.setColor("白色"); }else{ product.setName("玉米饼"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } product.setColor("黄色"); } System.out.println("生产者生产商品"+product.getName()
+" "+product.getColor()); } i++; } } }
public class ConsumeRunnable implements Runnable { private Product product; public void run() { while(true){ synchronized (product){ System.out.println("消费者消费商品"
+product.getName()+" "+product.getColor()); } } } }
|
- 注意:不仅生产者要加锁,而且消费者也要加锁,并且必须是一把锁(不仅是一个引用变量,而且必须是指向同一个对象)
1.3实现线程通信-同步代码块形式
【示例3】 实现线程通信—同步代码块方式
public class ProduceRunnable implements Runnable { public void run() { int i = 0; while(true){ synchronized (product){ //如果已经有商品,就等待 if(product.flag){ try { product.wait(); //必须调用同步监视器的通信方法 } catch (InterruptedException e) { e.printStackTrace(); } } //生产商品 if(i%2==0){ product.setName("馒头"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } product.setColor("白色"); }else{ product.setName("玉米饼"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } product.setColor("黄色"); } //输出结果 System.out.println("生产者生产商品"+product.getName() +" "+product.getColor()); //修改一下商品的状态 product.flag = true; //通知消费者来消费 product.notify(); }
i++; } } }
public class ConsumeRunnable implements Runnable { public void run() { while(true){ synchronized (product){ //如果没有商品,就等待 if(!product.flag){ try { product.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //消费商品 System.out.println("消费者消费商品" +product.getName()+" "+product.getColor()); //修改商品的状态 product.flag = false; //通知生产者进行生产 product.notifyAll(); } } } }
|
线程同步的细节
细节1:进行线程通信的多个线程,要使用同一个同步监视器(product),还必须要调用该同步监视器的wait()、notify()、notifyAll();
细节2:线程通信的三个方法
wait() 等待
在【其他线程】调用【此对象】的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。当前线程必须拥有此对象监视器。
wait(time) 等待
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。 当前线程必须拥有此对象监视器。
notify() 通知 唤醒
唤醒在【此对象监视器】上等待的【单个】线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。【选择是任意性的】,并在对实现做出决定时发生
notifyAll() 通知所有 唤醒所有
唤醒在【此对象监视器】上等待的【所有】线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程【进行竞争】;
细节3:完整的线程生命周期
阻塞状态有三种
普通的阻塞 sleep,join,Scanner input.next()
同步阻塞(锁池队列) 没有获取同步监视器的线程的队列
等待阻塞(阻塞队列) 被调用了wait()后释放锁,然后进行该队列
细节4:sleep()和wait()的区别
区别1:sleep() 线程会让出CPU进入阻塞状态,但不会释放对象锁
wait() 线程会让出CPU进入阻塞状态, 【也会放弃对象锁】,进入等待【此对象】的等待锁定池
区别2: 进入的阻塞状态也是不同的队列
区别3:wait只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用