線程間的通信——生產者消費者問題
【題目一】
- 用兩個線程操作初始值爲0的資源類,一個線程進行加操作,另一個線程進行減操作:
【思路】
- 線程、操作、資源類
- 判斷、幹活、通知(synchronized)
【代碼】
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareDataOne//資源類
{
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
{
// 1判斷
if (number == 0) {
this.wait();
}
// 2幹活
--number;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3通知
this.notifyAll();
}
}
public class NotifyWaitDemoOne
{
public static void main(String[] args)
{
ShareDataOne sd = new ShareDataOne();
new Thread(() -> {//線程一
for (int i = 1; i < 10; i++) {
try {
sd.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {//線程二
for (int i = 1; i < 10; i++) {
try {
sd.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
}
}
【題目二】
- 在【題目一】的基礎上改爲多個線程進行加操作,多個線程進行減操作:
【思路】
- 繼續使用if語句判斷會造成spurious weakup(虛假喚醒),使用while循環可以將等待的線程重新進行判斷
【代碼】
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.omg.IOP.Codec;
class ShareData//資源類
{
private int number = 0;//初始值爲零的一個變量
public synchronized void increment() throws InterruptedException
{
//判斷
while(number!=0) {
this.wait();
}
//幹活
++number;
System.out.println(Thread.currentThread().getName()+" \t "+number);
//通知
this.notifyAll();;
}
public synchronized void decrement() throws InterruptedException
{
//判斷
while(number!=1) {
this.wait();
}
//幹活
--number;
System.out.println(Thread.currentThread().getName()+" \t "+number);
//通知
this.notifyAll();
}
}
public class NotifyWaitDemo
{
public static void main(String[] args)
{
ShareData sd = new ShareData();
//四個線程
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
sd.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
sd.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
sd.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
sd.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
【題目三】
- 使用synchronized時用wait()和notify()/notifyAll()(Object類中)操作線程,現在改成使用Condition的await()和signal()/signalAll()(Condition類中)操作線程:
【思路】
- 將synchronized去掉,並將wait()和notify()/notifyAll()改寫成lock的await()和signal()/signalAll()
【代碼】
import org.omg.IOP.Codec;
class ShareData//資源類
{
private int number = 0;//初始值爲零的一個變量
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws InterruptedException
{
lock.lock();
try {
//判斷
while(number!=0) {
condition.await();
}
//幹活
++number;
System.out.println(Thread.currentThread().getName()+" \t "+number);
//通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException
{
lock.lock();
try {
//判斷
while(number!=1) {
condition.await();
}
//幹活
--number;
System.out.println(Thread.currentThread().getName()+" \t "+number);
//通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/*public synchronized void increment() throws InterruptedException
{
//判斷
while(number!=0) {
this.wait();
}
//幹活
++number;
System.out.println(Thread.currentThread().getName()+" \t "+number);
//通知
this.notifyAll();;
}
public synchronized void decrement() throws InterruptedException
{
//判斷
while(number!=1) {
this.wait();
}
//幹活
--number;
System.out.println(Thread.currentThread().getName()+" \t "+number);
//通知
this.notifyAll();
}*/
}
public class NotifyWaitDemo
{
public static void main(String[] args)
{
ShareData sd = new ShareData();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
sd.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
sd.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
sd.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
sd.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
【題目四】
- 多線程之間按順序調用,實現A->B->C,三個線程啓動,要求如下:AA打印5次,BB打印10次,CC打印15次,接着AA打印5次,BB打印10次,CC打印15次…輪流10次。
【思路】
- 原來的synchronized無法實現多線程之間按順序調用,通過使用Condition的標誌位(配多把鎖,即多個condition對象)可以完成特定鎖的喚醒
【代碼】
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareResource
{
private int number = 1;//1:A 2:B 3:C
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5(int totalLoopNumber)
{
lock.lock();
try
{
//1 判斷
while(number != 1)
{
//A 就要停止
c1.await();
}
//2 幹活
for (int i = 1; i <=5; i++)
{
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber);
}
//3 通知
number = 2;
c2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10(int totalLoopNumber)
{
lock.lock();
try
{
//1 判斷
while(number != 2)
{
//A 就要停止
c2.await();
}
//2 幹活
for (int i = 1; i <=10; i++)
{
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber);
}
//3 通知
number = 3;
c3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15(int totalLoopNumber)
{
lock.lock();
try
{
//1 判斷
while(number != 3)
{
//A 就要停止
c3.await();
}
//2 幹活
for (int i = 1; i <=15; i++)
{
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber);
}
//3 通知
number = 1;
c1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ThreadOrderAccess
{
public static void main(String[] args)
{
ShareResource sr = new ShareResource();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
sr.print5(i);
}
}, "AA").start();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
sr.print10(i);
}
}, "BB").start();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
sr.print15(i);
}
}, "CC").start();
}
}
線程8鎖總結
-
一個對象裏面如果有多個synchronized方法,某一個時刻內,只要一個線程去調用其中的一個synchronized方法了,其它的線程都只能等待,換句話說,某一個時刻內,只能有唯一一個線程去訪問這些synchronized方法。鎖的是當前對象this,被鎖定後,其它的線程都不能進入到當前對象的其它的synchronized方法
-
加個普通方法後發現和同步鎖無關,換成兩個對象後,不是同一把鎖了。
-
synchronized實現同步的基礎:Java中的每一個對象都可以作爲鎖。具體表現爲以下3種形式:
- 對於普通同步方法,鎖是當前實例對象。
- 對於靜態同步方法,鎖是當前類的Class對象。
- 對於同步方法塊,鎖是Synchonized括號裏配置的對象
-
當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。也就是說如果一個實例對象的非靜態同步方法獲取鎖後,該實例對象的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖後才能獲取鎖,可是別的實例對象的非靜態同步方法因爲跟該實例對象的非靜態同步方法用的是不同的鎖,所以不用等待該實例對象已獲取鎖的非靜態同步方法釋放鎖就可以獲取他們自己的鎖。
-
所有的靜態同步方法用的也是同一把鎖——類對象本身,這兩把鎖是兩個不同的對象,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的。但是一旦一個靜態同步方法獲取鎖後,其他的靜態同步方法都必須等待該方法釋放鎖後才能獲取鎖,而不管是同一個實例對象的靜態同步方法之間,還是不同的實例對象的靜態同步方法之間,只要它們同一個類的實例對象!
【代碼示例】
import java.util.concurrent.TimeUnit;
class Phone
{
public synchronized void sendSMS() throws Exception
{
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception
{
System.out.println("------sendEmail");
}
public void getHello()
{
System.out.println("------getHello");
}
}
/**
*
* @Description: 8鎖
*
1 標準訪問,先打印短信還是郵件
2 停4秒在短信方法內,先打印短信還是郵件
3 新增普通的hello方法,是先打短信還是hello
4 現在有兩部手機,先打印短信還是郵件
5 兩個靜態同步方法,1部手機,先打印短信還是郵件
6 兩個靜態同步方法,2部手機,先打印短信還是郵件
7 1個靜態同步方法,1個普通同步方法,1部手機,先打印短信還是郵件
8 1個靜態同步方法,1個普通同步方法,2部手機,先打印短信還是郵件
* ---------------------------------
*
*/
public class Lock_8
{
public static void main(String[] args) throws Exception
{
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
phone.sendEmail();
//phone.getHello();
//phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}