點選這裡檢視更多JavaSE的筆記
如果對多線程很感興趣,可以提前看下進階知識 --> JavaSE學習筆記(十八)JUC并發程式設計
章節目錄
- 多線程
-
-
- 1、Process(程序)與Thread(線程)
- 2、線程、程序、多線程核心概念
- 3、建立線程
-
-
- 3.1、第一種方式:繼承Thread類(重點)
- 3.2、第二種方式:實作Runnable接口(重點)
- 3.3、小結
- 3.4、示例:多個線程同時操作同一個對象
- 3.5、示例:龜兔賽跑
- 3.6、第三種方式:實作Callable接口(了解即可)
-
- 4、靜态代理
- 5、Lamda表達式
- 6、線程停止
- 7、線程休眠 sleep
- 8、線程禮讓 yield
- 9、線程強制執行 join
- 10、線程狀态 Thead.State
- 11、線程優先級 Priority
- 12、守護線程 daemon
- 13、線程同步(多個線程操作同一個資源)
- 14、三大不安全案例
- 15、同步方法及同步塊
- 16、測試JUC安全類型的集合(JUC裡有一個關于list安全的集合:CopyOnWriteArrayList)
- 17、死鎖
- 18、Lock(鎖)
- 19、生産者消費者問題
-
-
- 19.1、解決方式1
- 19.2、解決方式2
-
- 20、線程池
- 總結
-
多線程
1、Process(程序)與Thread(線程)
- 說起程序,就不得不說下程式。程式是指令和資料的有序集合,其本身沒有任何運作的含義,是一個靜态的概念
- 而程序則是執行程式的一次執行過程,它是一個動态的概念。是系統資源配置設定的機關
- 通常在一個程序中可以包含若幹個線程,當然一個程序中至少有一個線程,不然沒有存在的意義。線程是CPU排程和執行的機關
- 注意:
- 很多多線程是模拟出來的,真正的多線程是指有多個CPU,即多核,如伺服器。如果是模拟出來的多線程,即在一個CPU的情況下,在同一個時間點,CPU隻能執行一個代碼,因為切換的很快,是以就有同時執行的錯覺。
2、線程、程序、多線程核心概念
- 線程就是獨立的執行路徑
- 在程式運作時,即使沒有自己建立線程,背景也會有多個線程,如主線程,gc線程
- main()稱之為主線程,為系統的入口,用于執行整個程式
- 在一個程序中,如果開辟了多個線程,線程的運作由排程器安排排程,排程器是與作業系統緊密相關的,先後順序是不能人為幹預的
- 對同一份資源操作時,會存在資源搶奪的問題,需要加入并發控制
- 線程會帶來額外的開銷,如cpu排程時間,并發控制開銷
- 每個線程在自己的工作記憶體互動,記憶體控制不當會造成資料不一緻
3、建立線程
3.1、第一種方式:繼承Thread類(重點)
package com.object.thread;
//建立線程方式一:繼承Thread類,重寫run()方法,調用start開啟線程
//總結:注意,線程開啟不一定立即執行,由CPU排程執行
public class ThreadDemo01 extends Thread{
@Override
public void run() {
//run方法線程體
for (int i = 0; i < 20; i++) {
System.out.println("我在看代碼--" + i);
}
}
public static void main(String[] args) {
//main線程,主線程
//建立一個線程對象
ThreadDemo01 td = new ThreadDemo01();
//td.run(); 正常調用run方法的話是按代碼順序執行
//調用start()方法開啟線程
td.start(); //使用start開啟線程将會多個線程交替執行
for (int i = 0; i < 1000; i++) {
System.out.println("我在寫代碼---" + i);
}
}
}
示例:網圖下載下傳(這裡用到了一個java工具類庫commons-io)
package com.object.thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//練習Thread,實作多線程同步下載下傳圖檔
public class ThreadDemo02 extends Thread{
private String url;
private String name;
public ThreadDemo02(String url, String name) {
this.url = url;
this.name = name;
}
//下載下傳圖檔線程執行體
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("成功下載下傳檔案:" + name);
}
public static void main(String[] args) {
ThreadDemo02 t1 = new ThreadDemo02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg-blog.csdnimg.cn%2F20201130170324506.png%3Fx-oss-process%3Dimage%2Fwatermark%2Ctype_ZmFuZ3poZW5naGVpdGk%2Cshadow_10%2Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDE3NjM5Mw%3D%3D%2Csize_16%2Ccolor_FFFFFF%2Ct_70&refer=http%3A%2F%2Fimg-blog.csdnimg.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659801398&t=295178de5fdf10b068707c41fa1a99c2", "檔案1.jpg");
ThreadDemo02 t2 = new ThreadDemo02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg-blog.csdnimg.cn%2F20201130170324506.png%3Fx-oss-process%3Dimage%2Fwatermark%2Ctype_ZmFuZ3poZW5naGVpdGk%2Cshadow_10%2Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDE3NjM5Mw%3D%3D%2Csize_16%2Ccolor_FFFFFF%2Ct_70&refer=http%3A%2F%2Fimg-blog.csdnimg.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659801398&t=295178de5fdf10b068707c41fa1a99c2", "檔案2.jpg");
ThreadDemo02 t3 = new ThreadDemo02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg-blog.csdnimg.cn%2F20201130170324506.png%3Fx-oss-process%3Dimage%2Fwatermark%2Ctype_ZmFuZ3poZW5naGVpdGk%2Cshadow_10%2Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDE3NjM5Mw%3D%3D%2Csize_16%2Ccolor_FFFFFF%2Ct_70&refer=http%3A%2F%2Fimg-blog.csdnimg.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659801398&t=295178de5fdf10b068707c41fa1a99c2", "檔案3.jpg");
t1.start();
t2.start();
t3.start();
//這三個線程看似是t1\t2\t3的順序運作,實際上并不是按順序的,随機運作的
}
}
//下載下傳器
class WebDownloader{
//下載下傳方法
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異常,downloader方法出現問題");
}
}
}
3.2、第二種方式:實作Runnable接口(重點)
package com.object.runnable;
//建立線程方式2:實作runnable接口,重寫run方法,執行線程需要丢入runnable接口實作類,調用start方法
public class RunnableDemo01 implements Runnable{
@Override
public void run() {
//run方法線程體
for (int i = 0; i < 20; i++) {
System.out.println("我在看代碼--" + i);
}
}
public static void main(String[] args) {
//建立Runnable接口的實作類對象
RunnableDemo01 runnableDemo01 = new RunnableDemo01();
//建立線程對象,通過線程對象來開啟我們的線程、代理
//Thread thread = new Thread(runnableDemo01);
//thread.start();
new Thread(runnableDemo01).start();
for (int i = 0; i < 1000; i++) {
System.out.println("我在寫代碼---" + i);
}
}
}
示例:網圖下載下傳(這裡用到了一個java工具類庫commons-io)
package com.object.runnable;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class RunnableDemo02 implements Runnable{
private String url;
private String name;
public RunnableDemo02(String url, String name) {
this.url = url;
this.name = name;
}
//下載下傳圖檔線程執行體
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("成功下載下傳檔案:" + name);
}
public static void main(String[] args) {
RunnableDemo02 t1 = new RunnableDemo02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg-blog.csdnimg.cn%2F20201130170324506.png%3Fx-oss-process%3Dimage%2Fwatermark%2Ctype_ZmFuZ3poZW5naGVpdGk%2Cshadow_10%2Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDE3NjM5Mw%3D%3D%2Csize_16%2Ccolor_FFFFFF%2Ct_70&refer=http%3A%2F%2Fimg-blog.csdnimg.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659801398&t=295178de5fdf10b068707c41fa1a99c2", "檔案1.jpg");
RunnableDemo02 t2 = new RunnableDemo02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg-blog.csdnimg.cn%2F20201130170324506.png%3Fx-oss-process%3Dimage%2Fwatermark%2Ctype_ZmFuZ3poZW5naGVpdGk%2Cshadow_10%2Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDE3NjM5Mw%3D%3D%2Csize_16%2Ccolor_FFFFFF%2Ct_70&refer=http%3A%2F%2Fimg-blog.csdnimg.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659801398&t=295178de5fdf10b068707c41fa1a99c2", "檔案2.jpg");
RunnableDemo02 t3 = new RunnableDemo02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg-blog.csdnimg.cn%2F20201130170324506.png%3Fx-oss-process%3Dimage%2Fwatermark%2Ctype_ZmFuZ3poZW5naGVpdGk%2Cshadow_10%2Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDE3NjM5Mw%3D%3D%2Csize_16%2Ccolor_FFFFFF%2Ct_70&refer=http%3A%2F%2Fimg-blog.csdnimg.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659801398&t=295178de5fdf10b068707c41fa1a99c2", "檔案3.jpg");
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
//這三個線程看似是t1\t2\t3的順序運作,實際上并不是按順序的,随機運作的
}
}
//下載下傳器
class WebDownloader{
//下載下傳方法
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異常,downloader方法出現問題");
}
}
}
3.3、小結
- 繼承Thread類
- 子類繼承Thread類具備多線程能力
- 啟動線程:子類對象.start()
- 不建議使用:存在OOP單繼承局限性
- 實作Runnable接口
- 實作接口Runnable具有多線程能力
- 啟動線程:傳入目标對象+Thread對象.start()
- 推薦使用:可以避免單繼承局限性,靈活友善,友善同一個對象被多個線程使用
//一份資源
StartThread station = new StartThread4();
//多個代理
new Thread(station, "小明").start();
new Thread(station, "小紅").start();
new Thread(station, "小吳").start();
3.4、示例:多個線程同時操作同一個對象
package com.object.runnable;
//多個線程同時操作同一個對象
//買火車票的例子
//發現問題:多個線程操作同一個資源的情況下,線程不安全,資料紊亂。這裡就涉及到線程并發的問題
public class RunnableDemo03 implements Runnable{
//票數
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums <= 0) {
break; //沒有票時結束
}
try {
Thread.sleep(200); //通過線程睡眠的方式确實能減少搶到同張票的次數,但還是沒能解決
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--> 拿到了第" + ticketNums-- + "張票");
//Thread裡的currentTread方法能擷取這個線程的資訊,比如姓名
}
}
public static void main(String[] args) {
RunnableDemo03 ticket = new RunnableDemo03();
new Thread(ticket, "小吳").start(); //這裡給線程傳了一個名字
new Thread(ticket, "老師").start();
new Thread(ticket, "黃牛黨").start();
}
}
3.5、示例:龜兔賽跑
package com.object.runnable;
//模拟龜兔賽跑
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(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//擷取比賽是否結束情況
boolean flag = gameOver(i);
//如果比賽結束,就停止程式
if (flag) {
break;
}
System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "步");
}
}
//判斷是否完成比賽
private boolean gameOver(int steps){
//判斷是否存在勝利者
if (winner != null) { //已存在勝利者
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();
}
}
3.6、第三種方式:實作Callable接口(了解即可)
1、實作Callable接口,需要傳回值類型
2、重寫call方法,需要抛出異常
3、建立目标對象
4、建立執行服務:ExecutorService ser = Executors.newFixedThreadPool(1);
5、送出執行:Futureresult1 = ser.submit(t1);
6、擷取結果:boolean r1 = result1.get();
7、關閉服務:ser.shutdownNow();
示例:網圖下載下傳(這裡用到了一個java工具類庫commons-io)
package com.object.callable;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
//線程建立方式三:實作Callable接口
/*
Callable的好處:
1、可以定義傳回值
2、可以抛出異常
*/
public class CallableDemo01 implements Callable<Boolean> {
private String url;
private String name;
public CallableDemo01(String url, String name) {
this.url = url;
this.name = name;
}
//下載下傳圖檔線程執行體
@Override
public Boolean call() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("成功下載下傳檔案:" + name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableDemo01 c1 = new CallableDemo01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg-blog.csdnimg.cn%2F20201130170324506.png%3Fx-oss-process%3Dimage%2Fwatermark%2Ctype_ZmFuZ3poZW5naGVpdGk%2Cshadow_10%2Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDE3NjM5Mw%3D%3D%2Csize_16%2Ccolor_FFFFFF%2Ct_70&refer=http%3A%2F%2Fimg-blog.csdnimg.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659801398&t=295178de5fdf10b068707c41fa1a99c2", "檔案1.jpg");
CallableDemo01 c2 = new CallableDemo01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg-blog.csdnimg.cn%2F20201130170324506.png%3Fx-oss-process%3Dimage%2Fwatermark%2Ctype_ZmFuZ3poZW5naGVpdGk%2Cshadow_10%2Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDE3NjM5Mw%3D%3D%2Csize_16%2Ccolor_FFFFFF%2Ct_70&refer=http%3A%2F%2Fimg-blog.csdnimg.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659801398&t=295178de5fdf10b068707c41fa1a99c2", "檔案2.jpg");
CallableDemo01 c3 = new CallableDemo01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg-blog.csdnimg.cn%2F20201130170324506.png%3Fx-oss-process%3Dimage%2Fwatermark%2Ctype_ZmFuZ3poZW5naGVpdGk%2Cshadow_10%2Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDE3NjM5Mw%3D%3D%2Csize_16%2Ccolor_FFFFFF%2Ct_70&refer=http%3A%2F%2Fimg-blog.csdnimg.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659801398&t=295178de5fdf10b068707c41fa1a99c2", "檔案3.jpg");
//建立執行服務
ExecutorService ser = Executors.newFixedThreadPool(3); //建立一個池子,裝線程,這裡需要輸入一下線程數
//送出執行
Future<Boolean> r1 = ser.submit(c1);
Future<Boolean> r2 = ser.submit(c2);
Future<Boolean> r3 = ser.submit(c3);
//擷取結果
System.out.println(r1.get());
System.out.println(r2.get());
System.out.println(r3.get());
//關閉服務
ser.shutdownNow();
}
}
//下載下傳器
class WebDownloader{
//下載下傳方法
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異常,downloader方法出現問題");
}
}
}
4、靜态代理
package com.object.thread;
/*
靜态代理模式總結:
真實對象和代理對象都要實作同一個接口
代理對象要代理真實角色
好處:
代理對象可以做很多真實對象做不了的事情
真實對象專注做自己的事情
*/
public class ThreadDemo03 {
public static void main(String[] args) {
new WeddingCompany(new You()).HappyMarry();
}
}
interface Marry{
//人間四大喜事
//久旱逢甘霖
//他鄉遇故知
//洞房花燭夜
//金榜題名時
void HappyMarry();
}
//真實角色,你去結婚
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("我要結婚啦");
}
}
//代理角色,幫助你結婚
class WeddingCompany implements Marry{
//代理誰?-->真實目标角色
private Marry target;
public WeddingCompany(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("結婚之後,收尾款");
}
}
5、Lamda表達式
示例1:
package com.lambda;
public class LambdaDemo01 {
//3、靜态内部類
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda2");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
like = new Like2();
like.lambda();
//4、局部内部類
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda3");
}
}
like = new Like3();
like.lambda();
//5、匿名内部類,沒有類的名稱,必須借助接口或者父類
like = new ILike(){
@Override
public void lambda() {
System.out.println("I lile lambda4");
}
};
like.lambda();
//6、使用lambda表達式簡化
like = ()->{
System.out.println("I like lambda5");
};
like.lambda();
}
}
//1、定義一個函數式接口
interface ILike{
void lambda();
}
//2、實作類
class Like implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda!");
}
}
示例2:
package com.lambda;
public class LambdaDemo02 {
public static void main(String[] args) {
//1、lambda表達式簡化
// Ilove love = (int a, int b)->{
// System.out.println("I love you" + a);
// };
//簡化1:去掉參數類型
// love = (a, b) ->{
// System.out.println("I love you" + a);
// };
//簡化2:去掉括号(隻有一個參數的時候才能去掉小括号)
// love = a -> {
// System.out.println("I love you" + a);
// };
//簡化3:去掉花括号
Ilove love = (a, b) -> System.out.println("I love you" + a + b);
//總結:
//lambda表達式隻能在隻有一行代碼的情況下才能簡化成為一行,如果有多行,那麼就用代碼塊包裹
//前提是接口為函數式接口
//多個參數也可以去掉參數類型,要去掉就都去掉
love.love(521, 50);
}
}
interface Ilove{
void love(int a, int b);
// void love2(int b); 接口内隻能有一個方法,不然lambda表達式會儲存
}
6、線程停止
package com.thread;
/*
測試stop
1、建議線程正常停止--->利用次數,不建議死循環
2、建議使用标志位--->設定一個标志位
3、不要使用stop或者destroy等過時或者JDK不建議使用的方法
*/
public class StopDemo01 implements Runnable{
//1、線程中定義線程體使用的辨別
private boolean flag = true;
@Override
public void run() {
int i = 0;
//2、線程體使用該辨別
while (flag) {
System.out.println("run ... Thread" + i++);
}
}
//3、設定一個公開的方法停止線程,轉換标志位
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
StopDemo01 stopDemo01 = new StopDemo01();
new Thread(stopDemo01).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main" + i);
if (i == 900) {
//4、調用stop方法切換标志位,讓線程停止
stopDemo01.stop();
System.out.println("線程在此停止");
}
}
}
}
7、線程休眠 sleep
- sleep(時間)指定目前線程阻塞的毫秒數
- sleep存在異常InterruptedException
- sleep時間達到後線程進入就緒狀态
- sleep可以模拟網絡延時,倒計時等
- 每一個對象都有一個鎖,sleep不會釋放鎖
示例:搶票休眠事件,放大問題
package com.thread;
//模拟網絡延時:放大問題的發生性(出現對個人同時搶到一張票的情況)
public class SleepDemo01 implements Runnable{
//票數
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums <= 0) {
break; //沒有票時結束
}
try {
Thread.sleep(200); //通過線程睡眠的方式确實能減少搶到同張票的次數,但還是沒能解決
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--> 拿到了第" + ticketNums-- + "張票");
//Thread裡的currentTread方法能擷取這個線程的資訊,比如姓名
}
}
public static void main(String[] args) {
SleepDemo01 ticket = new SleepDemo01();
new Thread(ticket, "小吳").start(); //這裡給線程傳了一個名字
new Thread(ticket, "老師").start();
new Thread(ticket, "黃牛黨").start();
}
}
示例:使用sleep實作倒計時
package com.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SleepDemo02 {
public static void main(String[] args) throws InterruptedException {
//tenDown(); 十秒倒計時
//列印倒計時
Date date = new Date(System.currentTimeMillis());//擷取系統目前時間
while (true) {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));//轉換時間格式
date = new Date(System.currentTimeMillis());//更新目前時間
}
}
//模拟倒計時
public static void tenDown() throws InterruptedException {
int num = 10;
while (true) {
Thread.sleep(1000);
System.out.println(num--);
if (num == 0) {
break;
}
}
}
}
8、線程禮讓 yield
- 禮讓線程,讓目前正在執行的線程暫停,但不阻塞
- 将線程從運作狀态轉為就緒狀态
- 讓cpu重新排程,禮讓不一定成功!看CPU心情
package com.thread;
//測試禮讓線程
//禮讓不一定成功,看CPU心情
public class YieldDemo01 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "開始執行");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "結束執行");
}
public static void main(String[] args) {
YieldDemo01 yield = new YieldDemo01();
new Thread(yield, "a").start();
new Thread(yield, "b").start();
}
}
9、線程強制執行 join
- jion合并線程,待此線程執行完畢後,再執行其他線程,其他線程阻塞
- 可以想象成插隊
package com.thread;
//測試join方法
//可以了解為插隊
public class JoinDemo01 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("vip程序來了" + i);
}
}
public static void main(String[] args) throws InterruptedException {
//啟動我們的線程
JoinDemo01 joinDemo01 = new JoinDemo01();
Thread thread = new Thread(joinDemo01);
thread.start();
//主線程
for (int i = 0; i < 200; i++) {
System.out.println("main" + i);
if (i == 100) {
thread.join();//main線程阻塞
}
}
}
}
10、線程狀态 Thead.State
- NEW 尚未啟動的線程處于此狀态
- RUNNABLE 在java虛拟機中執行的線程處于此狀态
- BLOCKED 被阻塞等待螢幕鎖定的線程處于此狀态
- WAITING 正在等待另一個線程執行特定動作的線程處于此狀态
- TIMED_WAITING 正在等待另一個線程執行動作達到指定等待時間的線程處于此狀态
- TERMINATED 已退出的線程處于此狀态
一個線程可以在給定時間點處于一個狀态。這些狀态是不反映任何作業系統線程狀态的虛拟機狀态
package com.thread;
public class StateDemo01 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread( ()->{
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("/");
});
//觀察狀态
Thread.State state = thread.getState();
System.out.println(state); //NEW
//觀察啟動後
thread.start(); //啟動線程
state = thread.getState();
System.out.println(state); //RUNNABLE
while (state != Thread.State.TERMINATED) { //隻要線程不終止,就一直輸出狀态
Thread.sleep(100); //增加延遲不讓他輸出那麼快
state = thread.getState(); //更新線程狀态
System.out.println(state); //輸出狀态
}
//thread.start(); 死亡之後的線程無法再啟動
}
}
11、線程優先級 Priority
- java提供一個線程排程器來監控程式中啟動後進入就緒狀态的所有線程,線程排程器按照優先級決定應該排程哪個線程來執行
- 線程的優先級用數字表示,範圍從1~10
- Thread.MIN_PRIORITY = 1
- Thread.MAX_PRIORITY = 10
- Thread.NORM_PRIORITY = 5
- 使用以下方式改變或擷取優先級
- getPriority() setPriority(int xxx)
- 優先級的設定建議在start()排程前
- 優先級低隻是意味着獲得排程的機率低,并不是優先級低就不會被調用了,這都是看CPU的排程
package com.thread;
public class PriorityDemo01 {
public static void main(String[] args) {
//主線程預設優先級
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);
Thread t7 = new Thread(myPriority);
//先設定優先級,再啟動
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(5);
t3.start();
//t4.setPriority(-1); //給的參數必須在1~10,超出這個範圍就會報錯IllegalArgumentException
t4.setPriority(Thread.MAX_PRIORITY); //Thread.MAX_PRIORITY是10
t4.start();
t5.setPriority(Thread.MIN_PRIORITY); //Thread.MIN_PRIORITY是1
t5.start();
t6.setPriority(Thread.NORM_PRIORITY); //Thread.NORM_PRIORITY是5
t6.start();
t7.setPriority(3);
t7.start();
}
static class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
}
}
}
12、守護線程 daemon
- 線程分為使用者線程和守護線程
- 虛拟機必須確定使用者線程執行完畢;使用者線程比如:main()
- 虛拟機不用等待守護線程執行完畢;守護線程比如:gc()
- 如:背景記錄記錄檔、監控記憶體、垃圾回收等
package com.thread;
//測試守護程序
//上帝守護你
public class DaemonDemo01 {
public static void main(String[] args) {
God god = new God();
Thread threadGod = new Thread(god);
threadGod.setDaemon(true); //預設是false表示使用者線程,正常的線程都是使用者線程...修改為true表示更改為守護線程
threadGod.start(); //上帝線程啟動 守護線程啟動...
//JVM無需等待守護線程執行完成就可以關閉
new Thread(new You()).start(); //你線程啟動 使用者線程啟動...
}
}
//你
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 35; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我還活着呀" + i);
}
System.out.println("======Goodbye World!========");
}
}
//上帝
class God implements Runnable{
@Override
public void run() {
System.out.println("上帝守護着你");
}
}
13、線程同步(多個線程操作同一個資源)
- 并發:同一個對象被多個線程同時操作
- 處理多線程問題時,多個線程通路同一個對象,并且某些線程還想修改這個對象。這時候我們就需要線程同步,線程同步其實就是一個等待機制,多個需要同時通路此對象的線程進入這個對象的等待池形成隊列,等待前面線程使用完畢,下一個線程再使用。
- 線程同步形成條件:隊列 + 鎖
- 由于同一程序的多個線程共享同一塊存儲空間,在帶來友善的同僚,也帶來了通路沖突的問題,為了保障資料在方法中被通路時的正确性,在通路中加入鎖機制synchronized,當一個線程獲得對象的排它鎖,獨占資源,其他線程必須等待,使用後釋放鎖即可,存在以下問題:
- 一個線程持有鎖會導緻其他所需要此鎖的線程挂起
- 在多線程競争下,加鎖,釋放鎖會導緻比較多的上下文切換 和 排程延時,引起性能問題
- 如果一個優先級高的線程等待一個優先級低的線程釋放鎖 會導緻優先級倒置,引起性能問題
14、三大不安全案例
- 案例一:搶票
package com.unsafe;
//不安全的買票
//線程不安全,存在同時搶到一張票和負票的問題
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();
}
}
class BuyTicket implements Runnable{
private int ticket = 10; //票數
boolean flag = true; //外部停止
@Override
public void run() {
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//搶票
private void buy() throws InterruptedException {
//判斷是否有票
if (ticket <= 0) {
flag = false;
return;
}
//模拟搶票延時,放大問題發生可能性
Thread.sleep(3000);
//搶票
System.out.println(Thread.currentThread().getName() + "搶到第" + ticket-- + "張票");
}
}
- 案例二:取錢
package com.unsafe;
//模拟銀行取錢
//存在餘額負數情況,沒有餘額了還能取錢
public class UnsafeBank {
public static void main(String[] args) {
Account getmarriedFund = new Account(1000000, "結婚基金");
new Bank(getmarriedFund, 500000, "你").start();
new Bank(getmarriedFund, 1000000, "girlFriend").start();
}
}
//賬戶
class Account{
private int money; //賬戶金額
private String name; //賬戶名稱
public Account(int money, String name) {
this.money = money;
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//模拟銀行取款
class Bank extends Thread {
private Account account; //賬戶
private int nowMoney; //手上有多少錢
private int drawingMoney; //取多少錢
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public int getNowMoney() {
return nowMoney;
}
public void setNowMoney(int nowMoney) {
this.nowMoney = nowMoney;
}
public int getDrawingMoney() {
return drawingMoney;
}
public void setDrawingMoney(int drawingMoney) {
this.drawingMoney = drawingMoney;
}
public Bank(Account account, int drawingMoney, String name) {
this.account = account;
this.drawingMoney = drawingMoney;
super.setName(name);
}
//取錢
@Override
public void run() {
try {
DrawMoney();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取錢方法
private void DrawMoney() throws InterruptedException {
if ( (account.getMoney() - getDrawingMoney()) < 0) {
System.out.println(Thread.currentThread().getName() + "取錢失敗" + account.getName() + "賬戶餘額不足");
return;
}
//模拟延遲:放大問題發生性
Thread.sleep(3000);
//餘額變換 = 賬戶餘額-要取的錢
account.setMoney(account.getMoney() - getDrawingMoney());
//手上有多少錢
setNowMoney(getNowMoney() + getDrawingMoney());
System.out.println(Thread.currentThread().getName() + "取錢成功,目前" + account.getName() + "剩餘餘額:" + account.getMoney());
System.out.println(Thread.currentThread().getName() + "手上拿着" + getNowMoney() + "元錢!");
System.out.println(account.getName() + "目前賬戶餘額" + account.getMoney());
}
}
- 案例三:線程不安全的集合
package com.unsafe;
import java.util.ArrayList;
//線程不安全的集合
public class UnsafeList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
new Thread( ()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
15、同步方法及同步塊
- 由于我們可以通過private關鍵字來保證資料對象隻能被方法通路,是以我們隻需要針對方法提出一套機制,這套機制就是synchronized關鍵字,它包括兩種用法:synchronized方法和synchronized塊
- 同步方法:
- synchronized方法控制對“對象”的通路,每個對象對應一把鎖,每個synchronized方法都必須獲得調用該方法的對象的鎖才能執行,否則線程會阻塞,方法一旦執行,就會獨占該鎖,直到該方法傳回才釋放鎖,後面被阻塞的線程才能獲得這個鎖,繼續執行
- 缺陷:若将一個大的方法申明為synchronized将會影響效率
- 同步塊:synchronized(Obj){}
- Obj稱之為同步螢幕
- Obj可以是任何對象,但是推薦使用共享資源作為同步螢幕
- 同步方法中無需指定同步螢幕,因為同步方法的同步螢幕就是this,就是這個對象本身,或者是class【反射中講解】
- 同步螢幕的執行過程
- 1、第一個線程通路,鎖定同步螢幕,執行其中的代碼
- 2、第二個線程通路,發現同步螢幕被鎖定,無法通路
- 3、第一個線程通路完畢,解鎖同步螢幕
- 4、第二個線程通路,發現同步螢幕沒有鎖,然後鎖定并通路
- 案例一:搶票(增加同步鎖)
package com.unsafe;
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();
}
}
class BuyTicket implements Runnable{
private int ticket = 10; //票數
boolean flag = true; //外部停止
@Override
public void run() {
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//搶票
//synchronized同步方法,鎖的是this
private synchronized void buy() throws InterruptedException {
//判斷是否有票
if (ticket <= 0) {
flag = false;
return;
}
//模拟搶票延時,放大問題發生可能性
Thread.sleep(3000);
//搶票
System.out.println(Thread.currentThread().getName() + "搶到第" + ticket-- + "張票");
}
}
- 案例二:取錢(增加同步鎖)
package com.unsafe;
//模拟銀行取錢
public class UnsafeBank {
public static void main(String[] args) {
Account getmarriedFund = new Account(10000000, "結婚基金");
new Bank(getmarriedFund, 500000, "你").start();
new Bank(getmarriedFund, 1000000, "girlFriend").start();
}
}
//賬戶
class Account{
private int money; //賬戶金額
private String name; //賬戶名稱
public Account(int money, String name) {
this.money = money;
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//模拟銀行取款
class Bank extends Thread {
private Account account; //賬戶
private int nowMoney; //手上有多少錢
private int drawingMoney; //取多少錢
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public int getNowMoney() {
return nowMoney;
}
public void setNowMoney(int nowMoney) {
this.nowMoney = nowMoney;
}
public int getDrawingMoney() {
return drawingMoney;
}
public void setDrawingMoney(int drawingMoney) {
this.drawingMoney = drawingMoney;
}
public Bank(Account account, int drawingMoney, String name) {
this.account = account;
this.drawingMoney = drawingMoney;
super.setName(name);
}
//取錢
@Override
public void run() {
/*
使用synchronized塊來鎖
鎖的對象是變化的量,需要增删改的對象
預設在方法前面添加synchronized鎖的對象是this,顯然在現在這種情況鎖this并不能保障資料安全,隻能使用synchronized塊來自定義鎖account
*/
synchronized (account) {
try {
DrawMoney();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//取錢方法
private void DrawMoney() throws InterruptedException {
if ( (account.getMoney() - getDrawingMoney()) < 0) {
System.out.println(Thread.currentThread().getName() + "取錢失敗" + account.getName() + "賬戶餘額不足");
return;
}
//模拟延遲:放大問題發生性
Thread.sleep(3000);
//餘額變換 = 賬戶餘額-要取的錢
account.setMoney(account.getMoney() - getDrawingMoney());
//手上有多少錢
setNowMoney(getNowMoney() + getDrawingMoney());
System.out.println(Thread.currentThread().getName() + "取錢成功,目前" + account.getName() + "剩餘餘額:" + account.getMoney());
System.out.println(Thread.currentThread().getName() + "手上拿着" + getNowMoney() + "元錢!");
System.out.println(account.getName() + "目前賬戶餘額" + account.getMoney());
}
}
- 案例三:增加同步鎖實作線程安全的集合
package com.unsafe;
import java.util.ArrayList;
//增加同步鎖實作線程安全的集合
public class UnsafeList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
new Thread( ()->{
//在這裡把變化的量,list對象加一個同步鎖
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
16、測試JUC安全類型的集合(JUC裡有一個關于list安全的集合:CopyOnWriteArrayList)
package com.thread;
import java.util.concurrent.CopyOnWriteArrayList;
//測試JUC安全類型的集合
public class JUCArrayList {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread().start();
list.add(Thread.currentThread().getName());
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
17、死鎖
- 多個線程各自占有一些共享資源,并且互相等待其他線程占有的資源才能運作,而導緻兩個或多個線程都在等待對方釋放資源,都停止執行的情形,某一個同步塊同時擁有“兩個以上對象的鎖”時,就可能會發生“死鎖”的問題。
package com.thread;
//死鎖:多個線程互相抱着對方需要的資源,然後形成僵持
public class LockDemo01 {
public static void main(String[] args) {
new Thread(new Makeup(0, "灰姑娘")).start();
new Thread(new Makeup(1, "白雪公主")).start();
}
}
//口紅
class Lipstick{
}
//鏡子
class Mirror{
}
class Makeup extends Thread{
//需要的資源隻有一份,用static來保證隻有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
//選擇
private int choice;
//使用的人
private 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();
}
}
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {
System.out.println(this.girlName + "搶到了口紅");
Thread.sleep(1000);
//此部位鎖内又拿鎖,會與下面互強鎖形成僵持,程式卡死
// synchronized (mirror) {
// System.out.println(this.girlName + "搶到了鏡子");
// }
}
synchronized (mirror) {
System.out.println(this.girlName + "搶到了鏡子");
}
} else {
synchronized (mirror) {
System.out.println(this.girlName + "搶到了鏡子");
Thread.sleep(2000);
// synchronized (lipstick) {
// System.out.println(this.girlName + "搶到了口紅");
// }
}
synchronized (lipstick) {
System.out.println(this.girlName + "搶到了口紅");
}
}
}
}
- 産生死鎖的四個必要條件:
- 互斥條件:一個資源每次隻能被一個程序使用
- 請求與保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不放
- 不剝奪條件:程序已獲得的資源,在未使用完之前,不能強行剝奪
- 循環等待條件:若幹程序之間形成一種頭尾相接的循環等待資源關系
- 上面列出了死鎖的四個必要條件,我們隻要想辦法破其中的任意一個或多個條件就可以避免死鎖發生
18、Lock(鎖)
- 從JDK5.0開始,Java提供了更強大的線程同步機制,通過顯式定義同步鎖對象來實作同步。同步鎖使用Lock對象充當。
- java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行通路的工具。鎖提供了對共享資源的獨占通路,每次隻能有一個線程對Lock對象加鎖,線程開始通路共享資源之前應先獲得Lock對象
- ReentrantLock類實作了Lock,它擁有與synchronized相同的并發性和記憶體語義,在實作線程安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖、釋放鎖。
示例:搶票
package com.thread;
import java.util.concurrent.locks.ReentrantLock;
//測試Lock鎖
//買票例子
public class LockDemo02 {
public static void main(String[] args) {
ticket ticket = new ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
}
class ticket implements Runnable{
int ticketNums = 10;
//線程不安全
// @Override
// public void run() {
// while (ticketNums > 0) {
// System.out.println(ticketNums--);
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
//定義Lock鎖
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (ticketNums > 0){
try{
lock.lock(); //加鎖
System.out.println(ticketNums--);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); //解鎖
}
}
}
}
總結
class A{
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
lock.lock()
try{
//需要保證線程安全的代碼
} finally{
lock.unlock();
//如果同步代碼有異常,要将unlock()寫入finally語句塊
}
}
}
- synchronized與Lock的對比
- Lock是顯式鎖(手動開啟或關閉鎖,别忘記關閉鎖)synchronized是隐式鎖,出了作用域自動釋放
- Lock隻有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
- 使用Lock鎖,JVM将花費較少的時間來排程線程,性能更好。并且具有更好的擴充性(提供更多的子類)
- 優先使用順序:
- Lock > 同步代碼塊(已經進入了方法體,配置設定了相應資源)> 同步方法(在方法體之外)
19、生産者消費者問題
- 應用場景:生産者和消費者問題
- 假設倉庫中隻能存放一件産品,生産者将生産出來的産品放入倉庫,消費者将倉庫中産品取走消費
- 如果倉庫中沒有産品,則生産者将産品放入倉庫,否則停止生産并等待,直到倉庫中的産品被消費者取走為止
- 如果倉庫中放有産品,則消費者可以将産品取走消費,否則停止消費并等待,直到倉庫中再次放入産品為止
- 這是一個線程同步問題,生産者和消費者共享同一個資源,并且生産者和消費者之間互相依賴,互為條件:
- 對于生産者,沒有生産産品之前,要通知消費者等待。而生産了産品之後,又需要馬上通知消費者消費
- 對于消費者,在消費之後,要通知生産者已經結束消費,需要生産新的産品以供消費
- 在生産者消費者問題中,僅有synchronized是不夠的
- synchronized可阻止并發更新同一個共享資源,實作了同步
- synchronized不能用來實作不同線程之間的消息傳遞(通信)
- Java提供了幾個方法解決線程之間的通信問題
方法名 | 作用 |
---|---|
wait() | 表示線程一直等待,直到其他線程通知,與sleep不同,會釋放鎖 |
wait(long timeout) | 指定等待的毫秒數 |
notify() | 喚醒一個處于等待狀态的線程 |
notifyAll() | 喚醒同一個對象上所有調用wait()方法的線程,優先級别高的線程優先排程 |
- 注意:均是Object類的方法,都隻能在同步方法或同步代碼塊中使用,否則會抛出LIIegalMonitorStateException
19.1、解決方式1
- 并發協作模型“生産者/消費者模式" --> 管程法
- 生産者:負責生産資料的子產品(可能是方法、對象、線程、程序)
- 消費者:負責處理資料的子產品(可能是方法、對象、線程、程序)
- 緩沖區:消費者不能直接使用生産者的資料,他們之間有一個“緩沖區”
- 生産者将生産好的資料放入緩沖區,消費者從緩沖區拿出資料
示例:買賣雞肉
package com.thread;
//測試:生産者消費者模型 --> 利用緩沖區解決:管程法
//生産者,消費者,産品,緩沖區
public class PCDemo01 {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生産者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container = container;
}
//生産
@Override
public void run() {
for (int i = 1; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生産了" + i + "隻雞");
}
}
}
//消費者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
//消費
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消費了-->" + container.pop().id + "隻雞");
}
}
}
//産品
class Chicken{
int id;//産品編号
public Chicken(int id) {
this.id = id;
}
}
//緩沖區
class SynContainer{
//需要一個容器大小
Chicken[] chickens = new Chicken[10];
//計數器
int count = 0;
//生産者放入産品
public synchronized void push(Chicken chicken){
//如果容器滿了,就需要等待消費者消費
if (count == chickens.length){
//通知消費者消費,生産等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果沒有滿,我們就需要丢入産品
chickens[count] = chicken;
count++;
//可以通知消費者消費了。
this.notifyAll();
}
//消費者消費産品
public synchronized Chicken pop(){
//判斷能否消費
if (count == 0) {
//等待生産者生産,消費者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消費
count--;
Chicken chicken = chickens[count];
//吃完了,通知生産者生産
this.notifyAll();
return chicken;
}
}
19.2、解決方式2
- 并發協作模型“生産者/消費者模式“ -->信号燈法
- 通過定義一個标志位true、false來判斷代碼需要執行
示例:
package com.thread;
//測試生産者消費者問題2:信号燈法,标志位解決
public class PCDemo02 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生産者 --> 演員
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("快樂大學營");
} else {
this.tv.play("抖音:記錄美好生活");
}
}
}
}
//消費者 --> 觀衆
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();
}
}
}
//産品 --> 節目
class TV{
String voice; //通知喚醒
//演員表演,觀衆等待 T
//觀衆觀看,演員等待 F
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;
}
}
20、線程池
- 背景:經常建立和銷毀、使用量特别大的資源,比如并發情況下的線程,對性能影響很大。
- 思路:提前建立好多個線程,放入線程池中,使用時直接擷取,使用完放回池中。可以避免頻繁建立銷毀、實作重複利用。類似生活中的公共交通工具。
- 好處:
- 提高響應速度(減少了建立新線程的時間)
- 降低資源消耗(重複利用線程池中線程,不需要每次都建立)
- 便于線程管理(…)
- corePoolSize:核心池的大小
- maximumPoolSize:最大線程數
- keepAliveTime:線程沒有任務時最多保持多長時間後會終止
- JDK5.0起提供了線程池相關API:ExecutorService和Executors
- ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor
- void execute(Runnable command):執行任務/指令,沒有傳回值,一般用來執行Runndable
- Futuresubmit(Callable task):執行任務,有傳回值,一般用來執行Callable
- void shutdown():關閉連接配接池
- Executors:工具類、線程池的工廠類,用于建立并傳回不同類型的線程池
package com.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//測試線程池
public class PoolDemo01 {
public static void main(String[] args) {
//1、建立服務,建立線程池
//newFixedThreadPool 參數為:線程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//執行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//2、關閉連結
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
總結
package com.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadSumUp {
public static void main(String[] args) {
new MyThread1().start();
new Thread(new MyThread2()).start();
FutureTask<Integer> integerFutureTask = new FutureTask<>(new MyThread3());
new Thread(integerFutureTask).start();
try {
Integer integer = integerFutureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//1、繼承Thread類
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("This is MyThread1");
}
}
//2、實作Runnable接口
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("This is MyThread2");
}
}
//3、實作Callable接口
class MyThread3 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("This is MyThread3");
return 100;
}
}
如果有同學發現筆記存在問題,麻煩私信找我,我馬上來更正,謝謝!