理解多執行緒(篇二)
執行緒間通訊等待喚醒機制:
- wait---放棄執行資格,讓執行緒凍結,會釋放鎖。
- sleep---也會放棄執行資格,但是不會釋放鎖
- notify---喚醒第一個凍結執行緒
- notifyAll---喚醒所有凍結執行緒
1、 執行緒間通訊演示---執行緒間通訊安全問題----執行緒間通訊等待喚醒機制
例1:男人掙錢女人花錢
package net.csdn.qf.test; /** * @author 北冥有熊 * 2018年11月7日 */ public class Test01 { public static void main(String[] args) { Person p = new Person(); In in = new In(p); Out out = new Out(p); new Thread(in,"郭靖").start(); new Thread(in,"楊康").start(); new Thread(out,"黃蓉").start(); new Thread(out,"穆念慈").start(); } } //IN class In implements Runnable{ Person person = null; public In(Person person) { this.person = person; } public void run() { while(person.count>0) { person.inMoney(); person.count--; } } } //Out class Out implements Runnable{ Person person = null; public Out(Person person) { this.person = person; } @Override public void run() { while(person.count>0) { person.outMoney(); person.count--;//因為共享count,所以這句可以不要 } } } //Person class Person{ private String name; private boolean haveMoney; int count = 20; public Person() { super(); } //男人掙錢 public synchronized void inMoney() { //同步方法 while(haveMoney) { //判斷是否有錢 try { this.wait();//等待 } catch (Exception e) { // TODO: handle exception } try { Thread.sleep(500); } catch (Exception e) { // TODO: handle exception } } name = "男人"; System.out.println(Thread.currentThread().getName()+"--"+name+"---掙錢"); haveMoney = !haveMoney; //改成沒錢 notifyAll(); //喚醒全部 } //女人花錢 public synchronized void outMoney() { //同步方法 while(!haveMoney){ //判斷是否有錢 try { this.wait(); //等待 } catch (Exception e) { // TODO: handle exception } try { Thread.sleep(500); } catch (Exception e) { // TODO: handle exception } } name = "女人"; System.out.println(Thread.currentThread().getName()+"--"+name+"----花錢"); haveMoney = !haveMoney; //改成有錢 notifyAll(); //喚醒全部 } public String toString() { return name+"-----"; } }
while+NotifyAll-------其中while解決的是喚醒自己方執行緒出現重複錄入或者輸出的情況,notifyAll 解決的是防止出現全部wait。
所有的控制前提:
- 要有兩個以上的執行緒
- 要操作同一個資源
- 而且還要是同一把鎖
Lock、Condition:
在Java中Lock和Condition 也是用來解決多執行緒安全問題。Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用。
- Lock lock = new ReentrantLock(); //建立一個鎖物件lock
- Condition manCondition = lock.newCondition(); //建立男人執行緒物件manCondition
- Condition womanCondition = lock.newCondition(); //建立女人執行緒物件womanCondition
- lock.lock(); //上鎖
- lock.unLock(); //解鎖
- await()---執行緒等待
- signal()---執行緒喚醒
- signalAll()---喚醒所有執行緒
下面以Lock Condition解決男人掙錢女人花錢的多執行緒安全問題:
package net.scdn.qf.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 北冥有熊
* 2018年11月8日
*/
public class Test {
public static void main(String[] args) {
Person p = new Person();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (p.count<=20) {
p.inMoney();
}
}
},"郭靖").start();
new Thread(new Runnable() { //匿名內部類
@Override
public void run() {
// TODO Auto-generated method stub
while (p.count<=20) {
p.outMoney();
}
}
},"黃蓉").start();
}
}
class Person{
private String name;
private boolean haveMoney;
int count = 1;
Lock lock = new ReentrantLock(); //建立一個鎖物件
Condition manCondition = lock.newCondition(); //建立男人執行緒物件
Condition womanCondition = lock.newCondition(); //建立女人執行緒物件
//男人掙錢
public void inMoney() {
lock.lock();//上鎖
try {
while (haveMoney) {//當有錢的時候等待
try {
manCondition.await(); //男方執行緒等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
name = "男人";
if (count<=20) {
System.out.println(Thread.currentThread().getName()+name+"--掙錢");
}
haveMoney = !haveMoney; //轉變為沒錢
womanCondition.signal(); //喚醒女方執行緒
} finally {
lock.unlock();//釋放鎖
}
}
//女人花錢
public void outMoney() {
lock.lock();//上鎖
try {
while (!haveMoney) {
try {
womanCondition.await(); //女方執行緒等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}name = "女人";
if (count<=20) {
System.out.println(Thread.currentThread().getName()+name+"----花錢");
}
count++;
haveMoney = !haveMoney;
manCondition.signal(); //喚醒男方執行緒
}finally {
lock.unlock();//釋放鎖
}
}
}
以上這總供求供求關係也被稱為"生產者消費者模式"。
執行緒死鎖
執行緒間搶佔資源導致,死鎖不能解決,只能防止
死鎖的前提:
1:資源匱乏
2:多個執行緒都來操作這個資源,有多個鎖
3:鎖之間有使用對方資源的情況,必須有兩把鎖以上,而且要鎖中有鎖 你中有我 我中有你。
關於死鎖,在面試的時候面試官可能讓你寫一個死鎖。
例:
package net.scdn.qf.test;
/**
* @author 北冥有熊
* 2018年11月8日
*/
public class Test01 {
public static void main(String[] args) {
DeadLock dLock = new DeadLock();
new Thread(dLock,"AA").start();
new Thread(dLock,"BB").start();
new Thread(dLock,"CC").start();
new Thread(dLock,"DD").start();
}
}
class DeadLock implements Runnable{
private static final Object OBJECT1 = new Object();
private static final Object OBJECT2 = new Object();//建立兩個鎖物件
boolean isup;
@Override
public void run() {
// TODO Auto-generated method stub
deadLock();
}
public void deadLock() {
while (true) {
if (isup) {
//鎖一套鎖二
synchronized(OBJECT1) {
System.out.println(Thread.currentThread().getName()+"--if---OBJECT1---");
synchronized(OBJECT2) {
System.out.println(Thread.currentThread().getName()+"--if---OBJECT2---");
}
}
}else {
//鎖二套鎖一
synchronized(OBJECT2) {
System.out.println(Thread.currentThread().getName()+"--else---OBJECT2---");
synchronized(OBJECT1) {
System.out.println(Thread.currentThread().getName()+"--else---OBJECT1---");
}
}
}
isup = !isup;
}
}
}
結果:死鎖
AA--else---OBJECT2---
AA--else---OBJECT1---
AA--if---OBJECT1---
DD--else---OBJECT2---
分析:
剛一開始,ABCD四個執行緒進行if判斷,都進入else語句,首先執行緒A搶到OBJECT1鎖物件,這是BCD執行緒再鎖外等待。執行緒A進去,執行第一天輸出語句,緊接著順利進入OBJECT1鎖物件,在執行第二條輸出語句,再接著A執行緒釋放鎖1,再接著釋放鎖2.就在剛釋放鎖2後,剛開始在外等待的BCD執行緒中的D執行緒搶到鎖2物件,進入鎖2.與此同時,A執行緒再進行if判斷,並進入鎖1,此時鎖1呈關閉狀態,A在執行第三條輸出語句,接著D執行緒執行第四條輸出語句。就在這時發生了死鎖狀態。由於A將鎖1鎖住,執行緒D無法進入到else語句中的鎖1,同時D執行緒進入到的鎖1也被鎖,在if語句中的執行緒A無法進入鎖2。這時發生死鎖。
有關執行緒死鎖方面有一個很經典的故事叫"哲學家進餐"問題,小夥伴們可以參見我的另一篇部落格。
連線地址:
停止執行緒
- 使用邏輯控制執行緒結束
- 強行叫醒正在沉睡(sleep、wait)的執行緒,再使用邏輯控制執行緒結束
例:
package net.csdn.qf.teat;
/**
* @author 北冥有熊
* 2018年11月8日
*/
public class Test03 {
public static void main(String[] args) {
//demo1(); //邏輯中斷執行緒
demo2(); //停止sleep或wait的執行緒
}
public static void dmo1() {
Demo1 demo1 = new Demo1();
Thread thread = new Thread(demo1,"AA");
thread.start();
try {
Thread.sleep(5000); //讓AA執行緒先執行一會兒,
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
demo1.num = 1001; //使用邏輯控制AA執行緒結束
System.out.println("主執行緒main---over---");
}
public static void demo2() {
Demo2 demo2 = new Demo2();
Thread thread2 = new Thread(demo2,"BB");
thread2.start();
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
Thread.sleep(5000); //沉睡5秒後再叫醒已經熟睡的BB執行緒
thread2.interrupt();//強行叫醒正在沉睡的BB執行緒
} catch (Exception e) {
// TODO: handle exception
}
demo2.num = 1001; //控制BB執行緒結束
System.out.println("主執行緒main---over---");
}
}
class Demo2 implements Runnable{
int num = 1;
public void run() {
while (num<=100) {
System.out.println(Thread.currentThread().getName()+"---"+num++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(num==15) {
try {
System.out.println("BB執行緒開始睡覺");
Thread.sleep(100000000); //將其改為wait同理
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
class Demo1 implements Runnable{
int num = 1;
public void run() {
while (num<1000) {
System.out.println(Thread.currentThread().getName()+"---"+num++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
守護執行緒:
守護執行緒(Daemon)是執行在後臺的一種特殊執行緒。它獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。使用setDaemon()方法將執行緒變為守護執行緒。
例:
package net.csdn.qf.teat;
/**
* @author 北冥有熊
* 2018年11月8日
*/
public class Test04 {
public static void main(String[] args) {
Demo4 demo4 = new Demo4();
Thread thread = new Thread(demo4,"CC");
thread.setDaemon(true); //將這個執行緒設定為守護執行緒,該方法必須在啟動執行緒前呼叫。
thread.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主執行緒main---over---JVM退出");
}
}
class Demo4 implements Runnable{
int num = 1;
public void run() {
while(num<=100) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("---------"+num++);
}
}
}
如果使用者執行緒全部退出了,只剩下守護執行緒存在了,虛擬機器也就退出了。
最後再來看一下join方法:
t1.join()---搶奪執行權---主方法會把執行權交給t1,坐等t1掛,t1掛了主方法才接著執行。可以用來中途加入需要執行的執行緒。
例:
package net.csdn.qf.teat;
/**
* @author 北冥有熊
* 2018年11月8日
*/
public class Test05 {
public static void main(String[] args) {
JoinDemo joinDemo=new JoinDemo();
Thread t1=new Thread(joinDemo);
t1.start();
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
if (i==5) {
try {
t1.join();//搶奪執行權
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"執行緒執行---"+i);
}
}
}
class JoinDemo implements Runnable {
public int num=10;
public void run() {
// TODO Auto-generated method stub
while (num<50) {
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(("----AAA執行緒執行---"+num));
}
}
}
結果:
main執行緒執行---0
----AAA執行緒執行---10
main執行緒執行---1
----AAA執行緒執行---10
main執行緒執行---2
----AAA執行緒執行---10
main執行緒執行---3
----AAA執行緒執行---10
main執行緒執行---4
----AAA執行緒執行---10
----AAA執行緒執行---10
----AAA執行緒執行---10
----AAA執行緒執行---10
----AAA執行緒執行---10
----AAA執行緒執行---10
----AAA執行緒執行---10
----AAA執行緒執行---10
很明顯,在當兩個執行緒同時執行時,中途main執行緒將執行權交給了AAA執行緒。
本部落格持續更新中.......!