本文總結自,B站-遇見狂神說
1. 進程與線程
程序
指令和數據的有序集合,本身沒有任何運行的含義,是一個靜態的概念。
進程(Process)
是程序的一次過程,它是一個動態的概念。是系統資源分配的單位。
線程
- 一個進程可以包含若干個線程
- 一個進程中至少有一個線程(不然沒有意義)
- 線程是CPU調度和執行的單位
多線程
真正的多線程是指有多個CPU,即多核。如果是模擬出來的多線程(即一個PCU的情況下),在同一時間點,CPU只能執行一個代碼,因爲切換的很快,所以產生同時執行的錯覺。
2. 核心內容
- 線程就是獨立的執行路徑
- 在程序運行時,即使沒有自己創建線程,後臺也會有多個線程,如主線程,gc線程。
- 主線程
main()
,爲系統的入口,用於執行整個程序。 - 在一個進程中,如果開闢了多個線程,線程 的運行由調度器安排,調度器是與操作系統緊密相關的,先後順序是不能人爲干預的。
- 對同一份資源操作時,會存在搶奪資源的問題,需要加入併發控制;
- 線程會帶來額外開銷,如CPU調度時間,併發控制開銷;
- 每個線程在自己的工作內存交互,內存控制不當會造成數據不一致;
3. 三種創建方式
繼承Thread類
public class ThreadExtends extends Thread {
@Override
public void run(){
// run方法線程體
for (int i = 0; i <= 20; i++) {
System.out.println("run方法線程體--"+ i);
}
}
public static void main(String[] args) {
// main線程,主線程
ThreadExtends t = new ThreadExtends();
// 開啓線程(和主線程同時跑)
t.start();
// 先跑子線程
// t.run();
for (int i = 0; i <= 20; i++) {
System.out.println("主線程--"+ i);
}
}
}
結果:
...
run方法線程體--6
run方法線程體--7
run方法線程體--8
主線程--2
run方法線程體--9
主線程--3
...
案例:網圖下載
實現多線程下載圖片【導包:commons-io-2.7.jar】
public class ThreadDown_Extends extends Thread {
/**
* 網絡圖片地址
*/
private String url;
/**
* 保存的文件名
*/
private String name;
/**
* 全參構造方法
* @param url
* @param name
*/
public ThreadDown_Extends(String url, String name) {
this.url = url;
this.name = name;
}
/**
* 下載圖片線程的執行體
*/
@Override
public void run(){
WebDownloader web = new WebDownloader();
web.downloader(url,name);
System.out.println("下載了文件名爲:"+name);
}
}
下載類
public class WebDownloader{
/**
* 下載方法
* @param url
* @param name
*/
public void downloader(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO異常,downloder方法出現問題...");
}
}
}
測試類
public class TestThread {
public static void main(String[] args) {
String url = "https://img-blog.csdnimg.cn/20200409100541773.png";
long times = System.currentTimeMillis();
String name = times+".png";
testExtends(url, name);
}
/**
* 測試創建多線成方法:繼承線程類
*/
private static void testExtends(String url, String name) {
ThreadDown_Extends td1 = new ThreadDown_Extends(url, name);
ThreadDown_Extends td2 = new ThreadDown_Extends(url, name);
ThreadDown_Extends td3 = new ThreadDown_Extends(url, name);
td1.start();
td2.start();
td3.start();
}
}
實現Runable接口
public class ThreadRunnable implements Runnable {
@Override
public void run(){
// run方法線程體
for (int i = 0; i <= 20; i++) {
System.out.println("run方法線程體--"+ i);
}
}
public static void main(String[] args) {
// main線程,主線程
ThreadRunnable r = new ThreadRunnable();
// 調用開啓多線程(兩個線程同時跑)
new Thread(r).start();
for (int i = 0; i <= 20; i++) {
System.out.println("主線程--"+ i);
}
}
}
案例:網圖下載
public class ThreadDown_Runnable implements Runnable {
/**
* 網絡圖片地址
*/
private String url;
/**
* 保存的文件名
*/
private String name;
/**
* 全參構造方法
* @param url
* @param name
*/
public ThreadDown_Runnable(String url, String name) {
this.url = url;
this.name = name;
}
/**
* 下載圖片線程的執行體
*/
@Override
public void run(){
WebDownloader web = new WebDownloader();
web.downloader(url,name);
System.out.println("下載了文件名爲:"+name);
}
}
下載類【與繼承Thread類案例相同】
測試類
public class TestThread {
public static void main(String[] args) {
String url = "https://img-blog.csdnimg.cn/20200409100541773.png";
long times = System.currentTimeMillis();
String name = times+".png";
testRunnable(url, name);
}
/**
* 測試創建多線成方法:實現Runnable接口
*/
private static void testRunnable(String url, String name) {
// 創建Runnable接口的實現類對象
ThreadDown_Runnable r = new ThreadDown_Runnable(url, name);
// 創建線程對象,通過線程對象來開啓我們的線程,【代理】
/*Thread t = new Thread(r);
t.start();*/
// 簡潔版
new Thread(r).start();
}
}
案例:龜兔賽跑
- 首先來個賽道距離,然後要離終點越來越近
- 判斷比賽是否結束
- 打印出勝利者
- 龜兔賽跑開始
- 模擬兔子睡覺
- 最終烏龜贏得比賽
代碼:
package cn.luis.race;
/**
* @ClassName Race
* @Description TODO :龜兔賽跑,(兔子休息,烏龜贏得比賽)
* @Author L
* @Date 2020.06.15 0:13
* @Version 1.0
* @Remark
**/
public class Race implements Runnable {
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
// 模擬兔子睡覺(名爲兔子的進程和每走十步,兔子睡一下)
if ("流氓兔".equals(Thread.currentThread().getName()) && i % 10 == 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 判斷比賽是否結束
boolean flag = gameOver(i);
// 如果比賽結束了
if (flag) {
break;
}
System.out.println(Thread.currentThread().getName() + " -- 跑了 --" + i + " --步");
}
}
/**
* 判斷是否完成比賽
* @param step
* @return
*/
private boolean gameOver(int step) {
// 判斷是否有獲勝者
if (winner != null) {
// 存在
return true;
}
if (step >= 100) {
winner = Thread.currentThread().getName();
System.out.println("Winner is : " + winner);
return true;
}
return false;
}
/**
* 主方法
* @param args
*/
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "小烏龜").start();
new Thread(race, "流氓兔").start();
}
}
結果:
...
小烏龜 -- 跑了 --97 --步
小烏龜 -- 跑了 --98 --步
小烏龜 -- 跑了 --99 --步
Winner is : 小烏龜
實現Callable接口
-
實現此接口需要返回值類型
-
從寫call方法,需要拋出異常
-
創建目標對象
-
創建執行服務
ExecutorService service = Executors.newFixedThreadPool(3);
-
提交執行
Future<Boolean> r1 = service.submit(c1);
-
獲取結果
boolean rs1 = r1.get();
-
關閉服務
service.shutdownNow();
案例:網圖下載
package cn.luis.callable;
import cn.luis.down.WebDownloader;
import java.util.concurrent.Callable;
/**
* @ClassName ThreadDown
* @Description TODO 實現Callable接口:實現多線程下載圖片
* @Author L
* @Date 2020.06.14 10:39
* @Version 1.0
* @Remark
**/
public class ThreadDown_Callable implements Callable<Boolean> {
/**
* 網絡圖片地址
*/
private String url;
/**
* 保存的文件名
*/
private String name;
/**
* 全參構造方法
* @param url
* @param name
*/
public ThreadDown_Callable(String url, String name) {
this.url = url;
this.name = name;
}
/**
* 下載圖片線程的執行體
*/
@Override
public Boolean call(){
WebDownloader web = new WebDownloader();
web.downloader(url,name);
System.out.println("下載了文件名爲:"+name);
return true;
}
}
測試類:
package cn.luis.callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* @ClassName TestThread
* @Description TODO 測試類
* @Author L
* @Date 2020.06.14 11:45
* @Version 1.0
* @Remark TODO callable接口好處:1.可以定義返回值 2.可以拋出異常
**/
public class TestThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
String url = "https://img-blog.csdnimg.cn/20200409100541773.png";
long times = System.currentTimeMillis();
String name = times + ".png";
testCallable(url, name);
}
/**
* 測試創建多線程方法:實現Callable接口
*/
private static void testCallable(String url, String name) throws ExecutionException, InterruptedException {
// 創建Callable接口的實現類對象
ThreadDown_Callable c1 = new ThreadDown_Callable(url, name);
ThreadDown_Callable c2 = new ThreadDown_Callable(url, name);
ThreadDown_Callable c3 = new ThreadDown_Callable(url, name);
// 創建執行服務
ExecutorService service = Executors.newFixedThreadPool(3);
// 提交執行
Future<Boolean> r1 = service.submit(c1);
Future<Boolean> r2 = service.submit(c2);
Future<Boolean> r3 = service.submit(c3);
// 獲取結果(可以拋出異常)
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
// 打印結果
System.out.println(rs1);
System.out.println(rs2);
System.out.println(rs3);
// 關閉服務
service.shutdownNow();
}
}
4. Lambda表達式
-
λ
希臘字母中排序第十一位的字母。 -
避免匿名內部類定義過多,只展示核心邏輯。
-
函數式(接口)編程思想:接口只包含唯一一個抽象方法
代碼:
接口
public interface ILike {
void like();
}
實現類
public class Like implements ILike {
@Override
public void like() {
System.out.println("i like lambda 1!");
}
}
測試類
public class testLambda {
// 第二種:靜態內部類
static class Like2 implements ILike {
@Override
public void like() {
System.out.println("i like lambda 2!");
}
}
public static void main(String[] args) {
// 第一種:接口引用指向實現類
ILike like = new Like();
like.like();
// // 第二種:靜態內部類的使用
like = new Like2();
like.like();
// 第三種:局部內部類
class Like3 implements ILike {
@Override
public void like() {
System.out.println("i like lambda 3!");
}
}
like = new Like3();
like.like();
// 第四種:匿名內部類,沒有類的名字,必須藉助接口或者父類
like = new ILike() {
@Override
public void like() {
System.out.println("i like lambda 4!");
}
};
like.like();
// 第五種:lambda表達式
like = () -> System.out.println("i like lambda 5!");
like.like();
}
}
表達式簡化
-
參數類型,要麼都寫要麼都不寫
(a,b)-> {...}
-
省略大括號,只有一條語句時纔可以
(a,b)-> System.out.println("aaa");
-
省略參數小括號,只有一個參數時纔可以
c -> System.out.println("aaa");
5. 靜態代理
要求:
- 真實對象和代理對象都要實現同一接口
- 代理對象必須要代理真實角色(將真實角色傳入)
優點:
- 代理對象可以做很多真實對象做不了的事情
- 真實對象專注做自己的事情
案例:婚慶公司和結婚
接口
public interface Marry {
void happyMarry();
}
新郎
public class Xinlang implements Marry {
@Override
public void happyMarry() {
System.out.println("小明要結婚啦!");
}
}
婚慶公司:幫助你結婚(是個代理角色)
public class HunQing implements Marry {
/**
* 代理誰 --> 真實目標角色
*/
private Marry target;
public HunQing(Marry target) {
this.target = target;
}
/**
* 相當於增強了方法(功能多了)
*/
@Override
public void happyMarry() {
before();
// 真實對象調用方法
this.target.happyMarry();
after();
}
/**
* 婚後
*/
private void after() {
System.out.println("婚後--收尾款");
}
/**
* 婚前
*/
private void before() {
System.out.println("婚前--佈置婚禮殿堂");
}
}
測試類
public class StaticProxy {
public static void main(String[] args) {
new HunQing(new Xinlang()).happyMarry();
// 線程底部原理與結婚案例相同
new Thread(() -> System.out.println("I love You!")).start();
}
}
6. 線程狀態
7. 線程操作
線程方法
方法 | 說明 |
---|---|
setPriority(int newPriority) | 更改線程的優先級 |
static void sleep(long millis) | 休眠 |
void join() | 等待該線程終止 |
static void yield() | 暫停單籤正在執行的線程對象,並執行其他線程 |
void interrupt() | 中斷線程【廢棄】 |
boolean isAlive() | 測試線程是否處於活動狀態 |
停止線程
- 利用次數,不建議死循環
- 建議使用標誌位
- 不要使用JDK不建議的方法,如:stop,destory
代碼:
public class TestStop implements Runnable {
/**
* 1.設置標誌位
*/
private boolean flag = true;
/**
* 重寫線程執行方法
*/
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("子線程 -- " + i++);
}
}
/**
* 2.設置一個公開的方法停止線程,轉換標誌位
*/
public void stop() {
this.flag = false;
}
/**
* 主方法
*
* @param args
*/
public static void main(String[] args) {
// 常見Runnable接口實現類
TestStop stop = new TestStop();
// 模擬多線程
for (int i = 0; i < 10; i++) {
System.out.println("主線程 -- " + i);
// 滿足條件時停止子線程
if (i == 9) {
stop.stop();
System.out.println("線程該停止了!");
}
// 開啓多線程
new Thread(stop).start();
}
}
}
結果:
主線程 -- 0
主線程 -- 1
主線程 -- 2
主線程 -- 3
線程該停止了!
主線程 -- 4
線程休眠
格式
Thread.sleep(1000);
代碼:
package cn.luis.state;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 線程休眠
* 1. 模擬網絡延時:買票
* 2. 倒計時
**/
public class TestSleep {
public static void main(String[] args) {
/* down1();
down2();*/
time();
}
/**
* 打印當前系統時間
*/
private static void time() {
while (true) {
try {
Thread.sleep(1000);
Date date = new Date();
System.out.println(new SimpleDateFormat("YYYY:HH:mm:ss").format(date));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 倒計時2
*/
private static void down2() {
int num = 5;
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
if (num <= 0) {
break;
}
}
System.out.println("時間到!");
}
/**
* 倒計時1
*/
public static void down1() {
for (int i = 5; i > 0; i--) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
System.out.println("時間到!");
}
}
線程禮讓
格式
Thread.yield();
代碼:
禮讓不一定成功!
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()+"線程開始執行!");
// 禮讓
Thread.yield();
System.out.println(Thread.currentThread().getName()+"線程停止執行!");
}
}
結果:(禮讓成功)
a線程開始執行!
b線程開始執行!
a線程停止執行!
b線程停止執行!
線程強制執行
相當於插隊
格式:(中斷異常)
Thread.join();
代碼:
public class TestJoin implements Runnable {
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 3; i++) {
System.out.println("線程VIP來啦!");
}
}
public static void main(String[] args) {
TestJoin join = new TestJoin();
// 啓動子線程
Thread t = new Thread(join);
t.start();
// 主線程
for (int i = 0; i < 5; i++) {
if (i == 2) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main" + i);
}
}
}
結果:
main0
main1
線程VIP來啦!
線程VIP來啦!
線程VIP來啦!
main2
main3
main4
觀測線程狀態
格式:
Thread.State state = thread.getState();
代碼:
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("線程終止啦~");
});
// 觀察狀態
Thread.State state = thread.getState();
// NEW
System.out.println(state);
// 觀察啓動後
// 啓動線程
thread.start();
// RUN
System.out.println(thread.getState());
// 只要線程不終止,就一直輸出狀態
while(state != Thread.State.TERMINATED){
Thread.sleep(100);
// 更新線程狀態
state = thread.getState();
// 輸出狀態
System.out.println(state);
}
// 線程中斷或者結束後不能再次啓動
thread.start();
}
}
結果:
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
...
TIMED_WAITING
線程終止啦~
TERMINATED
Exception in thread "main" java.lang.IllegalThreadStateException
at java.base/java.lang.Thread.start(Thread.java:804)
at cn.luis.state.TestState.main(TestState.java:44)
線程優先級
並不是一定按照優先級執行!
格式:
thread.setPriority(1);
thread.setPriority(Thread.MAX_PRIORITY);
代碼:
public class TestPriority {
public static void main(String[] args) {
// 主線程默認優先級
System.out.println(Thread.currentThread().getName() + " -- " + Thread.currentThread().getPriority());
MyPriority priority = new MyPriority();
Thread t1 = new Thread(priority);
Thread t2 = new Thread(priority);
Thread t3 = new Thread(priority);
Thread t4 = new Thread(priority);
Thread t5 = new Thread(priority);
Thread t6 = new Thread(priority);
// 先設置優先級,再啓動
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(6);
t4.start();
t5.setPriority(8);
t5.start();
// 10
t6.setPriority(Thread.MAX_PRIORITY);
t6.start();
}
}
class MyPriority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " -- " + Thread.currentThread().getPriority());
}
}
結果:
main -- 5
Thread-5 -- 10
Thread-4 -- 8
Thread-0 -- 5
Thread-3 -- 6
Thread-2 -- 4
Thread-1 -- 1
8. 守護線程
格式:
thread.setDaemon(true);
代碼;
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
Life life = new Life();
Thread thread = new Thread(god);
// 默認是false表示是用戶線程,正常的線程都是用戶線程
thread.setDaemon(true);
// 上帝守護線程啓動(上帝線程會一直運行,直到life線程掛了,虛擬機不用等待守護線程執行完畢)
thread.start();
// 生命線程啓動
new Thread(life).start();
}
}
/**
* 上帝
*/
class God implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("守護線程...");
}
}
}
/**
* 生命
*/
class Life implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("用戶線程正在活躍...!");
}
System.out.println("用戶線程掛啦!");
}
}
結果:(虛擬機無需等待守護線程停止)
守護線程...
用戶線程正在活躍...!
用戶線程正在活躍...!
用戶線程正在活躍...!
用戶線程正在活躍...!
用戶線程正在活躍...!
用戶線程掛啦!
9. 線程同步
同一進程的多個線程共享同一塊存儲空間,在方便的同時也帶來了訪問衝突問題,爲了保證數據在方法中被訪問時的正確性,在訪問時加入鎖機制
。
當一個線程獲得對象的排它鎖,獨佔資源,其他線程必須等待它用完後釋放鎖,有可能存在以下問題:
- 一個線程持有鎖會導致其他所有需要此鎖的線程掛起;
- 在多線程競爭下,加鎖和釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題;
- 若一個優先級高的線程等待優先級低的線程釋放鎖,會導致優先級倒置沒因其性能問題。
同步方法
同步方法:synchonized默認鎖的對象是this,也就是類本身
案例:買票
class BuyTicket implements Runnable {
private int ticketNum = 5;
// 標誌位
boolean flag = true;
@Override
public void run() {
// 買票
while (flag) {
buy();
}
}
/**
* 同步鎖方法,鎖的是this
*/
private synchronized void buy() {
// 判斷是否有票
if (ticketNum <= 0) {
flag = false;
return;
}
// 模擬延時
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 買票
System.out.println(Thread.currentThread().getName() + "拿到-- " + ticketNum-- + " --票");
}
}
測試類:
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"小明").start();
new Thread(buyTicket,"小李").start();
new Thread(buyTicket,"黃牛").start();
}
}
結果:
小明拿到-- 5 --票
小明拿到-- 4 --票
黃牛拿到-- 3 --票
小李拿到-- 2 --票
黃牛拿到-- 1 --票
同步代碼塊
Obj:同步監視器,也就是代碼塊裏要操作的對象,【可以鎖任何對象,鎖增刪改的對象就好】
案例:銀行取錢
賬戶類
class Account {
/**
* 餘額
*/
int nowMoney;
/**
* 構造方法
*
* @param nowMoney
*/
public Account(int nowMoney) {
this.nowMoney = nowMoney;
}
}
銀行:模擬取款
class Bank extends Thread {
/**
* 賬戶
*/
Account account;
/**
* 取了多少錢
*/
int quMoney;
public Bank(Account account, int quMoney, String name) {
super(name);
this.account = account;
this.quMoney = quMoney;
}
/**
* 同步方法:synchonized默認鎖的對象是this,也就是類本身
* 同步塊: Obj:同步監視器,也就是代碼塊裏要操作的對象,【可以鎖任何對象,鎖增刪改的對象就好】
*/
@Override
public void run() {
synchronized (account) {
// 判斷有沒有錢
if (account.nowMoney - quMoney < 0) {
System.out.println(Thread.currentThread().getName() + "錢不夠!");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 手裏的錢
//System.out.println(this.getName()+Thread.currentThread().getName());
System.out.println(this.getName() + "手裏的錢:" + this.quMoney);
// 卡內餘額 = 餘額 - 取出的錢
account.nowMoney = account.nowMoney - quMoney;
System.out.println("卡內餘額爲:" + account.nowMoney);
}
}
}
測試類:
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100);
Bank you = new Bank(account, 70, "二萌");
Bank her = new Bank(account, 40, "阿雷");
you.start();
her.start();
}
}
結果:
二萌手裏的錢:70
卡內餘額爲:30
阿雷錢不夠
線程不安全的集合:ArrayList
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
// 模擬延時
Thread.sleep(100);
// 主線程
System.out.println(Thread.currentThread().getName()+"線程,集合中存儲了多少個線程:"+list.size());
}
}
結果:
main線程,集合中存儲了多少個線程:10
Lock鎖【JDK1.5】
通過顯式定義同步鎖對象來實現同步,同步鎖使用Lock對象充當
ReentrantLock類實現了Lock接口,可重入鎖,與synchonized有相同的併發性和內存語義。
格式:
private final ReentrantLock lock = new ReentrantLock();
可以顯式加鎖、釋放鎖:【必須在try—catch–finally代碼塊中】
try {
// 加鎖
lock.lock();
...
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解鎖
lock.unlock();
}
代碼:
package cn.luis.lock;
import java.util.concurrent.locks.ReentrantLock;
class BuyTicket implements Runnable {
int ticketNum = 5;
/**
* 定義Lock鎖
* ReentrantLock:可重入鎖
*/
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 加鎖
lock.lock();
if (ticketNum > 0) {
// 模擬延時(睡眠)
Thread.sleep(200);
// 打印線程名稱
System.out.println(Thread.currentThread().getName() + " -- 拿到了第-- " + ticketNum-- + " --張票");
} else {
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解鎖
lock.unlock();
}
}
}
}
測試類:
public class TestLock {
public static void main(String[] args) {
BuyTicket b = new BuyTicket();
new Thread(b).start();
new Thread(b).start();
new Thread(b).start();
}
}
結果:
Thread-0 -- 拿到了第-- 5 --張票
Thread-2 -- 拿到了第-- 4 --張票
Thread-1 -- 拿到了第-- 3 --張票
Thread-0 -- 拿到了第-- 2 --張票
Thread-0 -- 拿到了第-- 1 --張票
死鎖
產生原因
- 多個線程各自佔有一下共享資源,並且互相等待其他線程線程佔有的資源才能運行,導致兩個或多個線程都在等待對方釋放資源,都停止執行的情況
- 某一個同步塊同時擁有兩個以上對象的鎖,就可能發生此問題。
死鎖避免方法
產生死鎖的四個必要條件:
- 互斥條件:一個資源每次只能被一個進程使用
- 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
- 不剝奪條件:進程以獲得資源,再未使用完之前,不能強行剝奪
- 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係。
只要破壞其中的任意一個或多個即可!
synchnoized與Lock的對比
- Lock是顯式鎖(手動開啓和關閉鎖)
- Lock只有代碼塊鎖
- 使用Lock鎖,JVM將花費較少的時間來調度線程,性能更好、擴展型好(提供更多的子類)
- synchonized是隱式鎖,出了作用域自動釋放
- synchonized有代碼塊鎖和方法鎖
優先使用順序
- Lock > 同步代碼塊(已經進入了方法體,分配了相應資源)> 同步方法(在方法體之外)
10. 線程通信 – 生產者和消費者問題
線程間通信方法
- 均是
Object
類的方法,都只能在同步方法或者同步代碼塊中使用,否則會拋出異常:lllegalMonitorStateException
方法名 | 作用 |
---|---|
wait() | 一直等待,直到其他線程通知,與sleep不同,會釋放鎖 |
wait(long timeout) | 指定等待的毫秒數 |
notify() | 喚醒一個處於等待狀態的線程 |
notifyAll() | 喚醒同一對象上所有調用wait方法的線程,優先級高的線程優點調度 |
併發協作模式”生產者消費者模式“
管程法 – 利用緩衝區解決
- 生產者:負責生產數據的模塊(可能是方法、對象、線程、進程)
- 消費者:負責處理數據的模塊(可能是方法、對象、線程、進程)
- 緩衝區:消費者不能直接使用生產者的數據
生產者將生產好的數據放入緩衝區,消費者衝緩衝區中拿出數據
代碼:
信號燈法 – 標誌位
生產者:演員
class Actor extends Thread {
Movie movie;
public Actor(Movie movie) {
this.movie = movie;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
this.movie.play("夏洛特煩惱");
} else {
this.movie.play("唐人街探案");
}
}
}
}
消費者:觀衆
class watcher extends Thread {
Movie movie;
public watcher(Movie movie) {
this.movie = movie;
}
@Override
public void run() {
for (int i = 0; i < 4; i++) {
movie.watch();
}
}
}
產品:電影
class Movie {
/**
* 拍攝電影時,觀衆等待
*/
/**
* 放映電影時,演員等待
*/
/**
* 表演的節目
*/
String movie;
/**
* 標誌位
*/
boolean flag = true;
/**
* 表演方法
*/
public synchronized void play(String movie) {
// 判斷演員等待
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演員表演了:" + movie);
System.out.println("==========");
// 通知觀衆觀看
// 通知喚醒
this.notifyAll();
this.movie = movie;
this.flag = !this.flag;
}
/**
* 觀看
*/
public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("觀衆看了:" + movie);
System.out.println("~~~~~~~~~~");
// 通知演員表演
this.notifyAll();
this.flag = !this.flag;
}
}
測試類:
public class TestPC2 {
public static void main(String[] args) {
Movie movie= new Movie();
new Actor(movie).start();
new watcher(movie).start();
}
}
結果:
演員表演了:夏洛特煩惱
==========
觀衆看了:夏洛特煩惱
~~~~~~~~~~
演員表演了:唐人街探案
==========
觀衆看了:唐人街探案
~~~~~~~~~~
演員表演了:夏洛特煩惱
==========
觀衆看了:夏洛特煩惱
~~~~~~~~~~
演員表演了:唐人街探案
==========
觀衆看了:唐人街探案
~~~~~~~~~~
11. 線程池 【JDK5】
經常創建和銷燬使用量特別大的資源,如併發情況下的線程,對性能影響很大。
解決
- 提前創建好多個線程,放入線程池中,使用時直接獲取,用完放回池中。
JDK5.0起提供了線程池相關API:ExecutorService
和Excecutors
ExecutoeService
方法
ExecutoeService
:真正的線程池接口,常見子類ThreadExecutor
方法
- 執行Runnable,任務/命令,無返回值
void execute(Runnable command);
- 執行Callable接口,任務/命令,有返回值
<T> Future<T> sunbmit(Callable<T> task);
Excecutors
工具類
線程池的工廠類,用於創建並返回不同類型的線程池
優點
- 提高響應速度(減少了創建新線程的時間)
- 降低資源消耗(重複利用線程池中的線程)
- 便於線程管理
- corePoolSize:核心池的大小
- maxmumPooSize:最大線程數
- keeoAliveTime:線程無任務是最多保持多長時間後會終止
使用步驟:
-
創建服務,創建線程池(參數爲線程池大小)
ExecutorService service = Executors.newFixedThreadPool(10);
-
執行線程類,方法:
// runable接口是execute方法 service.execute(new MyThread()); // callable接口是submit方法 Future<Boolean> r1 = service.submit(c1);
-
關閉連接池
service.shutdownNow();
代碼:
線程類
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
創建線程池
package cn.luis.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
// 1.創建服務,創建線程池[參數爲線程池大小]
ExecutorService service = Executors.newFixedThreadPool(10);
// 2.執行Runable接口實現類的線程【execute方法】,callable接口是submit方法
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 3.釋放連接
service.shutdownNow();
}
}
結果:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4