一栅盲、線程簡(jiǎn)介
- 多任務(wù):用戶(hù)在同一時(shí)間內(nèi),運(yùn)行多個(gè)應(yīng)用程序废恋;
-
多線程:指程序(一個(gè)進(jìn)程)運(yùn)行時(shí)產(chǎn)生了不止一個(gè)線程谈秫。
- 進(jìn)程:操作系統(tǒng)中運(yùn)行的程序;
- 線程:一個(gè)進(jìn)程里面可以有多個(gè)線程鱼鼓,如視頻中拟烫,同時(shí)有聲音,圖像等迄本。
- 調(diào)用方法與調(diào)用多線程的區(qū)別:
- 調(diào)用方法:run() 方法硕淑,需要執(zhí)行的方法體;
- 調(diào)用多線程:start() 方法,開(kāi)啟多線程的方法體嘉赎。
- Process(進(jìn)程) 與 Thread(線程)
1. 程序進(jìn)程置媳、線程
- 在操作系統(tǒng)中,運(yùn)行的程序公条,就是進(jìn)程拇囊,而進(jìn)程通常采用多進(jìn)程執(zhí)行;
- 程序:指令和數(shù)據(jù)的有序集合靶橱,本身沒(méi)有任何運(yùn)行的含義寥袭,是靜態(tài)概念;
- 進(jìn)程:是執(zhí)行程序的一次執(zhí)行過(guò)程关霸,是一個(gè)動(dòng)態(tài)概念传黄,是系統(tǒng)資源分配的單位;
- 線程:一個(gè)進(jìn)程通常包括多個(gè)線程队寇,線程是 CPU 調(diào)度和執(zhí)行的單位尝江。
注意:
- 很多多線程,是模擬出來(lái)的英上,真正的多線程是指炭序,有多個(gè) CPU啤覆,即多核,如服務(wù)器;如果是模擬出來(lái)的多線程朱庆,即在一個(gè) CPU 的情況下巧颈,在同一個(gè)時(shí)間點(diǎn),CPU 只能執(zhí)行一個(gè)代碼笨觅,因?yàn)榍袚Q的很快,所以耕腾,就有同時(shí)執(zhí)行的錯(cuò)覺(jué)见剩。
2. 線程的核心概念
- 線程,就是獨(dú)立的執(zhí)行路徑扫俺;
- 在程序運(yùn)行時(shí)苍苞,即使沒(méi)有自己創(chuàng)建線程,后臺(tái)也會(huì)有多個(gè)線程狼纬,比如主線程羹呵,GC 線程;
- main() 稱(chēng)之為主線程疗琉,為系統(tǒng)的入口冈欢,用于執(zhí)行整個(gè)程序;
- 在一個(gè)進(jìn)程中盈简,如果開(kāi)辟了多個(gè)線程凑耻,線程的運(yùn)行是由調(diào)度器(cpu)安排調(diào)度的,調(diào)度器是與操作系統(tǒng)緊密相關(guān)的柠贤,先后順序是不能人為干預(yù)的拳话。
- 對(duì)同一份資源操作時(shí),會(huì)存在資源搶奪的問(wèn)題种吸,需要加入并發(fā)控制弃衍;
- 線程會(huì)帶來(lái)額外的開(kāi)銷(xiāo),如 CPU 調(diào)度時(shí)間坚俗,并發(fā)控制開(kāi)銷(xiāo)镜盯;
- 每個(gè)線程,在自己的工作內(nèi)存交互猖败,內(nèi)存控制不當(dāng)速缆,會(huì)造成數(shù)據(jù)不一致。
二恩闻、線程創(chuàng)建
1. 線程創(chuàng)建三種方式:
- 繼承 Thread 類(lèi)(重要)不建議使用:避免 OOP 單繼承局限性
- 自定義線程類(lèi)繼承 Thread 類(lèi)艺糜;
- 重寫(xiě) run() 方法,編寫(xiě)線程執(zhí)行體;
- 創(chuàng)建線程對(duì)象破停,調(diào)用 start() 方法啟動(dòng)線程翅楼。
package com.xxx.demo01;
/**
* 創(chuàng)建線程方式 1:繼承 Thread 類(lèi)
* 1.重寫(xiě) run() 方法
* 2.調(diào)用 start 開(kāi)啟線程
*/
public class TestThread01 extends Thread {
// 重寫(xiě) run() 方法
// 線程入口點(diǎn)
@Override
public void run() {
// run 方法線程體
for (int i = 0; i < 20; i++) {
System.out.println("run 方法線程 -- " + i);
}
}
// main 線程:主線程
public static void main(String[] args) {
// 創(chuàng)建線程對(duì)象
TestThread01 testThread01 = new TestThread01();
// 調(diào)用 start() 開(kāi)啟線程
testThread01.start();
for (int i = 0; i < 200; i++) {
System.out.println("main 主線程 -- " + i);
}
}
}
- 運(yùn)行結(jié)果:多線程時(shí),線程交替執(zhí)行
總結(jié):線程不一定立即執(zhí)行真慢,由 CPU 安排調(diào)度毅臊。
- 實(shí)例:下載網(wǎng)絡(luò)圖片
package com.xxx.demo01;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* 多線程同步下載圖片
* 需下載 Commons IO 包
*/
public class TestThread02 extends Thread {
private String url; // 網(wǎng)絡(luò)圖片地址
private String name; // 保存的文件名
// 有參構(gòu)造器
public TestThread02(String url, String name) {
this.url = url;
this.name = name;
}
// 下載圖片的線程執(zhí)行體
@Override
public void run() {
WebDownload webDownload = new WebDownload();
webDownload.download(url, name);
System.out.println("下載了文件名為:" + name);
}
public static void main(String[] args) {
TestThread02 t1 = new TestThread02("圖片地址1", "1.jpg");
TestThread02 t2 = new TestThread02("圖片地址2", "2.jpg");
TestThread02 t3 = new TestThread02("圖片地址3", "3.jpg");
// 開(kāi)啟多線程:實(shí)際執(zhí)行順序,不確定(CPU 調(diào)度)
t1.start();
t2.start();
t3.start();
}
}
// 下載器
class WebDownload {
public void download(String url, String name) {
// 下載方法
try {
//調(diào)用 Commons_io 包里面的 copyURLToFile 方法
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO異常黑界,downloader下載方法異常");
}
}
}
- 結(jié)果:
Commons IO:針對(duì)開(kāi)發(fā) IO 功能的工具類(lèi)庫(kù) 下載地址
- 將下載的 jar 包管嬉,復(fù)制到
lib
包(創(chuàng)建)下,并右鍵添加為庫(kù)...
- 實(shí)現(xiàn) Runnable 接口(推薦使用)
- 推薦使用 Runnable 對(duì)象朗鸠,因?yàn)?Java 單繼承的局限性蚯撩;
- 自定義線程類(lèi),實(shí)現(xiàn) Runnable 接口烛占;
- 實(shí)現(xiàn) run() 方法胎挎,編寫(xiě)線程執(zhí)行體;
- 創(chuàng)建線程對(duì)象扰楼,調(diào)用 start() 方法呀癣,啟動(dòng)對(duì)象美浦。
package com.xxx.demo01;
/**
* 創(chuàng)建線程方式 2:實(shí)現(xiàn) Runnable 接口
* 1.重寫(xiě) run() 方法
* 2.執(zhí)行線程需要弦赖,丟入 Runnable 接口實(shí)現(xiàn)類(lèi)
* 3.調(diào)用 start 開(kāi)啟線程
*/
public class TestThread03 implements Runnable {
// 重寫(xiě) run() 方法
// 線程入口點(diǎn)
@Override
public void run() {
// run 方法線程體
for (int i = 0; i < 20; i++) {
System.out.println("run 方法線程 -- " + i);
}
}
// main 線程:主線程
public static void main(String[] args) {
// 創(chuàng)建 Runnable 接口的實(shí)現(xiàn)類(lèi)對(duì)象
TestThread03 testThread03 = new TestThread03();
// 代理:創(chuàng)建線程對(duì)象,通過(guò)線程對(duì)象開(kāi)啟線程
// Thread thread = new Thread(testThread03);
// thread.start();
// 簡(jiǎn)化
new Thread(testThread03).start();
for (int i = 0; i < 200; i++) {
System.out.println("main 主線程 -- " + i);
}
}
}
- 運(yùn)行結(jié)果:線程交替執(zhí)行
總結(jié):兩種方式對(duì)比
- 實(shí)例:并發(fā)問(wèn)題
package com.xxx.demo01;
/**
* 多個(gè)線程同時(shí)操作同一個(gè)對(duì)象:買(mǎi)火車(chē)票案例
* 發(fā)現(xiàn)問(wèn)題:多個(gè)線程操作同一個(gè)資源的情況下,線程不安全,數(shù)據(jù)紊亂
*/
public class TestThread04 implements Runnable {
// 票數(shù)
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums <= 0) {
break;
}
// 模擬延時(shí)(需要捕獲異常)
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Thread.currentThread().getName() 獲取線程名
System.out.println(Thread.currentThread().getName() +
"-->拿到了第 " + ticketNums-- + " 張票");
}
}
public static void main(String[] args) {
TestThread04 thread04 = new TestThread04();
new Thread(thread04, "用戶(hù)1").start();
new Thread(thread04, "用戶(hù)2").start();
new Thread(thread04, "用戶(hù)3").start();
}
}
- 結(jié)果:發(fā)現(xiàn)問(wèn)題浦辨,數(shù)據(jù)重復(fù)
- 實(shí)例:龜兔賽跑
package com.xxx.demo01;
/**
* 模擬龜兔賽跑
*/
public class Race implements Runnable {
// 勝利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
// 模擬兔子休息
if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 判斷比賽是否結(jié)束
boolean flag = gameOver(i);
// 如果比賽結(jié)束蹬竖,停止程序
if (flag) {
break;
}
System.out.println(Thread.currentThread().getName() + " -->跑了 " + i + " 步");
}
}
// 判斷是否完成比賽
private boolean gameOver(int steps) {
// 判斷是否有勝利者
if (winner != null) { // 不為空:已經(jīng)有勝利者
return true;
} else {
if (steps >= 100) {
winner = Thread.currentThread().getName();
System.out.println("Winner is " + winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "兔子").start();
new Thread(race, "烏龜").start();
}
}
- 實(shí)現(xiàn) Callable 接口(了解)
- 實(shí)現(xiàn) Callable 接口,需要返回值類(lèi)型流酬;
- 重寫(xiě) call 方法币厕,需要拋出異常;
- 創(chuàng)建目標(biāo)對(duì)象芽腾;
- 創(chuàng)建執(zhí)行服務(wù):
ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交執(zhí)行:
Future<> result1 = ser.submit(t1);
- 獲取結(jié)果:
boolean r1 = result1.get();
- 關(guān)閉服務(wù):
ser.shutdownNow();
- 實(shí)例:
package com.xxx.demo02;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
/**
* 線程創(chuàng)建方式 3:實(shí)現(xiàn) Callable 接口
*/
public class TestCallable implements Callable<Boolean> {
private String url; // 網(wǎng)絡(luò)圖片地址
private String name; // 保存的文件名
// 有參構(gòu)造器
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
// 下載圖片的線程執(zhí)行體
@Override
public Boolean call() {
WebDownload webDownload = new WebDownload();
webDownload.download(url, name);
System.out.println("下載了文件名為:" + name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("圖片地址1", "1.jpg");
TestCallable t2 = new TestCallable("圖片地址2", "2.jpg");
TestCallable t3 = new TestCallable("圖片地址3", "3.jpg");
// 創(chuàng)建執(zhí)行服務(wù):
ExecutorService ser = Executors.newFixedThreadPool(3);
// 提交執(zhí)行:
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);
// 獲取結(jié)果:
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
System.out.println(rs1);
System.out.println(rs2);
System.out.println(rs3);
// 關(guān)閉服務(wù):
ser.shutdownNow();
}
}
// 下載器
class WebDownload {
public void download(String url, String name) {
// 下載方法
try {
//調(diào)用 Commons_io 包里面的 copyURLToFile 方法
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO異常旦装,downloader下載方法異常");
}
}
}
- Callable 優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn):可以定義返回值、可以拋出異常摊滔;
- 缺點(diǎn):實(shí)現(xiàn)方式比較復(fù)雜阴绢。
- Thread 和 Runnable 對(duì)比
- 繼承 Thred 類(lèi):
- 子類(lèi)繼承 Thread 類(lèi)具備多線程能力;
- 啟動(dòng)線程:子類(lèi)對(duì)象.start()艰躺;
- 不建議使用:避免OOP單繼承局限性呻袭。
- 實(shí)現(xiàn) Runnable 接口
- 實(shí)現(xiàn) Runnable 接口,具有多線程能力腺兴;
- 啟動(dòng)線程:傳入目標(biāo)對(duì)象 + Thread 對(duì)象.start()左电;
- 推薦使用:避免單繼承局限性,靈活方便,方便同一個(gè)對(duì)象被多個(gè)線程使用篓足。
2. 靜態(tài)代理
- 實(shí)例:實(shí)現(xiàn)靜態(tài)代理段誊,對(duì)比 Thread
package com.xxx.demo02;
/**
* 靜態(tài)代理
*/
public class StaticProxy {
public static void main(String[] args) {
// WeddingCompany weddingCompany = new WeddingCompany(new You());
// weddingCompany.HappyMarry();
// 簡(jiǎn)化
new WeddingCompany(new You()).HappyMarry();
}
}
// 自定義接口
interface Marry {
void HappyMarry();
}
// 真實(shí)角色:自定義類(lèi),并實(shí)現(xiàn) Marry 接口
class You implements Marry {
@Override
public void HappyMarry() {
System.out.println("You Marry...");
}
}
// 代理角色:自定義類(lèi)纷纫,并實(shí)現(xiàn) Marry 接口(代理角色幫助真實(shí)角色執(zhí)行)
class WeddingCompany implements Marry {
// 代理誰(shuí)枕扫?--->真實(shí)目標(biāo)角色(幫誰(shuí)實(shí)現(xiàn))
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
// 真實(shí)對(duì)象
this.target.HappyMarry();
after();
}
private void before() {
System.out.println("前:布置現(xiàn)場(chǎng)");
}
private void after() {
System.out.println("后:收尾款");
}
}
- 優(yōu)化:使用線程,Lamda 表達(dá)式辱魁。(線程的實(shí)現(xiàn)原理烟瞧,就是代理模式)
package com.xxx.demo02;
/**
* 線程中的代理模式
*/
public class StaticProxy02 {
public static void main(String[] args) {
// 線程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("線程中的代理模式");
}
}).start();
// 簡(jiǎn)化:Lambda 表達(dá)式
new Thread(() -> System.out.println("線程,Lambda 表達(dá)式")).start();
// 靜態(tài)代理模式
new WeddingCompany(new You()).HappyMarry();
}
}
- 總結(jié):
- 真實(shí)對(duì)象和代理對(duì)象染簇,都要實(shí)現(xiàn)同一個(gè)接口参滴;
- 代理對(duì)象要代理真實(shí)角色。
- 好處
- 代理對(duì)象可以做很多真實(shí)對(duì)象做不了的事情锻弓;
- 真實(shí)對(duì)象專(zhuān)注做自己的事砾赔。
3. Lambda 表達(dá)式
- λ 希臘字母表中,排序第十一位的字母青灼,英語(yǔ)名稱(chēng)為 Lambda暴心;
- 避免匿名內(nèi)部類(lèi)定義過(guò)多;
- 其實(shí)質(zhì)屬于函數(shù)式編程的概念杂拨;
- 去掉了一堆沒(méi)有意義的代碼专普,只留下核心邏輯。
(params) -> expression [表達(dá)式]
(params) -> statement [語(yǔ)句]
(params) -> {statements}
a -> System.out.println("i like lamda-->"+a)
new Thread (()->System.out.println(“多線程學(xué)習(xí)弹沽。檀夹。。策橘≌ǘ桑”)).start();
- 理解 Functional Interface (函數(shù)式接口)丽已,是學(xué)習(xí) Java 8 lambda 表達(dá)式的關(guān)鍵蚌堵。
函數(shù)式接口的定義
- 任何接口,如果 只包含唯一 1 個(gè)抽象方法沛婴,那么它就是一個(gè)函數(shù)式接口吼畏。
public interface Runnable{
public abstract void run();
}
- 對(duì)于函數(shù)式接口,我們可以通過(guò) Lambda 表達(dá)式來(lái)創(chuàng)建該接口的對(duì)象瘸味。
推導(dǎo) Lambda 表達(dá)式
- 案例 1:
package com.xxx.lambda;
/**
* 推導(dǎo) Lambda 表達(dá)式
*/
public class TestLambda01 {
public static void main(String[] args) {
// 接口方式創(chuàng)建對(duì)象
ILike like = new Like();
like.lambda();
}
}
// 1.定義一下函數(shù)式接口
interface ILike {
void lambda();
}
// 2.定義接口實(shí)現(xiàn)類(lèi)
class Like implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda...");
}
}
- 優(yōu)化一:靜態(tài)內(nèi)部類(lèi)方式
package com.xxx.lambda;
/**
* 推導(dǎo) Lambda 表達(dá)式
*/
public class TestLambda01 {
// 3.靜態(tài)內(nèi)部類(lèi)
static class Like2 implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda 2");
}
}
public static void main(String[] args) {
// 靜態(tài)內(nèi)部類(lèi)方式
Like2 like2 = new Like2();
like2.lambda();
}
}
// 1.定義一下函數(shù)式接口
interface ILike {
void lambda();
}
- 優(yōu)化二:局部?jī)?nèi)部類(lèi)方式
package com.xxx.lambda;
/**
* 推導(dǎo) Lambda 表達(dá)式
*/
public class TestLambda01 {
public static void main(String[] args) {
// 4.局部?jī)?nèi)部類(lèi)
class Like3 implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda 3");
}
}
Like3 like3 = new Like3();
like3.lambda();
}
}
// 1.定義一下函數(shù)式接口
interface ILike {
void lambda();
}
- 優(yōu)化三:匿名內(nèi)部類(lèi)方式
package com.xxx.lambda;
/**
* 推導(dǎo) Lambda 表達(dá)式
*/
public class TestLambda01 {
public static void main(String[] args) {
// 5.匿名內(nèi)部類(lèi):沒(méi)有類(lèi)的名稱(chēng)宫仗,必須借助接口或者父類(lèi)
ILike like4 = new ILike() {
@Override
public void lambda() {
System.out.println("I like lambda 4");
}
};
like4.lambda();
}
}
// 1.定義一下函數(shù)式接口
interface ILike {
void lambda();
}
- 最終版:用 Lambda 簡(jiǎn)化
package com.xxx.lambda;
/**
* 推導(dǎo) Lambda 表達(dá)式
*/
public class TestLambda01 {
public static void main(String[] args) {
// 6.用 Lambda 簡(jiǎn)化
ILike like5 = () -> {
System.out.println("I like lambda 5");
};
like5.lambda();
}
}
// 1.定義一下函數(shù)式接口
interface ILike {
void lambda();
}
- 案例 2:Lambda 表達(dá)式簡(jiǎn)化
package com.xxx.lambda;
/**
* Lambda 表達(dá)式簡(jiǎn)化
*/
public class TestLambda02 {
public static void main(String[] args) {
ILove love = null;
// 1.Lambda 表達(dá)式簡(jiǎn)化
love = (int a) -> {
System.out.println("I love you -> " + a);
};
// 簡(jiǎn)化1:去掉參數(shù)類(lèi)型
love = (a) -> {
System.out.println("I love you -> " + a);
};
// 簡(jiǎn)化2:去掉括號(hào)
love = a -> {
System.out.println("I love you -> " + a);
};
// 簡(jiǎn)化3:去掉花括號(hào)
love = a -> System.out.println("I love you -> " + a);
/*
總結(jié):
1.{} 簡(jiǎn)略的條件是,只能有一行代碼,多行代碼旁仿,{} 就不能簡(jiǎn)略
2.前提是接口為函數(shù)式接口(只能有一個(gè)方法)
3.多個(gè)參數(shù)也可以去掉參數(shù)類(lèi)型,要去掉就全部去掉,但必須加上()
*/
love.love(1);
}
}
// 自定義接口
interface ILove {
void love(int a);
}
- lambda 表達(dá)式只有一行代碼才能簡(jiǎn)化成藕夫,多行得用代碼塊孽糖。
hello = a->System.out.println("hello " + a);
- 多個(gè)參數(shù)需要括號(hào),要去掉類(lèi)型毅贮,所有參數(shù)都要去掉類(lèi)型办悟。
hello = (a, b)->System.out.println(a + " Hello " + b);
三、線程狀態(tài)
1. 線程的五大狀態(tài)
2. 線程方法
方法 | 說(shuō)明 |
---|---|
setPriority(int newPriority) | 更改線程的 優(yōu)先級(jí) |
static void sleep(long millis) | 在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程 休眠 |
void join() | 等待該線程終止(插隊(duì)) |
static void yield() | (禮讓)暫停當(dāng)前正在執(zhí)行的線程對(duì)象滩褥,并執(zhí)行其他線程 |
void interrupt() | 中斷線程病蛉,不推薦用這個(gè)方式 |
boolean isAlive() | 測(cè)試線程是否處于活動(dòng)狀態(tài) |
- 停止線程
- 實(shí)例:設(shè)置標(biāo)志位,讓線程停止
package com.xxx.state;
/**
* 線程停止:測(cè)試 stop
* 1.建議線程正常停止 --> 利用次數(shù),不建議死循環(huán)
* 2.建議使用標(biāo)志位 --> 設(shè)置一個(gè)標(biāo)志位
* 3.不要使用 stop 或者 destroy 等過(guò)時(shí)或者 JDK 不建議使用的方法
*/
public class TestStop implements Runnable {
// 1.設(shè)置一個(gè)標(biāo)志位
private Boolean flag = true;
@Override
public void run() {
int i = 0;
// 2.線程體使用該標(biāo)志位
while (flag) {
System.out.println("run......Thread" + i++);
}
}
// 3.自定義一個(gè)公開(kāi)的方法瑰煎,停止線程铺然,轉(zhuǎn)換標(biāo)志位
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
// 創(chuàng)建線程對(duì)象
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main " + i);
if (i == 900) {
// 調(diào)用自定義 stop(),切換標(biāo)志位酒甸,讓線程終止
testStop.stop();
System.out.println("線程該停止了魄健!");
}
}
}
}
- 線程休眠
- 實(shí)例:模擬網(wǎng)絡(luò)延時(shí):放大問(wèn)題的發(fā)生性
package com.xxx.state;
/**
* 模擬網(wǎng)絡(luò)延時(shí):放大問(wèn)題的發(fā)生性
*/
public class TestSleep implements Runnable {
// 票數(shù)
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums <= 0) {
break;
}
// 模擬延時(shí)(需要捕獲異常)
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Thread.currentThread().getName() 獲取線程名
System.out.println(Thread.currentThread().getName() +
"-->拿到了第 " + ticketNums-- + " 張票");
}
}
public static void main(String[] args) {
TestSleep thread = new TestSleep();
new Thread(thread, "用戶(hù)1").start();
new Thread(thread, "用戶(hù)2").start();
new Thread(thread, "用戶(hù)3").start();
}
}
- 實(shí)例:模擬倒計(jì)時(shí)
package com.xxx.state;
/**
* 模擬倒計(jì)時(shí)
*/
public class TestSleep02 {
public static void main(String[] args) {
try {
tenDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 模擬倒計(jì)時(shí)
public static void tenDown() throws InterruptedException {
int num = 10;
while (true) {
Thread.sleep(1000);
System.out.println(num--);
if (num <= 0) {
break;
}
}
}
}
- 實(shí)例:每隔一秒,獲取當(dāng)前時(shí)間
package com.xxx.state;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 每隔一秒插勤,獲取當(dāng)前時(shí)間
*/
public class TestSleep03 {
public static void main(String[] args) {
// 獲取系統(tǒng)當(dāng)前時(shí)間
Date startTime = new Date(System.currentTimeMillis());
while (true) {
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
// 更新當(dāng)前時(shí)間
startTime = new Date(System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 線程禮讓
- 禮讓線程沽瘦,讓當(dāng)前正在執(zhí)行的線程暫停,但不阻塞农尖;
- 將線程從運(yùn)行狀態(tài)轉(zhuǎn)為就緒狀態(tài)析恋;
- 讓 CPU 重新調(diào)試,禮讓不一定成功盛卡,主要看 CPU助隧。
- 實(shí)例:
package com.xxx.state;
/**
* 禮讓線程
* 禮讓不一定成功,取決于 CPU
*/
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield, "a").start();
new Thread(myYield, "b").start();
}
}
class MyYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "線程開(kāi)始執(zhí)行");
Thread.yield(); // 禮讓線程
System.out.println(Thread.currentThread().getName() + "線程停止執(zhí)行");
}
}
- 禮讓成功
圖片.png
- 禮讓不成功
- 線程插隊(duì)
- Join 合并線程窟扑,只能是當(dāng)前線程執(zhí)行完之后喇颁,才能執(zhí)行其他線程漏健,對(duì)其他線程造成阻塞嚎货;
- 可以想象成插隊(duì)(強(qiáng)制執(zhí)行)
- 實(shí)例:
package com.xxx.state;
/**
* Join 插隊(duì)(強(qiáng)制執(zhí)行)
*/
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("線程VIP " + i);
}
}
public static void main(String[] args) throws InterruptedException {
// 啟動(dòng)線程
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
// 主線程
for (int i = 0; i < 500; i++) {
if (i == 200) {
thread.join(); // 插隊(duì):需要捕獲異常
}
System.out.println("main " + i);
}
}
}
3. 線程狀態(tài)觀測(cè)
public static enum Thread.State
extends Enum<Thread.State>
線程狀態(tài)。 線程可以處于以下?tīng)顟B(tài)之一:
-
NEW
:尚未啟動(dòng)的線程處于此狀態(tài)蔫浆。 -
RUNNABLE
:在Java虛擬機(jī)中執(zhí)行的線程處于此狀態(tài)殖属。 -
BLOCKED
:被阻塞等待監(jiān)視器鎖定的線程處于此狀態(tài)。 -
WAITING
:正在等待另一個(gè)線程執(zhí)行特定動(dòng)作的線程處于此狀態(tài)瓦盛。 -
TIMED_WAITING
:正在等待另一個(gè)線程執(zhí)行動(dòng)作達(dá)到指定等待時(shí)間的線程處于此狀態(tài)洗显。 -
TERMINATED
:已退出的線程處于此狀態(tài)。 - 一個(gè)線程可以在給定時(shí)間點(diǎn)處于一個(gè)狀態(tài)原环。 這些狀態(tài)是不反映任何操作系統(tǒng)線程狀態(tài)的虛擬機(jī)狀態(tài)挠唆。
- 實(shí)例:
package com.xxx.state;
/**
* 觀測(cè)線程狀態(tài)
*/
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("---------------------");
});
// 觀測(cè)狀態(tài)
Thread.State state = thread.getState();
System.out.println(state); // NEW
// 觀察啟動(dòng)后
thread.start();
state = thread.getState();
System.out.println(state); // RUN
// 只要線程不終止,就一直輸出狀態(tài)
while (state != Thread.State.TERMINATED) {
Thread.sleep(100);
state = thread.getState(); // 更新線程狀態(tài)
System.out.println(state); // 輸出狀態(tài)
}
// 線程只開(kāi)啟一次:死亡后的線程不能再啟動(dòng)了嘱吗,啟動(dòng)會(huì)報(bào)異常
// thread.start();
}
}
4. 線程優(yōu)先級(jí)
- Java 提供一個(gè)線程調(diào)度器玄组,來(lái)監(jiān)控程序中啟動(dòng)后,進(jìn)入就緒狀態(tài)的所有線程,線程調(diào)度器按照優(yōu)先級(jí)俄讹,決定應(yīng)該調(diào)度哪個(gè)線程來(lái)執(zhí)行哆致。
- 線程的優(yōu)先級(jí)用數(shù)字表示,范圍從1~10患膛。
- 最小優(yōu)先級(jí):Thread.MIN_PRIORITY= 1;
- 最大優(yōu)先級(jí):Thread.MAX_PRIORITY = 10;
- 默認(rèn)優(yōu)先級(jí):Thread.NORM_PRIORITY= 5;
- 使用以下方式獲取或改變優(yōu)先級(jí)
- getPriority() 摊阀,setPriority(int xxx)
注意:
- 先設(shè)置優(yōu)先級(jí)再啟動(dòng),優(yōu)先級(jí)的設(shè)定踪蹬,建議在 start() 調(diào)度前胞此;
- main 方法的默認(rèn)優(yōu)先級(jí)為 5;
- 理論上來(lái)說(shuō)跃捣,優(yōu)先級(jí)越高的越先執(zhí)行豌鹤,哪怕 start() 更晚;
- 優(yōu)先級(jí)低枝缔,只意味著獲得調(diào)度的概率低布疙,并不是優(yōu)先級(jí)低就不會(huì)被調(diào)用了,這都是看 CPU 的調(diào)度愿卸。
- 實(shí)例:
package com.xxx.state;
/**
* 測(cè)試線程優(yōu)先級(jí):getPriority()
*/
public class TestPriority {
public static void main(String[] args) {
// 主線程優(yōu)先級(jí) main(Thread-0 --> 5)
System.out.println(Thread.currentThread().getName() +
" --> " + Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
Thread t6 = new Thread(myPriority);
// 先設(shè)置優(yōu)先級(jí)灵临,再啟動(dòng)
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
// MAX_PRIORITY=10 最大優(yōu)先級(jí)
t4.setPriority(Thread.MAX_PRIORITY);
t4.start();
// t5.setPriority(-1); 報(bào)錯(cuò)
// t5.start();
// t6.setPriority(11); 報(bào)錯(cuò)
// t6.start();
}
}
class MyPriority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +
" --> " + Thread.currentThread().getPriority());
}
}
5. 守護(hù)(daemon)線程
- 線程分為 用戶(hù)線程 和 守護(hù)線程;
- 虛擬機(jī) 必須確保用戶(hù)線程 執(zhí)行完畢趴荸;
- 虛擬機(jī) 不用等待守護(hù)線程 執(zhí)行完畢儒溉;
- 守護(hù)線程,如:后臺(tái)記錄操作日志发钝,監(jiān)控內(nèi)存垃圾回收等待……
- 實(shí)例:
package com.xxx.state;
/**
* 守護(hù)線程
*/
public class TestDaemon {
public static void main(String[] args) {
Guard guard = new Guard();
You you = new You();
Thread thread = new Thread(guard);
// 默認(rèn) false:表示是用戶(hù)線程顿涣,正常線程都是用戶(hù)線程
thread.setDaemon(true);
// 守護(hù)線程啟動(dòng)
thread.start();
// 用戶(hù)線程啟動(dòng)
new Thread(you).start();
}
}
// 守護(hù)線程
class Guard implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("守護(hù)線程一直運(yùn)行");
}
}
}
// 用戶(hù)線程
class You implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("用戶(hù)線程 " + i);
}
System.out.println("------用戶(hù)線程結(jié)束------");
}
}
四、線程同步
1. 介紹
- 多個(gè)線程操作同一個(gè)資源 酝豪;
- 并發(fā):同一個(gè)對(duì)象涛碑,被多個(gè)線程同時(shí)操作。
- 現(xiàn)實(shí)生活中孵淘,我們會(huì)遇到 同—個(gè)資源蒲障,多個(gè)人都想使用 的問(wèn)題,比如:食堂排隊(duì)打飯瘫证,每個(gè)人都想吃飯揉阎,最天然的解決辦法就是:排隊(duì),一個(gè)個(gè)來(lái)背捌。
- 處理多線程問(wèn)題時(shí)毙籽,多個(gè)線程訪問(wèn)同一個(gè)對(duì)象,并且毡庆,某些線程還想修改這個(gè)對(duì)象坑赡,這時(shí)巡扇,就需要線程同步。
- 線程同步:其實(shí)就是一種等待機(jī)制垮衷,多個(gè)需要同時(shí)訪問(wèn)此對(duì)象的線程厅翔,進(jìn)入這個(gè) 對(duì)象的等待池 形成隊(duì)列,等待前面線程使用完畢搀突,下一個(gè)線程再使用刀闷。
隊(duì)列和鎖
- 隊(duì)列:排隊(duì)
- 鎖:每個(gè)對(duì)象都有把鎖,當(dāng)獲取對(duì)象時(shí)仰迁,獨(dú)占資源甸昏,其他線程必須等待,使用結(jié)束后才釋放徐许。
線程同步:
- 由于同一進(jìn)程的多個(gè)線程施蜜,共享同一塊存儲(chǔ)空間,在帶來(lái)方便的同時(shí)雌隅,也帶來(lái)了訪問(wèn)沖突問(wèn)題翻默,為了保證數(shù)據(jù),在方法中被訪問(wèn)時(shí)的正確性恰起,在訪問(wèn)時(shí)加入 鎖機(jī)制 synchronized修械。
- 當(dāng)一個(gè)線程獲得對(duì)象的排它鎖,獨(dú)占資源检盼,其他線程必須等待肯污,使用后釋放鎖即可。
- 鎖機(jī)制吨枉,存在以下問(wèn)題:
- 一個(gè)線程持有鎖蹦渣,會(huì)導(dǎo)致其他所有需要此鎖的線程掛起;
- 在多線程競(jìng)爭(zhēng)下貌亭,加鎖柬唯,釋放鎖,會(huì)導(dǎo)致比較多的上下文切換属提,和調(diào)度延時(shí)权逗,引起性能問(wèn)題美尸;
- 如果一個(gè)優(yōu)先級(jí)高的線程冤议,等待一個(gè)優(yōu)先級(jí)低的線程釋放鎖,會(huì)導(dǎo)致優(yōu)先級(jí)倒置师坎,引起性能問(wèn)題恕酸。
2. 不安全的線程案例
- 實(shí)例 1:
package com.xxx.syn;
/**
* 多線程不安全實(shí)例:不安全買(mǎi)票
* 線程不安全:有負(fù)數(shù)
*/
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station, "用戶(hù)1").start();
new Thread(station, "用戶(hù)2").start();
new Thread(station, "用戶(hù)3").start();
}
}
class BuyTicket implements Runnable {
// 票
private int ticketNums = 10;
// 外部停止標(biāo)志
private Boolean flag = true;
@Override
public void run() {
// 買(mǎi)票
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 買(mǎi)票
private void buy() throws InterruptedException {
// 判斷是否有票
if (ticketNums <= 0) {
flag = false;
return;
}
// 模擬延時(shí)
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() +
"拿到了第 " + ticketNums-- + " 張票");
}
}
- 實(shí)例 2:
package com.xxx.syn;
/**
* 不安全取錢(qián):兩個(gè)人同時(shí)取一個(gè)賬戶(hù)的錢(qián)
*/
public class UnsafeBank {
public static void main(String[] args) {
// 賬戶(hù)
Account account = new Account(100, "個(gè)人賬戶(hù)");
Drawing youA = new Drawing(account, 50, "YouA");
Drawing youB = new Drawing(account, 100, "YouB");
youA.start();
youB.start();
}
}
//賬戶(hù)
class Account {
int money; // 余額
String cardName; // 卡名
public Account(int money, String cardName) {
this.money = money;
this.cardName = cardName;
}
}
// 銀行:模擬取款
class Drawing extends Thread {
// 賬戶(hù)
Account account;
// 取錢(qián)
int drawingMoney;
// 手里的錢(qián)
int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
// super(name) = 父類(lèi)構(gòu)造方法(name)
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
// 取錢(qián)
@Override
public void run() {
// 判斷有沒(méi)有錢(qián)
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + " 錢(qián)不夠,取不了胯陋!");
return;
}
// 延時(shí):可以放大問(wèn)題的發(fā)生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 卡內(nèi)余額 = 余額 - 取的錢(qián)
account.money = account.money - drawingMoney;
// 手里的錢(qián)
nowMoney = nowMoney + drawingMoney;
System.out.println(account.cardName + "余額為:" + account.money);
// Thread.currentThread().getName() = this.getName()
System.out.println(this.getName() + "手里的錢(qián):" + nowMoney);
}
}
- 實(shí)例 3:
package com.xxx.syn;
import java.util.ArrayList;
import java.util.List;
/**
* 線程不安全的集合
*/
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> list.add(Thread.currentThread().getName())).start();
}
// 延時(shí)
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
3. 同步方法
-
由于可以通過(guò)
private
關(guān)鍵字蕊温,來(lái)保證數(shù)據(jù)對(duì)象袱箱,只能被方法訪問(wèn),所以只需要針對(duì)方法义矛,提岀一套機(jī)制发笔,這套機(jī)制就是:synchronized
關(guān)鍵字,它包括兩種用法:synchronized 方法
和synchronized 塊
凉翻。- 同步方法:
public synchronized void method (int args) {}
- 同步方法:
-
synchronized
方法了讨,控制對(duì) “對(duì)象” 的訪問(wèn),每個(gè)對(duì)象對(duì)應(yīng)一把鎖制轰,每個(gè)synchronized
方法前计,都必須獲得調(diào)用該方法的對(duì)象的鎖,才能執(zhí)行垃杖,否則線程會(huì)阻塞男杈,方法一旦執(zhí)行,就獨(dú)占該鎖调俘,直到該方法返回伶棒,才釋放鎖,后面被阻塞的線程彩库,才能獲得這個(gè)鎖苞冯,繼續(xù)執(zhí)行。- 缺陷:若將一個(gè)大的方法侧巨,申明為 synchronized 將會(huì)影響效率舅锄。
實(shí)例 1:同步方法
package com.xxx.syn;
/**
* 安全買(mǎi)票
* 同步方法:synchronized
*/
public class SafeBuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station, "用戶(hù)1").start();
new Thread(station, "用戶(hù)2").start();
new Thread(station, "用戶(hù)3").start();
}
}
class BuyTicket implements Runnable {
// 票
private int ticketNums = 10;
// 外部停止標(biāo)志
private Boolean flag = true;
@Override
public void run() {
// 買(mǎi)票
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 買(mǎi)票
// synchronized 同步方法,鎖的是 this
private synchronized void buy() throws InterruptedException {
// 判斷是否有票
if (ticketNums <= 0) {
flag = false;
return;
}
// 模擬延時(shí)
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() +
"拿到了第 " + ticketNums-- + " 張票");
}
}
4. 同步塊
- 同步塊:
synchronized (Obj) {}
- Obj 稱(chēng)之為同步監(jiān)視器
- Obj 可以是任何對(duì)象司忱,但是推薦使用共享資源皇忿,作為同步監(jiān)視器。
- 同步方法中坦仍,無(wú)需指定同步監(jiān)視器鳍烁,因?yàn)橥椒椒ǖ耐奖O(jiān)視器,就是 this繁扎,就是這個(gè)對(duì)象本身幔荒,或者是 class。
- 同步監(jiān)視器的執(zhí)行過(guò)程:
- 第一個(gè)線程訪問(wèn)梳玫,鎖定同步監(jiān)視器爹梁,執(zhí)行其中代碼;
- 第二個(gè)線程訪問(wèn)提澎,發(fā)現(xiàn)同步監(jiān)視器被鎖定姚垃,無(wú)法訪問(wèn);
- 第一個(gè)線程訪問(wèn)完畢盼忌,解鎖同步監(jiān)視器积糯;
- 第二個(gè)線程訪問(wèn)掂墓,發(fā)現(xiàn)同步監(jiān)視器沒(méi)有鎖,然后鎖定并訪問(wèn)看成。
- 同步塊:鎖的對(duì)象就是變化的量君编,需要增、刪川慌、改的對(duì)象
- 實(shí)例 2:同步塊
package com.xxx.syn;
/**
* 安全取錢(qián):兩個(gè)人同時(shí)取一個(gè)賬戶(hù)的錢(qián)
* 同步塊:synchronized (Obj) {}
* 鎖的對(duì)象就是變化的量,需要增啦粹、刪、改的對(duì)象
*/
public class SafeBank {
public static void main(String[] args) {
// 賬戶(hù)
Account account = new Account(1000, "個(gè)人賬戶(hù)");
Drawing youA = new Drawing(account, 50, "YouA");
Drawing youB = new Drawing(account, 100, "YouB");
youA.start();
youB.start();
}
}
//賬戶(hù)
class Account {
int money; // 余額
String cardName; // 卡名
public Account(int money, String cardName) {
this.money = money;
this.cardName = cardName;
}
}
// 銀行:模擬取款
class Drawing extends Thread {
// 賬戶(hù)
Account account;
// 取錢(qián)
int drawingMoney;
// 手里的錢(qián)
int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
// super(name) = 父類(lèi)構(gòu)造方法(name)
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
// 取錢(qián)
// synchronized 默認(rèn)鎖的是 this
@Override
public void run() {
// 同步塊:鎖的對(duì)象就是變化的量,需要增窘游、刪唠椭、改的對(duì)象
synchronized (account) {
// 判斷有沒(méi)有錢(qián)
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + " 錢(qián)不夠,取不了忍饰!");
return;
}
// 延時(shí):可以放大問(wèn)題的發(fā)生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 卡內(nèi)余額 = 余額 - 取的錢(qián)
account.money = account.money - drawingMoney;
// 手里的錢(qián)
nowMoney = nowMoney + drawingMoney;
System.out.println(account.cardName + "余額為:" + account.money);
// Thread.currentThread().getName() = this.getName()
System.out.println(this.getName() + "手里的錢(qián):" + nowMoney);
}
}
}
- 方法里面有需要修改的內(nèi)容贪嫂,才需要鎖,鎖的太多艾蓝,會(huì)浪費(fèi)資源力崇。
- 實(shí)例 3:同步塊,線程安全的集合
package com.xxx.syn;
import java.util.ArrayList;
import java.util.List;
/**
* 線程安全的集合
* 同步塊:synchronized (Obj) {}
*/
public class SafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
// 同步塊
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
// 延時(shí)
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
JUC 安全集合類(lèi)型擴(kuò)充
- 實(shí)例:
package com.xxx.syn;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* concurrent 并發(fā):測(cè)試 JUC安全類(lèi)型的集合
*/
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
// 延時(shí)
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
5. 死鎖
- 多個(gè)線程赢织,各自占有一些共享資源亮靴,并且,互相等待其他線程占有的資源于置,才能運(yùn)行茧吊,而導(dǎo)致兩個(gè)或者多個(gè)線程,都在等待對(duì)方釋放資源八毯,都停止執(zhí)行的情形搓侄。某一個(gè)同步塊,同時(shí)擁有 兩個(gè)以上對(duì)象的鎖 時(shí),就可能會(huì)發(fā)生 死鎖 的問(wèn)題。
- 實(shí)例:程序鎖死
package com.xxx.syn;
/**
* 死鎖:多個(gè)線程互相抱著對(duì)方需要的資源懈息,然后形成僵持
* 解決:一個(gè)鎖只鎖一個(gè)對(duì)象
*/
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0, "girl001");
Makeup g2 = new Makeup(1, "girl002");
g1.start();
g2.start();
}
}
// 口紅
class Lipstick {
}
// 鏡子
class Mirror {
}
// 化妝
class Makeup extends Thread {
// 需要的資源只有一份,用 static 保證只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
// 選擇
int choice;
// 使用化妝品的人
String girlName;
public Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
// 化妝
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 化妝:互相持有對(duì)方的鎖,就是需要拿到對(duì)方的資源
private void makeup() throws InterruptedException {
if (choice == 0) {
// 獲得口紅的鎖
synchronized (lipstick) {
System.out.println(this.girlName + "獲得口紅的鎖");
// 延時(shí)
Thread.sleep(1000);
// 1 秒后儿奶,獲得鏡子的鎖
synchronized (mirror) {
System.out.println(this.girlName + "獲得鏡子的鎖");
}
}
} else {
// 獲得鏡子的鎖
synchronized (mirror) {
System.out.println(this.girlName + "獲得鏡子的鎖");
// 延時(shí)
Thread.sleep(2000);
// 2 秒后,獲得口紅的鎖
synchronized (lipstick) {
System.out.println(this.girlName + "獲得口紅的鎖");
}
}
}
}
}
- 實(shí)例:解決死鎖問(wèn)題
package com.xxx.syn;
/**
* 死鎖:多個(gè)線程互相抱著對(duì)方需要的資源,然后形成僵持
* 解決:一個(gè)鎖只鎖一個(gè)對(duì)象
*/
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0, "girl001");
Makeup g2 = new Makeup(1, "girl002");
g1.start();
g2.start();
}
}
// 口紅
class Lipstick {
}
// 鏡子
class Mirror {
}
// 化妝
class Makeup extends Thread {
// 需要的資源只有一份,用 static 保證只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
// 選擇
int choice;
// 使用化妝品的人
String girlName;
public Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
// 化妝
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 化妝:互相持有對(duì)方的鎖,就是需要拿到對(duì)方的資源
private void makeup() throws InterruptedException {
if (choice == 0) {
// 獲得口紅的鎖
synchronized (lipstick) {
System.out.println(this.girlName + "獲得口紅的鎖");
// 延時(shí)
Thread.sleep(1000);
}
// 1 秒后云石,獲得鏡子的鎖
synchronized (mirror) {
System.out.println(this.girlName + "獲得鏡子的鎖");
}
} else {
// 獲得鏡子的鎖
synchronized (mirror) {
System.out.println(this.girlName + "獲得鏡子的鎖");
// 延時(shí)
Thread.sleep(2000);
}
// 2 秒后,獲得口紅的鎖
synchronized (lipstick) {
System.out.println(this.girlName + "獲得口紅的鎖");
}
}
}
}
避免死鎖的辦法
- 產(chǎn)生死鎖的四個(gè)必要條件
- 互斥條件:一個(gè)資源毎次只能被一個(gè)進(jìn)程使用白指;
- 請(qǐng)求與保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí)留晚,對(duì)已獲得的資源保持不放;
- 不剝奪條件∶進(jìn)程已獲得的資源告嘲,在末使用完之前错维,不能強(qiáng)行剝奪;
- 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系橄唬。
- 上面列出了死鎖的四個(gè)必要條件赋焕,只要想辦法破其中的任意一個(gè),或多個(gè)條件仰楚,就可以避免死鎖發(fā)生隆判。
6. Lock(鎖)
- 從 JDK 5.0 開(kāi)始,Java 提供了更強(qiáng)大的線程同步機(jī)制:通過(guò)顯式定義同步鎖對(duì)象僧界,來(lái)實(shí)現(xiàn)同步侨嘀。同步鎖使用 Lock 對(duì)象充當(dāng)。
-
java.util.concurrent.locks.Lock
接口捂襟,是控制多個(gè)線程咬腕,對(duì)共享資源進(jìn)行訪問(wèn)的工具。鎖葬荷,提供了對(duì)共享資源的獨(dú)占訪問(wèn)涨共,每次只能有一個(gè)線程,對(duì) Lock 對(duì)象加鎖宠漩,線程開(kāi)始訪問(wèn)共享資源之前举反,應(yīng)先獲得 Lock 對(duì)象。 -
ReentrantLock
類(lèi)扒吁,實(shí)現(xiàn)了 Lock火鼻,它擁有與synchronized
相同的并發(fā)性和內(nèi)存語(yǔ)義,在實(shí)現(xiàn)線程安全的控制中雕崩,比較常用的是ReentrantLock
凝危,可以顯式加鎖、釋放鎖晨逝。 - 鎖代碼格式
class A{
private final ReentrantLock lock = new ReentrantLock();
public void m(){
lock.lock();
try{
// 保證線程安全的代碼
}
finally{
lock.unlock();
// 如果同步代碼有異常蛾默,要將unlock()寫(xiě)入finally語(yǔ)句塊
}
}
}
- 實(shí)例:測(cè)試 Lock 鎖
package com.xxx.gaoji;
import java.util.concurrent.locks.ReentrantLock;
/**
* 測(cè)試 Lock 鎖
*/
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable {
// 票
private int ticketNums = 10;
// 定義 Lock 鎖
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 加鎖
lock.lock();
if (ticketNums > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
} else {
break;
}
} finally {
// 解鎖
lock.unlock();
}
}
}
}
7. synchroized 與 Lock 對(duì)比
- Lock 是顯式鎖 (手動(dòng)開(kāi)啟和關(guān)閉鎖,別忘記關(guān)閉鎖)捉貌, synchronized 是隱式鎖支鸡, 出了作用域自動(dòng)釋放;
- Lock 只有代碼塊鎖趁窃,synchronized 有代碼塊鎖和方法鎖牧挣;
- 使用 Lock 鎖,JVM 將花費(fèi)較少的時(shí)間來(lái)調(diào)度線程醒陆,性能更好瀑构,并且具有更好的擴(kuò)展性 (提供更多的子類(lèi));
- 優(yōu)先使用順序:
- Lock > 同步代碼塊 (已經(jīng)進(jìn)入了方法體刨摩,分配了相應(yīng)資源) > 同步方法 (在方法體之外)
五寺晌、線程通信問(wèn)題
- 應(yīng)用場(chǎng)景:生產(chǎn)者和消費(fèi)者問(wèn)題
- 假設(shè)倉(cāng)庫(kù)中只能存放一件產(chǎn)品世吨,生產(chǎn)者將生產(chǎn)出來(lái)的產(chǎn)品放入倉(cāng)庫(kù),消費(fèi)者將倉(cāng)庫(kù)中產(chǎn)品取走消費(fèi)呻征;
- 如果倉(cāng)庫(kù)中沒(méi)有產(chǎn)品耘婚,則生產(chǎn)者將產(chǎn)品放入倉(cāng)庫(kù),否則停止生產(chǎn)并等待陆赋,直到倉(cāng)庫(kù)中的產(chǎn)品被消費(fèi)者取走為止沐祷;
- 如果倉(cāng)庫(kù)中放有產(chǎn)品,則消費(fèi)者可以將產(chǎn)品取走消費(fèi)攒岛,否則停止消費(fèi)并等待赖临,直到倉(cāng)庫(kù)中再次放入產(chǎn)品為止。
1. 線程通信方法
- Java提供了幾個(gè)方法解決線程之間的通信問(wèn)題灾锯。
方法名 | 作用 |
---|---|
wait() | 表示線程一直等待兢榨,直到其他線程通知,與 sleep 不同挠进,會(huì)釋放鎖色乾。 |
wait(long timeout) | 指定等待的毫秒數(shù)。 |
notify() | 喚醒一個(gè)處于等待狀態(tài)的線程领突。 |
notifyAll() | 喚醒同一個(gè)對(duì)象上暖璧,所有調(diào)用 wait() 方法的線程,優(yōu)先級(jí)別高的線程君旦,優(yōu)先調(diào)度澎办。 |
- 注意:均是 Object 類(lèi)的方法,都只能在同步方法金砍,或者同步代碼塊中使用局蚀,否則會(huì)拋出異常
IIIegalMonitorStateException
。 -
這是一個(gè)線程同步問(wèn)題恕稠,生產(chǎn)者和消費(fèi)者共享同一個(gè)資源琅绅,并且生產(chǎn)者和消費(fèi)者之間相互依賴(lài),互為條件:
- 對(duì)于生產(chǎn)者鹅巍,沒(méi)有生產(chǎn)產(chǎn)品之前千扶,要通知消費(fèi)者等待。而生產(chǎn)了產(chǎn)品之后骆捧,又需要馬上通知消費(fèi)者消費(fèi)澎羞;
- 對(duì)于消費(fèi)者,在消費(fèi)之后敛苇,要通知生產(chǎn)者已經(jīng)結(jié)束消費(fèi)妆绞,需要生產(chǎn)新的產(chǎn)品以供消費(fèi);
- 在生產(chǎn)者消費(fèi)者問(wèn)題中,僅有 synchronized 是不夠的:
- synchronized 可阻止并發(fā)更新同一個(gè)共享資源括饶,實(shí)現(xiàn)了同步株茶;
- synchronized 不能用來(lái)實(shí)現(xiàn)不同線程之間的消息傳遞(通信)。
2. 線程通信問(wèn)題解決方式
解決方式 1:管程法
- 并發(fā)協(xié)作模型巷帝,生產(chǎn)者/消費(fèi)者模式 —> 管程法:
- 生產(chǎn)者:負(fù)責(zé)生產(chǎn)數(shù)據(jù)的模塊(可能是方法忌卤,對(duì)象扫夜,線程楞泼,進(jìn)程);
- 消費(fèi)者:負(fù)責(zé)處理數(shù)據(jù)的模塊(可能是方法笤闯,對(duì)象堕阔,線程,進(jìn)程)颗味;
- 緩沖區(qū):消費(fèi)者不能直接使用生產(chǎn)者的數(shù)據(jù)超陆,他們之間有個(gè) 緩沖區(qū)。
- 生產(chǎn)者將生產(chǎn)好的數(shù)據(jù)放入緩沖區(qū)浦马,消費(fèi)者從緩沖區(qū)拿出數(shù)據(jù)时呀。
- 實(shí)例:管程法
package com.xxx.gaoji;
/**
* 測(cè)試:生產(chǎn)者消費(fèi)者模型-->利用緩沖區(qū)解決:管程法
* 生產(chǎn)者、消費(fèi)者晶默、產(chǎn)品谨娜、緩沖區(qū)
*/
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
// 生產(chǎn)者
class Productor extends Thread {
// 緩沖區(qū)
SynContainer container;
public Productor(SynContainer container) {
this.container = container;
}
// 生產(chǎn)
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Product(i));
System.out.println("生產(chǎn)了 " + i + " 件商品");
}
}
}
// 消費(fèi)者
class Consumer extends Thread {
// 緩沖區(qū)
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
// 消費(fèi)
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消費(fèi)了--> " + container.pop().id + " 件商品");
}
}
}
// 產(chǎn)品
class Product {
// 產(chǎn)品編號(hào)
int id;
public Product(int id) {
this.id = id;
}
}
// 緩沖區(qū)
class SynContainer {
// 需要一個(gè)容器大小
Product[] products = new Product[10];
// 容器計(jì)數(shù)器
int count = 0;
// 生產(chǎn)者放入產(chǎn)品
public synchronized void push(Product product) {
// 如果容器滿(mǎn)了,就需要等待消費(fèi)者消費(fèi)
/*
如果是 if 的話磺陡,假如消費(fèi)者1 消費(fèi)了最后一個(gè)趴梢,
這時(shí) index 變成 0 此時(shí)釋放鎖,被消費(fèi)者2 拿到币他,而不是生產(chǎn)者拿到坞靶,
這時(shí)消費(fèi)者的 wait 是在 if 里,所以它就直接去消費(fèi) index-1 下標(biāo)越界蝴悉,
如果是 while 就會(huì)再去判斷一下 index 得值是不是變成 0 了
*/
while (count == products.length) {
// 生產(chǎn)等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果沒(méi)有滿(mǎn)彰阴,就需要放入產(chǎn)品
products[count] = product;
count++;
// 通知消費(fèi)者消費(fèi)
this.notify();
}
// 消費(fèi)者消費(fèi)產(chǎn)品
public synchronized Product pop() {
// 判斷能否消費(fèi)
while (count <= 0) {
// 等待生產(chǎn)者生產(chǎn),消費(fèi)者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果可以消費(fèi)
count--;
Product product = products[count];
// 消費(fèi)完了拍冠,通知生產(chǎn)者生產(chǎn)
this.notify();
return product;
}
}
解決方式 2:信號(hào)燈法
- 并發(fā)協(xié)作模型:生產(chǎn)者/消費(fèi)者模式 —> 信號(hào)燈法尿这。
- 實(shí)例:
package com.xxx.gaoji;
/**
* 測(cè)試:生產(chǎn)者消費(fèi)者模型 2 -->信號(hào)燈法,標(biāo)志位解決
*/
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
// 生產(chǎn)者 --> 演員
class Player extends Thread {
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
this.tv.play("節(jié)目--1");
} else {
this.tv.play("節(jié)目--2");
}
}
}
}
// 消費(fèi)者 --> 觀眾
class Watcher extends Thread {
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.tv.watch();
}
}
}
// 產(chǎn)品 --> 節(jié)目
class TV {
// 演員表演,觀眾等待 T
// 觀眾觀看倦微,演員等待 F
// 表演的節(jié)目
String voice;
// 標(biāo)志位
Boolean flag = true;
// 表演
public synchronized void play(String voice) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演員表演了:" + voice);
// 通知觀眾觀看
// 通知喚醒
this.notifyAll();
this.voice = voice;
this.flag = !this.flag;
}
// 觀看
public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("觀看了:" + voice);
// 通知演員表演
this.notifyAll();
this.flag = !this.flag;
}
}
六妻味、線程池
- 背景:經(jīng)常創(chuàng)建和銷(xiāo)毀、使用量特別大的資源欣福,比如并發(fā)情況下的線程责球,對(duì)性能影響很大。
- 思路:提前創(chuàng)建好多個(gè)線程,放入線程池中雏逾,使用時(shí)直接獲取嘉裤,使用完放回池中∑懿可以避免頻繁創(chuàng)建銷(xiāo)毀屑宠、實(shí)現(xiàn)重復(fù)利用。類(lèi)似生活中的公共交通工具仇让。
- 好處:
- 提高響應(yīng)速度(減少了創(chuàng)建新線程的時(shí)間)典奉;
- 降低資源消耗(重復(fù)利用線程池中線程,不需要?dú)按味紕?chuàng)建)丧叽;
- 便于線程管理(…)
- corePoolSize:核心池的大形谰痢;
- maximumPoolSize:最大線程數(shù)踊淳;
- keepAliveTime:線程沒(méi)有任務(wù)時(shí)假瞬,最多保持多長(zhǎng)時(shí)間后會(huì)終止。
- JDK 5.0 起提供了線程池相關(guān) API:
ExecutorService
和Executors
迂尝。 -
ExecutorService
:真正的線程池接口脱茉。常見(jiàn)子類(lèi)ThreadPoolExecutor
。-
void execute( Runnable command)
:執(zhí)行任務(wù)命令垄开,沒(méi)有返回值琴许,一般用來(lái)執(zhí)行Runnable
; -
<T> Future<T> submit( Callable<T>task)
:執(zhí)行任務(wù)说榆,有返回值虚吟,一般又來(lái)執(zhí)行Callable
; -
void shutdown()
:關(guān)閉連接池签财。
-
-
Executors
:工具類(lèi)串慰、線程池的工廠類(lèi),用于創(chuàng)建并返回不同類(lèi)型的線程池唱蒸。 - 實(shí)例:線程池
package com.xxx.gaoji;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 測(cè)試線程池
*/
public class TestPool {
public static void main(String[] args) {
// 1.創(chuàng)建服務(wù)邦鲫,創(chuàng)建線程池
// newFixedThreadPool 參數(shù)為:線程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
// 執(zhí)行 Runnable 接口的實(shí)現(xiàn)類(lèi)
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 2.關(guān)閉鏈接
service.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
總結(jié):
package com.xxx.gaoji;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 回顧總結(jié)線程的創(chuàng)建
*/
public class ThreadNew {
public static void main(String[] args) {
// 1.繼承 Thread 類(lèi)
new MyThread1().start();
// 2.實(shí)現(xiàn) Runnable 接口
new Thread(new MyThread2()).start();
// 3.實(shí)現(xiàn) Callable 接口
FutureTask<Integer> futureTask = new FutureTask(new MyThread3());
new Thread(futureTask).start();
// 獲取返回值
try {
Integer integer = futureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
// 1.繼承 Thread 類(lèi)
class MyThread1 extends Thread {
@Override
public void run() {
System.out.println("MyThread1");
}
}
// 2.實(shí)現(xiàn) Runnable 接口
class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("MyThread2");
}
}
// 3.實(shí)現(xiàn) Callable 接口
class MyThread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("MyThread3");
return 100;
}
}