線程基礎03 6.用戶線程和守護線程 用戶線程:也叫工作線程,當線程的任務執行完或者通知方法結束。平時用到的普通線程均是用戶線程,當在Java程式中創建一個線程,它就被稱為用戶線程 守護線程(Daemon):一般是為工作線程服務的,當所有的用戶線程結束,守護線程自動結束 常見的守護線程:垃圾回收機制 ...
線程基礎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();
}
}
}
}