一.線程間通信
1.生産者與消費者問題
//多線程經典,賣烤鴨程式--生産者與消費者
//定義一個資源類Resource
class Resource{
private String name;
private int count = 1;
//set()方法給生産者調用
public void set(String name){
this.name = name + count;
//調用set()方法count自增
count++;
System.out.println(Thread.currentThread().getName()+"...生産者..."+this.name);
}
//out()方法給消費者調用
public void out(){
System.out.println(Thread.currentThread().getName()+"...消費者........"+this.name);
}
}
class Producer implements Runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
public void run(){
//設定生産者不停地生産
while(true){
r.set("烤鴨");
}
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this.r = r;
}
public void run(){
//設定消費者不停地消費
while(true){
r.out();
}
}
}
class ProducerConsumerDemo{
public static void main(String[] args) {
Resource r = new Resource();
//建立一個生産者和一個消費者
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//分别将其加入到兩個線程中
Thread t0 = new Thread(pro);
Thread t1 = new Thread(con);
t0.start();
t1.start();
}
}
運作結果:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBHL0FWby9mZvwVZnFWbp1zczV2YvJHctM3cv1Ce-MzZE1UNFRVT5VFVNFTSU90djRVT3lkeMBjVtJWd0ckW65UbM5WOHJWa1knW0xmMMZ3bENGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
問題:消費者消費了生産者沒有生産的資源
分析:set( )方法内操作了共享資料count,而且有多條操作語句,出現了多線程安全問題,消費者的線程可能搶在生産者前列印資料(如圖烤鴨1638)
解決:使用同步函數解決多線程安全問題
隻需更改Resource類,将set( )函數和out( )函數加上synchronized關鍵字
class Resource{
private String name;
private int count = 1;
public synchronized voidset(String name){
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生産者..."+this.name);
}
public void synchronized out(){
System.out.println(Thread.currentThread().getName()+"...消費者........"+this.name);
}
}
運作結果:
問題:消費者隻能消費最近一次生産的資源
原因:兩個方法使用了同一個鎖,同一時間隻能有一個方法執行,而set( )方法執行時count不斷自增,out( )方法執行時count值則一直不變
顯然僅僅加一個鎖是不能達到我們的預期的,這時就要考慮其他解決辦法。即線程間通信
2.線程間通信的概念
多個線程在操作同一個資源,但是操作的動作卻不一樣
實作方式:
1:将資源封裝成對象
2:将線程執行的任務(任務其實就是run方法。)也封裝成對象
3.等待喚醒機制:
涉及到的方法:
wait( )
将同步中的線程處于當機狀态,釋放了執行權和執行資格,同時将線程對象存儲到線程池中
notify( )
喚醒線程池中某一個等待線程
notifyAll( )
喚醒線程池中的所有線程
使用前提:
1,這些方法都需要定義在同步中,不使用同步則這些方法沒有意義
2,這些方法必須要标示所屬的鎖。
A鎖上的線程被wait了,那這個線程就會被當機然後儲存到A鎖的線程池中,隻能A鎖的notify喚醒
這三個方法都定義在Object類中,因為這三個方法在使用過程中必須要辨別所屬的鎖對象,而鎖對象可以是任意對象,是以這些可以被任意對象調用的方法一定定義在Object類中
//使用等待喚醒機制解決生産者和消費者問題
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name){
//如果flag=true,則生産者進入等待狀态
while(flag)
try{this.wait();}catch(InterruptedException e){}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生産者..."+this.name);
flag = true;
//執行完後喚醒消費者
this.notify();
}
public synchronized void out(){
//如果flag=false,則消費者進入等待狀态
while(!flag)
try{this.wait();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消費者........"+this.name);
flag = false;
//執行完後喚醒生産者
this.notify();
}
}
class Producer implements Runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
public void run(){
while(true){
r.set("烤鴨");
}
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this.r = r;
}
public void run(){
while(true){
r.out();
}
}
}
class ProducerConsumerDemo{
public static void main(String[] args){
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(con);
t0.start();
t1.start();
}
}
執行結果
wait和sleep差別:
1.喚醒方式不同
wait:可以指定時間也可以不指定時間,不指定時間則隻能由對應的notify或者notifyAll來喚醒。
sleep:必須指定時間,時間到自動從當機狀态轉成運作狀态(臨時阻塞狀态)。
2.線程持有的鎖的釋放
wait:線程會釋放執行權,而且線程會釋放鎖。
Sleep:線程會釋放執行權,但是不釋放鎖。
4.多生産多消費
public class ProducerConsumerDemo {
public static void main(String[] args){
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//分别定義兩個生産者和兩個消費者
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
定義多個生産者和消費者
運作結果:
現象:程式停止不動,不在繼續向下執行,但是卻不是死鎖,因為程式中沒有同步的嵌套
分析:set( )和get( )方法中喚醒線程使用的是notify( )方法,隻能喚醒一個線程。是以可能出現一個生産者線程喚醒了另一個生産者線程,而flag值是true,被喚醒的這個生産者線程經過while判斷後也進入了wait等待狀态,然後程式中就沒有可以執行的線程了,程式停止。
解決:使用notifyAll( )方法,将所有線程都喚醒
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name){
while(flag)
try{this.wait();}catch(InterruptedException e){}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生産者..."+this.name);
flag = true;
//隻需将notify更改為notifyAll
this.notifyAll();
}
public synchronized void out(){
while(!flag)
try{this.wait();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消費者........"+this.name);
flag = false;
this.notifyAll();
}
}
二.線程的停止
1.使用stop方法(已過時)
2.結束run( )方法
run方法裡一般會定義循環,是以隻要結束循環即可。
第一種方式:定義循環的結束标記。
第二種方式:如果線程處于了當機狀态,是不可能讀到标記的,這時就需要通過Thread類中的interrupt方法,将其當機狀态強制清除。讓線程恢複具備執行資格的狀态,讓線程可以讀到标記,并結束。
三.Lock接口和Condition接口
解決線程安全問題使用同步的形式(同步代碼塊和同步函數),其實最終使用的都是鎖機制。線程進入同步就是具備了鎖,執行完畢離開同步,就是釋放了鎖。擷取鎖,釋放鎖的動作是鎖對象的内容。在面向對象思想的指導下,将鎖單獨定義為對象并封裝對鎖的操作。Java的設計者專門開發了一個Lock接口,将擷取鎖,釋放鎖等操作都定義到了這個接口中
同步是隐示的鎖操作,而Lock接口是顯示的鎖操作,Lock接口的出現就替代了同步。
在之前的版本中使用Object類中wait、notify、notifyAll的方式來完成的。那是因為同步中的鎖是任意對象,是以操作鎖的等待喚醒的方法都定義在Object類中。
而現在鎖是指定對象Lock。是以查找等待喚醒機制方式需要通過Lock接口來完成。而Lock接口中并沒有直接操作等待喚醒的方法,而是将這些方式又單獨封裝到了Condition接口中。
Condition接口将Object中的三個方法進行單獨的封裝。并提供了功能一緻的方法 await( )、signal( )、signalAll( )
使用Lock接口實作多生産多消費
import java.util.concurrent.locks.*;
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
//建立一個Lock接口的實作類ReentrantLock的對象
Lock lock = new ReentrantLock();
//通過lock鎖建立兩個監聽器,分别監聽生産者和消費者
Condition producer_con = lock.newCondition();
Condition consumer_con = lock.newCondition();
public void set(String name){
//擷取鎖(相當于加同步)
lock.lock();
try{
while(flag)
//監聽器調用await()方法使線程進入等待狀态
try{producer_con.await();}catch(InterruptedException e){}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生産者5.0..."+this.name);
flag = true;
//喚醒消費者的線程
consumer_con.signal();
}
finally{
//操作執行完畢,釋放鎖
lock.unlock();
}
}
public void out(){
lock.lock();
try{
while(!flag)
try{cousumer_con.await();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消費者.5.0......."+this.name);
flag = false;
producer_con.signal();
}
finally{
lock.unlock();
}
}
}
class Producer implements Runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
public void run(){
while(true){
r.set("烤鴨");
}
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this.r = r;
}
public void run(){
while(true){
r.out();
}
}
}
class ProducerConsumerDemo2{
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}