執行緒基礎03
6.用戶執行緒和守護執行緒
-
用戶執行緒:也叫作業執行緒,當執行緒的任務執行完或者通知方法結束,平時用到的普通執行緒均是用戶執行緒,當在Java程式中創建一個執行緒,它就被稱為用戶執行緒
-
守護執行緒(Daemon):一般是為作業執行緒服務的,當所有的用戶執行緒結束,守護執行緒自動結束
-
常見的守護執行緒:垃圾回識訓制
例子1:如何將一個執行緒設定成守護執行緒
package li.thread.method;
public class ThreadMethodExercise {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果我們希望當主執行緒結束后,子執行緒自動結束,只需要將子執行緒設定為守護執行緒
myDaemonThread.setDaemon(true);
myDaemonThread.start();
for (int i = 1; i <= 10; i++) {//main執行緒
System.out.println("悟空在前方打妖精...");
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread {
@Override
public void run() {
for (; ; ) {//無限回圈
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("八戒收拾東西回高老莊...");
}
}
}
7.執行緒的生命周期
- JDK中用Thread.State列舉表示了執行緒的幾種狀態:


例子
package li.thread.state;
public class ThreadState_ {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getName() + "狀態 " + t.getState());
t.start();
while (t.getState() != Thread.State.TERMINATED) {
System.out.println(t.getName() + "狀態 " + t.getState());
Thread.sleep(1000);
}
System.out.println(t.getName() + "狀態 " + t.getState());
}
}
class T extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hi" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
8.執行緒同步機制
- 執行緒同步機制
- 在多執行緒編程中,一些敏感資料不允許被多個執行緒同時訪問,此時就使用同步訪問技術,保證資料在任何同一時刻,最多有一個執行緒訪問,以保證資料的完整性,
- 也可以理解為:執行緒同步,即當有一個執行緒在對記憶體進行操作時,其他執行緒都不可以對這個記憶體地址進行操作,直到該執行緒完成操作,其他執行緒才能對該記憶體地址進行操作,
- 同步具體方法--Synchronized
-
同步代碼塊
synchronized(物件){//得到物件的鎖,才能操作同步代碼 //需要被同步的代碼 } -
synchronized還可以放在方法宣告中,表示整個方法為同步方法
public synchronized void m(String name){ //需要被同步的代碼 }就好像某個小伙伴上廁所之前先把門關上(上鎖),完事之后再出來(解鎖),那么其他小伙伴就可以再使用廁所了
例子:使用synchronized解決3.1售票問題
package li.thread.syn;
//使用多執行緒,模擬三個視窗同時售票共100張
public class SynSellTicket {
public static void main(String[] args) {
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();//第1個執行緒-視窗
new Thread(sellTicket03).start();//第2個執行緒-視窗
new Thread(sellTicket03).start();//第3個執行緒-視窗
}
}
//實作介面方式,使用synchronized實作執行緒同步
class SellTicket03 implements Runnable {
private int ticketNum = 100;
private boolean loop = true;//控制run方法變數
public synchronized void sell() {//同步方法,在在同一時刻,只能有一個執行緒來執行run方法
if (ticketNum <= 0) {
System.out.println("售票結束...");
loop = false;
return;
}
//休眠50毫秒,模擬
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("視窗:" + Thread.currentThread().getName() + "售出一張票 "
+ "剩余票數:" + (--ticketNum));
}
@Override
public void run() {
while (loop) {
sell();//sell方法是一個同步方法
}
}
}
8.1互斥鎖
- 基本介紹
- Java語言中,引入了物件互斥鎖的額概念,來保證共享資料操作的完整性
- 每一個物件都對應于一個可稱為“互斥鎖”的標記,這個標記用來保證在任一時刻,只能有一個執行緒訪問該物件
- 關鍵字synchronized來與物件的互斥鎖聯系,當某個物件用synchronized修飾時,表明該物件在任一時刻只能有一個執行緒訪問
- 同步的局限性:導致程式的執行效率降低
- 非靜態的同步方法,鎖可以是this(當前物件),也可以是其他物件(要求鎖的是同一個物件)
- 同步方法(靜態的)的鎖為當前類本身(類.class)
synchronized實作同步的基礎:Java中的每一個物件都可以作為鎖,
具體表現為以下3種形式,
對于普通同步方法,鎖是當前實體物件,
對于靜態同步方法,鎖是當前類的Class物件,
對于同步方法塊,鎖是Synchonized括號里配置的物件,
- 注意事項和細節:
- 同步方法如果沒有使用static修飾:默認鎖物件為this
- 如果方法使用static修飾,默認鎖物件:當前類.class
- 實作的落地步驟:
- 需要先分析上鎖的代碼
- 選擇同步代碼塊或者同步方法
- 要求多個執行緒的鎖物件為同一個
package li.thread.syn;
//使用多執行緒,模擬三個視窗同時售票共100張
public class SynSellTicket {
public static void main(String[] args) {
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();//第1個執行緒-視窗
new Thread(sellTicket03).start();//第2個執行緒-視窗
new Thread(sellTicket03).start();//第3個執行緒-視窗
}
}
//實作介面方式,使用synchronized實作執行緒同步
class SellTicket03 implements Runnable {
private int ticketNum = 100;
private boolean loop = true;//控制run方法變數
Object object = new Object();
//1.public synchronized static void m1(){}的鎖加在SellTicket03.class
public synchronized static void m1(){}
//2.如果在靜態方法中,要實作一個同步代碼塊則應該這樣寫:(原因是靜態方法適合類一起加載的,靜態方法不能使用this)
public static void m2(){
synchronized (SellTicket03.class){
System.out.println("m2");
}
}
// public synchronized void sell() {}就是一個同步方法,這時,鎖在this物件
//也可以在代碼塊上寫synchronized,同步代碼塊,互斥鎖還是在this物件
public /*synchronized*/void sell() {//同步方法
synchronized (/*this*/object) {//如果是new Object就不是同一個物件
if (ticketNum <= 0) {
System.out.println("售票結束...");
loop = false;
return;
}
//休眠50毫秒,模擬
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("視窗:" + Thread.currentThread().getName() + "售出一張票 "
+ "剩余票數:" + (--ticketNum));
}
}
@Override
public void run() {
while (loop) {
sell();//sell方法是一個同步方法
}
}
}
8.2執行緒的死鎖
- 基本介紹:
多個執行緒都占用了對方的鎖資源,但不肯相讓,導致了死鎖,
在編程中一定要避免死鎖的發生,
例子:
package li.thread.syn;
//模擬執行緒死鎖
public class DeadLock_ {
public static void main(String[] args) {
//模擬死鎖現象
DeadLockDemo A = new DeadLockDemo(true);
DeadLockDemo B = new DeadLockDemo(false);
A.setName("A執行緒");
B.setName("B執行緒");
A.start();
B.start();
}
}
class DeadLockDemo extends Thread {
static Object o1 = new Object();//保證多執行緒,共享一個物件,這里使用static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//構造器
this.flag = flag;
}
@Override
public void run() {
//下面業務邏輯的分析
//1.如果flag為true,執行緒就會先得到/持有 o1物件鎖,然后嘗試去獲取o2物件鎖
//2.如果執行緒A得不到o2物件鎖,就會Blocked
//3.如果flag為false,執行緒B就會先得到/持有 o2物件鎖,然后嘗試去獲取o1物件鎖
//4.如果執行緒B得不到o1物件鎖,就會Blocked
if (flag) {
synchronized (o1) {//物件互斥鎖,下面就是同步代碼
System.out.println(Thread.currentThread().getName() + "進入1");
synchronized (o2) {//這里獲得li物件的監視權
System.out.println(Thread.currentThread().getName() + "進入2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + "進入3");
synchronized (o1) {//這里獲得li物件的監視權
System.out.println(Thread.currentThread().getName() + "進入4");
}
}
}
}
}
如下圖:兩個執行緒卡住了
8.3釋放鎖
下面操作會釋放鎖:
- 當前執行緒的同步方法、同步代碼塊執行結束
- 當前執行緒在同步代碼塊、同步方法中遇到break、return
- 當前執行緒在同步代碼塊、同步方法中出現了未處理的Error或者Exception,導致例外結束
- 當前執行緒在同步代碼塊、同步方法中執行了執行緒物件的wait()方法,當前執行緒暫停,并釋放鎖
下面的操作不會釋放鎖:
-
執行緒執行同步代碼塊或同步方法時,程式呼叫Thread.sleep()、Thread.yield()方法暫停當前執行緒的執行,不會釋放鎖
-
執行緒執行同步代碼塊時,其他執行緒呼叫了該執行緒的suspend()方法將該執行緒掛起,該執行緒不會釋放鎖,
? 提示:應盡量避免使用suspend()和resume()來控制執行緒,這兩個方法不再推薦使用
9.本章作業
9.1執行緒HomeWork01
(1)在main方法中啟動兩個執行緒
(2)第一個執行緒回圈隨機列印100以內的整數
(3)直到第二個執行緒從鍵盤讀取了“Q”命令
練習:
package li.thread.syn.homework;
import java.util.Scanner;
//(1)在main方法中啟動兩個執行緒
public class ThreadHomeWork01 {
public static void main(String[] args) {
A a = new A();
B b = new B(a);//注意把a物件傳入b構造器中
a.start();
b.start();
}
}
//創建A執行緒類
class A extends Thread {
private boolean loop = true;
@Override
public void run() {
while (loop) {
System.out.println((int) (Math.random() * 100 + 1));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("a執行緒退出...");
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
//創建B執行緒類
class B extends Thread {
private A a;
Scanner scanner = new Scanner(System.in);
public B(A a) {
this.a = a;
}
@Override
public void run() {
while (true) {
//接到用戶輸入
System.out.println("請輸入你的指令(Q)表示退出");
char key = scanner.next().toUpperCase().charAt(0);
if (key == 'Q') {
//以通知的方式結束a執行緒
a.setLoop(false);
System.out.println("b執行緒退出...");
break;
}
}
}
}
9.2執行緒執行緒HomeWork02
(1)有兩個用戶分別從同一張卡上取錢(總額10000)
(2)每次都取1000,當余額不足時,就不能取款了
(3)不能出現超取現象==>執行緒同步問題
易錯點:關于互斥鎖的理解
對于普通同步方法,鎖是當前實體物件,
對于靜態同步方法,鎖是當前類的Class物件,
對于同步方法塊,鎖是Synchonized括號里配置的物件
package li.thread.syn.homework;
public class ThreadHomeWork02 {
public static void main(String[] args) {
T t = new T();
Thread thread1 = new Thread(t);
Thread thread2 = new Thread(t);
thread1.setName("t1");
thread2.setName("t2");
thread1.start();
thread2.start();
}
}
class T implements Runnable {
private int money = 10000;
@Override
public void run() {
while (true) {//while不要放到同步代碼塊里面
//1.使用了synchronized實作執行緒同步
//2.當多個執行緒執行到這里的時候就會去爭奪 this物件鎖
//3.哪個執行緒爭奪到(獲取)this物件鎖,就執行synchronized代碼塊
//4.爭奪不到this物件鎖,就Blocked,準備繼續爭奪
//5.this物件鎖是非公平鎖
synchronized (this) {
if (money <= 0) {
System.out.println("余額不足...");
break;
}
money -= 1000;
System.out.println(Thread.currentThread().getName() + "取出了1000元" + " 當前余額為:" + money);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/504266.html
標籤:其他
