前言
最近也看完了<<併發編程的藝術>>,也零零散散的看了不少多線程有關的東西。早上寫代碼的時候看到一篇博客講了使用notify和wait相關的一些東西。本人自己對多線程一直有點苦手,書本看了一大堆。但是實際使用依然頭大。這次突然對於多線程編碼有點感悟,記錄於此。
概念相關
synchronized{}鎖住的代碼塊,結束代碼執行之後。會釋放對應的鎖
wait和notify,可以在synchronized{}之中使用。即使拿到了鎖,也會在代碼塊中釋放出去。
題目1
現在有3個生產者,3個消費者,生產者每次+1。消費者每次-1。庫存從0開始,不能小於0。也不能大於5。要線程安全的進行生產和消費。
解答:多線程的編程處理的核心問題就在於變量。
一般有如下2個原則:
1、共享變量處理,使用static共享,或者傳入同一個類控制
2、每個線程控制自己的私有變量,ThreadLocal中的實現
代碼實現
這是一個庫存類
import java.util.ArrayList;
import java.util.List;
public class Storage {
private List<Object> foods;
public final static int MAX_SIZE = 5;
private boolean flag = false;
public Storage(){
foods = new ArrayList<Object>();
}
public List<Object> getFoods() {
return foods;
}
public void setFoods(List<Object> foods) {
this.foods = foods;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
線程類
package main.java;
public class Company3 {
public static void main(String[] args) {
Company3 c = new Company3();
Storage storage = new Storage();
new Thread(c.new Customer(storage)).start();
new Thread(c.new Producer(storage)).start();
new Thread(c.new Customer(storage)).start();
new Thread(c.new Producer(storage)).start();
new Thread(c.new Customer(storage)).start();
new Thread(c.new Producer(storage)).start();
}
/**
* 消費者
*/
private class Customer implements Runnable {
private Storage storage;
public Customer(Storage storage) {
super();
this.storage = storage;
}
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (storage) {
System.out.println("消費者" + Thread.currentThread().getName() + "獲得鎖");
while (storage.getFoods().size() <= 0) {
System.out.println("貨物已空,提示生產者生產");
try {
System.out.println("消費者" + Thread.currentThread().getName() + "開始等待");
storage.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.getFoods().remove(0);
System.out.println("消費者消費1, " + Thread.currentThread().getName() + ", 餘量:" + storage.getFoods().size());
storage.notifyAll();
}
}
}
}
/**
* 生產者
*/
private class Producer implements Runnable {
private Storage storage;
public Producer(Storage storage) {
super();
this.storage = storage;
}
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (storage) {
System.out.println("生產者" + Thread.currentThread().getName() + "獲得鎖");
while (storage.getFoods().size() >= Storage.MAX_SIZE) {
System.out.println("貨物已滿,提示消費者消費");
try {
System.out.println("生產者" + Thread.currentThread().getName() + "開始等待");
storage.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.getFoods().add(1);
System.out.println("生產者生產1, " + Thread.currentThread().getName() + ",餘量:" + storage.getFoods().size());
storage.notifyAll();
}
}
}
}
}
效果如下
消費者消費1, Thread-0, 餘量:2
消費者Thread-2獲得鎖
消費者消費1, Thread-2, 餘量:1
消費者Thread-4獲得鎖
消費者消費1, Thread-4, 餘量:0
生產者Thread-3獲得鎖
生產者生產1, Thread-3,餘量:1
生產者Thread-1獲得鎖
生產者生產1, Thread-1,餘量:2
生產者Thread-5獲得鎖
生產者生產1, Thread-5,餘量:3
消費者Thread-0獲得鎖
消費者消費1, Thread-0, 餘量:2
消費者Thread-2獲得鎖
消費者消費1, Thread-2, 餘量:1
消費者Thread-4獲得鎖
消費者消費1, Thread-4, 餘量:0
消費者Thread-0獲得鎖
貨物已空,提示生產者生產
講解。
一個生產者類,一個消費者類。通過共享storage獲得一個顯式的變量進行控制,每次只能允許一個線程獲得storage的鎖。對其中的參數進行操作。當庫存爲0時,消費者wait停下當前線程,並且釋放鎖。循環等待庫存增多,其他線程進行爭搶鎖進行消費,同時喚醒其他線程,同理對於庫存5時,生產者也是一樣
tips:
1、我的線程在啓動後進行循環的頭位置,加了一個sleep。是因爲不加sleep的話,會導致偏向鎖很嚴重,一直會是該線程持有鎖,所以用sleep讓其他線程也可以爭搶到鎖。展示效果更佳明顯
2、如果添加更多的消費者或者生產者,那麼如果是更多消費者,會看到當消費到庫存爲0的時候,會有更多的消費者線程爭搶到線程,但是獲得不到鎖,就會wait等待中
題目2
我希望修改消費者和生產者的模式,當庫存爲0時,所有消費者停止消費,等待生產者生產,當生產者生產到5時,所有生產者也會停止,等待消費者消費
代碼2
public class Company {
public static void main(String[] args) {
Company c = new Company();
Storage storage = new Storage();
new Thread(c.new Customer(storage)).start();
new Thread(c.new Producer(storage)).start();
new Thread(c.new Customer(storage)).start();
new Thread(c.new Producer(storage)).start();
}
/**
* 消費者
*
* @author xtuali
*/
private class Customer implements Runnable {
private Storage storage;
public Customer(Storage storage) {
super();
this.storage = storage;
}
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (storage) {
System.out.println("消費者" + Thread.currentThread().getName() + "獲得鎖" );
if (storage.isFlag()) {
while (storage.getFoods().size() <= 0) {
System.out.println("貨物已空,提示生產者生產");
storage.setFlag(false);
try {
storage.notifyAll();
System.out.println("消費者" + Thread.currentThread().getName() + "開始等待" );
storage.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.getFoods().remove(0);
System.out.println("消費者消費1, " + Thread.currentThread().getName() + ", 餘量:" + storage.getFoods().size());
}else{
try {
System.out.println("消費者" + Thread.currentThread().getName() + "開始等待" );
storage.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
/**
* 生產者
*
* @author xtuali
*/
private class Producer implements Runnable {
private Storage storage;
public Producer(Storage storage) {
super();
this.storage = storage;
}
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (storage) {
System.out.println("生產者" + Thread.currentThread().getName() + "獲得鎖" );
if(!storage.isFlag()){
while (storage.getFoods().size() >= Storage.MAX_SIZE) {
System.out.println("貨物已滿,提示消費者消費");
storage.setFlag(true);
try {
storage.notifyAll();
System.out.println("生產者" + Thread.currentThread().getName() + "開始等待" );
storage.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.getFoods().add(1);
System.out.println("生產者生產1, " + Thread.currentThread().getName() + ",餘量:" + storage.getFoods().size());
}else{
try {
System.out.println("生產者" + Thread.currentThread().getName() + "開始等待" );
storage.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
結果
消費者Thread-0獲得鎖
消費者Thread-0開始等待
生產者Thread-1獲得鎖
生產者生產1, Thread-1,餘量:1
消費者Thread-2獲得鎖
消費者Thread-2開始等待
生產者Thread-3獲得鎖
生產者生產1, Thread-3,餘量:2
生產者Thread-1獲得鎖
生產者生產1, Thread-1,餘量:3
生產者Thread-3獲得鎖
生產者生產1, Thread-3,餘量:4
生產者Thread-1獲得鎖
生產者生產1, Thread-1,餘量:5
生產者Thread-3獲得鎖
貨物已滿,提示消費者消費
生產者Thread-3開始等待
消費者Thread-0獲得鎖
消費者消費1, Thread-0, 餘量:4
消費者Thread-2獲得鎖
消費者消費1, Thread-2, 餘量:3
解答:
我在Storage類中已經添加了flag標記,flag初始標記設置爲false,因爲開始的時候沒有庫存,一切都要等待生產者生產。flag也用於控制消費者在生產者生產的時候放棄消費並等待,也用於控制生產者在消費者消費的時候放棄生產,等待消費。