目錄
- 1、前言
- 2、Thread類
- 2.1 Thread類實作Runnable介面
- 2.2 創建步驟:
- 2.2.1 測驗
- 2.2.2 案例:實作多執行緒同步下載圖片
- 3、Runnable介面
- 3.1 Runnable介面
- 3.2 創建步驟
- 3.3 對比
- 4、Callable介面(了解即可)
- 4.1 原始碼:
- 4.2 創建步驟
- 5、靜態代理模式
- 5.1 內容
- 5.2 設計
- 6、Lambda運算式
- 6.1 為什么要使用lambda運算式?
- 7、執行緒
- 7.1 執行緒狀態
- 7.2 執行緒方法
- 7.3 停止執行緒
- 7.4 執行緒休眠
- 7.4 執行緒禮讓
- 7.5 執行緒強制執行
- 7.6 執行緒狀態
- 7.7 執行緒優先級
- 7.8 守護(daemon)執行緒
- 8、執行緒同步
- 8.1 佇列和鎖
- 8.2 同步方法
- 8.3 同步塊
- 9、鎖
- 9.1 死鎖
- 9.2 Lock(鎖)
- 9.3 synchronized和Lock的對比
- 10、執行緒協作(執行緒通信)
- 10.1 解決方式1:管程法
- 10.2 解決方式2:信號燈法
- 11、執行緒池
創建執行緒的主要方式
- 繼承Thread類創建執行緒類(重點)
- 實作Runnable介面創建執行緒類(重點)
- 實作Callable介面或Future介面創建執行緒(了解)
1、前言
關于學習Java多執行緒,其中程式,行程和執行緒等重要概念及其關系在這里就不多贅述,之前作業系統也詳細講過,我就不再詳細擴展,直接進入Java執行緒的學習吧!
此處分享一下Java1.8幫助檔案(中文版),提取碼: k26d
2、Thread類
2.1 Thread類實作Runnable介面
原始碼:
public
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
private volatile String name;
private int priority;
private Thread threadQ;
private long eetop;
/* Whether or not to single_step this thread. */
private boolean single_step;
...
}
2.2 創建步驟:
將一個類宣告為Thread的子類, 這個子類應該重寫Thread類的run方法 , 然后可以分配并啟動子類的實體
- 自定義執行緒類繼承Thread類
- 重寫run()方法,撰寫執行緒執行體
- 創建執行緒物件,呼叫start()方法啟動執行緒
2.2.1 測驗
public class Thread1 extends Thread {
@Override
public void run() {
// run方法執行緒體
for (int i = 0; i < 20; i++) {
System.out.print(String.format("%-3d", i) + "Hello ");
}
}
public static void main(String[] args) {
// main方法主執行緒
// 創建一個執行緒物件
Thread1 thread1 = new Thread1();
// 呼叫start方法開啟執行緒
thread1.start();
for (int i = 0; i < 800; i++) {
System.err.print(String.format("%-3d", i) + "World ");
}
}
}
查看控制臺輸出:

注意:執行緒開啟不一定立即執行,由CPU調度執行
呼叫start方法,是執行緒同時執行,交替執行,如果呼叫run方法,就是順序執行,

2.2.2 案例:實作多執行緒同步下載圖片
APACHE官網下載commons-io,匯入專案Library
public class Thread2 extends Thread {
private String url; // Picture url
private String path;
private String name; // Saved file name
public Thread2(String url, String path, String name) {
super();
this.url = url;
this.path = path;
this.name = name;
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, path, name);
System.out.println("Downloaded the file which name is " + name);
}
public static void main(String[] args) {
String path = "src/com/newthread/img/";
Thread2 t1 = new Thread2(
"https://wx1.sinaimg.cn/orj360/67e231a6ly1geq8okovekj21hc0u0e42.jpg",
path, "tower.jpg");
Thread2 t2 = new Thread2(
"https://wx1.sinaimg.cn/orj360/67e231a6ly1geq8oq8jx1j21hc0u0qse.jpg",
path, "bike.jpg");
Thread2 t3 = new Thread2(
"https://wx1.sinaimg.cn/orj360/67e231a6ly1geq8on2u4gj21hc0u0ngj.jpg",
path, "bridge.jpg");
// simultaneous execution
t1.start();
t2.start();
t3.start();
}
}
// downloader
class WebDownloader{
// download function
public void downloader(String url, String path,String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(path + name));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
System.err.println("IO Exception...downloader function has a problem!");
}
}
}
結果:

3、Runnable介面
3.1 Runnable介面
宣告實作類Runnable介面, 那個類然后實作了run方法, 然后可以分配類的實體,在創建Thread時作為引數傳遞,并啟動,
原始碼:
public
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
...
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
...
}
實作Runnable介面創建的執行緒,不能通過run或start方法來啟動,在Thread類中有一個構造器,這個構造器接收一個Runnable介面的實作類物件,構造Thread執行緒類物件,然后使用這個執行緒類物件呼叫start方法啟動我們的執行緒,
3.2 創建步驟
- 自定義執行緒類實作Runnable介面
- 實作run()方法,撰寫執行緒執行體
- 創建執行緒物件,呼叫start()方法啟動執行緒
推薦使用Runnable物件,因為Java單繼承的局限性
public class Thread3 implements Runnable {
@Override
public void run() {
// run方法執行緒體
for (int i = 0; i < 20; i++) {
System.out.print(String.format("%-3d", i) + "Hello ");
}
}
public static void main(String[] args) {
// main方法主執行緒
// 創建Runnable介面的實作類物件
Thread3 thread3 = new Thread3();
// Thread thread = new Thread(thread3);
// thread.start();
new Thread(thread3).start();
for (int i = 0; i < 800; i++) {
System.err.print(String.format("%-3d", i) + "World ");
}
}
}
3.3 對比
- 繼承Thread類
- 子類繼承Thread類具有多執行緒能力
- 啟動執行緒:子類物件.start()
- 不建議使用:避免OOP單繼承局限
- 實作Runnable介面
- 子類實作Runnable介面具有多執行緒能力
- 啟動執行緒:傳入目標物件+Thread物件.start()
- 推薦使用:避免單繼承局限性,靈活方便,方便同一物件被多個執行緒使用
4、Callable介面(了解即可)
4.1 原始碼:
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
從原始碼可以看出,Callable介面中的call()方法是有回傳值的,是一個泛型,和Future、FutureTask配合可以用來獲取異步執行的結果,
這其實是很有用的一個特性,因為多執行緒相比單執行緒更難、更復雜的一個重要原因就是因為多執行緒充滿著未知性,某條執行緒是否執行了?某條執行緒執行了多久?某條執行緒執行的時候我們期望的資料是否已經賦值完畢?無法得知,我們能做的只是等待這條多執行緒的任務執行完畢而已,而Callable+Future/FutureTask卻可以獲取多執行緒運行的結果,可以在等待時間太長沒獲取到需要的資料的情況下取消該執行緒的任務,
4.2 創建步驟
-
實作Callable介面,需要回傳值型別
-
重寫call方法,需要拋出例外
-
創建目標物件
-
創建執行服務:
ExecutorService ser = Executors.newFixedThreadPool(1); -
提交執行:
Future< Boolean> result1 = ser.submit(1); -
獲取結果:
boolean r1= result1.get(); -
關閉服務:
ser.shutdownNow();
public class Callable1 implements Callable<Boolean>{
@Override
public Boolean call() throws Exception {
for (int i = 0; i < 20; i++) {
System.out.println(String.format("%-3d", i) +
Thread.currentThread().getName());
}
return true;
}
public static void main(String[] args)
throws InterruptedException, ExecutionException {
Callable1 callable1 = new Callable1();
Callable1 callable2 = new Callable1();
Callable1 callable3 = new Callable1();
// 創建執行服務
ExecutorService ser = Executors.newFixedThreadPool(3);
// 提交執行
Future<Boolean> result1 = ser.submit(callable1);
Future<Boolean> result2 = ser.submit(callable2);
Future<Boolean> result3 = ser.submit(callable3);
// 獲取結果
boolean r1= result1.get();
boolean r2= result2.get();
boolean r3= result3.get();
System.out.println(r1 + " "+ r2 + " " + r3);
// 關閉服務
ser.shutdownNow();
}
}
5、靜態代理模式
5.1 內容
真實角色,代理角色;真實角色和代理角色要實作同一個介面,代理角色要持有真實角色的參考,
在Java中執行緒的設計使用了靜態代理設計模式,其中自定義執行緒類實作Runable介面,Thread類也實作了Runalbe介面,在創建子執行緒的時候,傳入了自定義執行緒類的參考,再通過呼叫start()方法,呼叫自定義執行緒物件的run()方法,實作了執行緒的并發執行,
Thread物件呼叫執行緒的start()方法,在內部呼叫了真實角色的run()方法,
5.2 設計
代碼結構由三部分組成
-
介面:主題
-
代理類
-
被代理類
實作方式:代理類和被代理類要實作同一個主題介面,而且代理類中要有一個被代理類的屬性(target),這樣才能把核心業務邏輯交還給被代理類完成;而一些與核心業務邏輯無關的邏輯,并且需求是多變的,那么這些邏輯就可以交給代理類來完成,
代理物件可以做很多真實物件做不了的事情,真實物件專注做自己的事情
public class StaticProxy {
public static void main(String[] args) {
SpringPioneer springPioneer = new SpringPioneer(new Programmer());
springPioneer.Deploy();
}
}
interface SpringBootFrame {
// 介面主題:框架整合部署
void Deploy();
}
// 真實角色,程式員
class Programmer implements SpringBootFrame{
@Override
public void Deploy() {
System.out.println("Programmer SpringBoot deployed successfully!");
}
}
// 代理角色,SpringBoot的作者,幫我們簡化開發
class SpringPioneer implements SpringBootFrame{
// 代理誰-->真實目標角色
private SpringBootFrame target;
public SpringPioneer(SpringBootFrame target) {
this.target = target;
}
@Override
public void Deploy() {
before();
this.target.Deploy(); // 這就是真實物件
after();
}
private void before() {
System.out.println("研發出這個SpringBoot框架,并開源,");
}
private void after() {
System.out.println("不斷推出新的特性,");
}
}
6、Lambda運算式
函式式編程的概念
6.1 為什么要使用lambda運算式?
避免匿名內部類定義過多,代碼簡潔,去掉無意義代碼,核心邏輯,
理解Functional Interface(函式式介面)是學習Java8 lambda運算式的關鍵,
函式式介面的定義:任何介面,如果只包含唯一一個抽象方法,那么它就是一個函式式介面,
public interface Runnable {
public abstract void run();
}
對于函式式介面,我們可以通過lambda運算式來創建介面的物件,
public class lambda1 {
// 3.靜態內部類
static class StaticEntityLike implements Like {
@Override
public void lambda() {
System.out.println("Static likes lambda!");
}
}
public static void main(String[] args) {
Like entityLike = new EntityLike();
entityLike.lambda();
Like staticEntityLike = new StaticEntityLike();
staticEntityLike.lambda();
// 4.區域內部類
class InternalEntityLike implements Like {
@Override
public void lambda() {
System.out.println("Internal likes lambda!");
}
}
Like internalEntityLike = new InternalEntityLike();
internalEntityLike.lambda();
// 5.匿名內部類,沒有類的名稱,必須借助介面或者父類
Like anonymousEntityLike = new Like() {
@Override
public void lambda() {
System.out.println("Anonymous likes lambda!");
}
};
anonymousEntityLike.lambda();
// 6.用lambda簡化
Like lambdaLike = () -> {
System.out.println("Lambda likes lambda!");
};
lambdaLike.lambda();
// new Like(()->System.out.println("Lambda likes lambda!")).lambda();
// 7.帶引數的
Love lambdaLove = (int t) -> {
System.out.println("Lambda loves you " + t + " times!");
};
lambdaLove.time(520);
// 7.1簡化去掉引數型別
Love love1 = (t) -> {
System.out.println("Lambda loves you " + t + " times!");
};
love1.time(521);
// 7.2簡化去掉括號
Love love2 = t -> {
System.out.println("Lambda loves you " + t + " times!");
};
love2.time(522);
// 7.3簡化去掉花括號
// lambda運算式只能有一行代碼的情況下才能簡化成為一行,如果有多行,那么就用代碼塊包裹
Love love3 = t -> System.out.println("Lambda loves you " + t + " times!");
love3.time(523);
}
}
// 1.定義一個函式式介面
interface Like {
void lambda();
}
// 2.實作類
class EntityLike implements Like {
@Override
public void lambda() {
System.out.println("I like lambda!");
}
}
// 帶引數的
interface Love {
void time(int t);
}
多個引數可以去掉引數型別,再簡化就必須加上括號(a, b)->{};
7、執行緒
7.1 執行緒狀態
創建狀態(new):Thread t = new Thread();,執行緒物件一旦創建,就進入到了新生狀態,
就緒狀態:當呼叫start()方法,執行緒立即進入就緒狀態,但不意味著立即調度執行,
運行狀態:CPU調度進入運行狀態執行緒才真正執行執行緒體的代碼塊,
阻塞狀態:當呼叫sleep(),wait()或同步鎖定時,執行緒進入阻塞狀態就是代碼不往下執行,阻塞
事件解除后,重新進入就緒狀態,等待cpu調度執行,
死亡狀態(dead):執行緒中斷或者結束,一旦進入死亡狀態,就不能再次啟動,
7.2 執行緒方法
| 方法 | 說明 |
|---|---|
| setPriority(int newPriority) | 設定當前執行緒的優先級 |
| static void sleep(long millis) | 在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行),休眠的執行緒進入阻塞狀態, |
| void join() | 呼叫join方法的執行緒強制執行,其他執行緒處于阻塞狀態,等該執行緒執行完后,其他執行緒再執行,有可能被外界中斷產生InterruptedException中斷例外, |
| static void yield() | 呼叫yield方法的執行緒,(暫停當前執行的執行緒物件)會禮讓其他執行緒先運行,(大概率其他執行緒先運行,小概率自己還會運行) |
| void interrupt() | 中斷執行緒,不建議使用 |
| boolean isAlive() | 判斷執行緒是否處于活動狀態 (執行緒呼叫start后,即處于活動狀態) |
7.3 停止執行緒
-
不推薦使用JDK提供的
stop()和destroy()方法,(已廢棄)public class Thread implements Runnable { ... @Deprecated public final void stop() { SecurityManager security = System.getSecurityManager(); if (security != null) { checkAccess(); if (this != Thread.currentThread()) { security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION); } } // A zero status value corresponds to "NEW", it can't change to // not-NEW because we hold the lock. if (threadStatus != 0) { resume(); // Wake up thread if it was suspended; no-op otherwise } // The VM can handle all thread states stop0(new ThreadDeath()); } ... @Deprecated public void destroy() { throw new NoSuchMethodError(); } ... } -
推薦執行緒自己停止下來,建議使用一個標志位進行終止變數當
flag=false,則終止執行緒運行,public class TestStop implements Runnable { // 1.設定一個標志位 private boolean flag = true; @Override public void run() { int i=0; while (flag) { System.out.println("run...Thread..."+i++); } } // 2.設定一個公開的方法停止執行緒,轉換標志位 public void stop() { this.flag = false; } public static void main(String[] args) { TestStop testStop = new TestStop(); new Thread(testStop).start(); for (int i = 0; i < 200; i++) { System.out.println("main...Thread..."+i); if(i==100) { // 呼叫stop方法停止執行緒 testStop.stop(); System.out.println("run...Thread...stoped..."); } } } }結果:執行緒停止,main執行緒繼續執行到結束
7.4 執行緒休眠
sleep(時間)指定當前執行緒阻塞的毫秒數;sleep存在例外Interrupted Exception;sleep時間達到后執行緒進入就緒狀態;sleep可以模擬網路延時,倒計時等;每一個物件都有一個鎖,sleep不會釋放鎖,
示例:
// 模擬倒計時
public class TestSleep2 {
public static void main(String[] args) {
try {
tenDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 列印當前系統時間
Date startTime = new Date(System.currentTimeMillis()); // 獲取當前系統時間
System.out.println("Start printing system time...");
while (true) {
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis()); // 更新時間
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void tenDown() throws InterruptedException {
int num=10;
while (true) {
Thread.sleep(1000);
System.out.println(num--);
if (num<=0) {
break;
}
}
}
}
7.4 執行緒禮讓
禮讓執行緒,讓當前正在執行的執行緒暫停,但不阻塞;將執行緒從運行狀態轉為就緒狀態;讓cpu重新調度,禮讓不一定成功!看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()+"Thread starts...");
Thread.yield(); // 禮讓
System.out.println(Thread.currentThread().getName()+"Thread ends...");
}
}
7.5 執行緒強制執行
join()合并程式,待此執行緒執行完成后,再執行其他執行緒,其他執行緒阻塞,可以想象為插隊,
示例:
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("VIP thread comes..."+i);
}
}
public static void main(String[] args) {
TestJoin testJoin = new TestJoin();
Thread thisThread = new Thread(testJoin);
thisThread.start();
for (int i = 0; i < 200; i++) {
if (i==90) {
try {
thisThread.join(); // 插隊
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main Thread..."+i);
}
}
}
取一個運行結果:
main Thread...0
VIP thread comes...0
main Thread...1
VIP thread comes...1
VIP thread comes...2
VIP thread comes...3
VIP thread comes...4
VIP thread comes...5
...
main Thread...87
main Thread...88
main Thread...89
VIP thread comes...19
VIP thread comes...20
VIP thread comes...21
VIP thread comes...22
VIP thread comes...23
VIP thread comes...24
...
VIP thread comes...98
VIP thread comes...99
main Thread...90
main Thread...91
main Thread...92
main Thread...93
在插隊之前,2執行緒并行,當呼叫join()后,這個執行緒即成為VIP,只能等VIP執行緒執行結束,其他執行緒才能繼續執行,
7.6 執行緒狀態
執行緒狀態State是列舉型別,有5個狀態,在中文幫助檔案中有宣告,
Thread.java原始碼:
public
class Thread implements Runnable {
...
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
public State getState() {
return sun.misc.VM.toThreadState(threadStatus);
}
...
}
示例:
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();
System.out.println(state); // NEW
// 啟動
thread.start();
state = thread.getState();
System.out.println(state); // RUN
while (state != Thread.State.TERMINATED) { // 只要執行緒不終止就一直輸出狀態
Thread.sleep(100);
state = thread.getState(); // 更新狀態
System.out.println(state);
}
}
}
7.7 執行緒優先級
Java提供一個執行緒調度器來監控程式中啟動后進入就緒狀態的所有執行緒,執行緒調度器按照優先級決定應該調度哪個執行緒來執行;執行緒的優先級用數字表示,范圍從1-10,
Thread.java原始碼:
public
class Thread implements Runnable {
...
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
...
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
...
}
優先級低只是意味著獲得調度的概率低,并不是優先級低就不會被呼叫了,這都是看CPU的調度,
示例:
public class TestPriority {
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);
t1.start();
t2.setPriority(2); // 先設定優先級再啟動
t2.start();
t3.setPriority(Thread.MAX_PRIORITY);
t3.start();
try {
t4.setPriority(-1);
t4.start();
t5.setPriority(11);
t5.start();
} catch (Exception e) {
System.err.println(e);
}
}
}
class MyPriority implements Runnable {
@Override
public void run() {
System.out.println(
Thread.currentThread().getName()+"-->" +
Thread.currentThread().getPriority()
);
}
}
取一個運行結果:
main-->5
java.lang.IllegalArgumentException
Thread-0-->5
Thread-2-->10
Thread-1-->2
總是主執行緒mian限制性,優先級低的也可能比高的優先執行,
7.8 守護(daemon)執行緒
執行緒分為用戶執行緒和守護執行緒;虛擬機必須確保用戶執行緒(main執行緒)執行完畢;虛擬機不用等待守護執行緒執行完畢;如后臺記錄操作日志,監控記憶體垃圾回收等,
示例:
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
Man man = new Man();
Thread thread = new Thread(god);
thread.setDaemon(true); // 默認false表示用戶執行緒,正常的執行緒都是用戶執行緒
thread.start(); // 守護執行緒啟動
new Thread(man).start(); // 用戶執行緒挺停止
}
}
class God implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("God bless you!");
}
}
}
class Man implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Hello World!");
}
System.err.println("Goodbye World!");
}
}
看似god執行緒會一直回圈執行下去,但它是守護執行緒,man執行緒執行結束后,守護執行緒等一會也會結束,因為虛擬機還會運行一會兒,
8、執行緒同步
多個執行緒操作同一個資源,
并發:同一個物件被多個執行緒同時操作,
處理多執行緒問題時,多個執行緒訪問同—個物件,并且某些執行緒還想修改這個物件,這時候我們就需要執行緒同步,執行緒同步其實就是一種等待機制,多個需要同時訪問此物件的執行緒進入這個物件的等待池形成佇列,等待前面執行緒使用完畢,下一個執行緒再使用,
8.1 佇列和鎖
由于同一行程的多個執行緒共享同一塊存盤空間,在帶來方便的同時,也帶來了訪問沖突問題,為了保證資料在方法中被訪問時的正確性,在訪問時加入鎖機制synchronized,當一個執行緒獲得物件的排它鎖(鎖是物件的),將會獨占資源,其他執行緒必須等待這個執行緒使用后釋放鎖即可,存在以下問題:
-
一個執行緒持有鎖會導致其他所有需要此鎖的執行緒掛起
-
在多執行緒競爭下,加鎖,釋放鎖會導致比較多的背景關系切換和調度延時,引起性能問題
-
如果一個優先級高的執行緒等待一個優先級低的執行緒釋放鎖會導致優先級倒置,引起性能問題
8.2 同步方法
-
由于我們可以通過private關鍵字來保證資料物件只能被方法訪問,所以我們只需要針對方法提出一套機制,這套機制就是synchronized關鍵字,它包括兩種用法synchronized方法和synchronized塊
同步方法:
public synchronized void method(int args) -
synchronized方法控制對“物件"的訪問,每個物件對應一把鎖,每個synchronized方法都必須獲得呼叫該方法的物件的鎖才能執行,否則執行緒會阻塞,方法一旦執行,就獨占該鎖,直到該方法回傳才釋放鎖,后面被阻塞的執行緒才能獲得這個鎖,繼續執行
缺陷:若將一個大的方法申明為 synchronized將會影響效率
-
弊端:需要鎖太多,浪費資源
8.3 同步塊
-
同步塊:
synchronized(obj){} -
obj稱之為同步監視器
- obj可以是任何物件,但是推薦使用共享資源作為同步監視器
- 同步方法中無需指定同步監視器,因為同步方法的同步監視器就是this,就是這個物件本身,或者是class[反射中講解]
-
同步監視器的執行程序
- 第一個執行緒訪問,鎖定同步監視器,執行其中代碼
- 第二個執行緒訪問,發現同步監視器被鎖定,無法訪問
- 第一個執行緒訪問完畢,解鎖同步監視器
- 第二個執行緒訪問,發現同步監視器沒有鎖,然后鎖定并訪問
案例:
public class Bank {
public static void main(String[] args) {
Account account = new Account(100, "Deposit");
Drawing husband = new Drawing(account, 50, "husband");
Drawing wife = new Drawing(account, 100, "wife");
husband.start();
wife.start();
}
}
class Account {
int money;
String name;
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
class Drawing extends Thread {
Account account;
int drawingMoney;
int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
// @Override
// public synchronized void run() {
// if(account.money-drawingMoney<0) {
// System.out.println(Thread.currentThread().getName()+" Money is not enough!");
// return;
// }
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// // 卡內余額
// account.money = account.money - drawingMoney;
// // 手里的現金
// nowMoney = nowMoney + drawingMoney;
// System.out.println(account.name + " balance is "+account.money);
// System.out.println(this.getName()+"'s cash now is "+nowMoney);
// }
@Override
public void run() {
synchronized (account) {
if(account.money-drawingMoney<0) {
System.out.println(Thread.currentThread().getName()+" Money is not enough!");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingMoney;
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name + " balance is "+account.money);
System.out.println(this.getName()+"'s cash now is "+nowMoney);
}
}
}
這里要注意了:同步塊指定鎖住的物件,可以鎖住任何物件,如果用上面的方法將run方法上鎖,鎖住的是husband和wife物件,也就是有兩把鎖了(2個執行緒2把鎖),只能用下面的方法鎖住變化的量,才有用,
List等常見資料集合是非安全的,JUC(java.util.concurrent包)中提供了CopyOnWriteArrayList等安全型別的集合,
9、鎖
9.1 死鎖
多個執行緒各自占有一些共享資源,并且互相等待其他執行緒占有的資源才能運行,而導致兩個或者多個執行緒都在等待對方釋放資源,都停止執行的情形,某一個同步塊同時擁有“兩個以上物件的鎖”時,就可能會發生“死鎖”的問題,
public class DeadLock {
public static void main(String[] args) {
Makeup girl1 = new Makeup(0, "Marry");
Makeup girl2 = new Makeup(1, "Lisa");
girl1.start();
girl2.start();
}
}
class Lipstick{
}
class Mirror {
}
class Makeup extends Thread {
// 用static保證資源只有一份兒
private static Lipstick lipstick = new Lipstick();
private static Mirror mirror = new Mirror();
int choice;
String name; // 化妝的人
public Makeup(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
try {
startMakeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 化妝,互相持有對方的鎖,就是需要拿到對方的資源
// private void startMakeup() throws InterruptedException {
// if (choice==0) {
// synchronized (lipstick) { // 獲得口紅的鎖
// System.out.println(this.name+" got the lipstick!");
// Thread.sleep(1000);
// synchronized (mirror) { // 1秒鐘后向獲得鏡子
// System.out.println(this.name+" got the mirror!");
// }
// }
// } else {
// synchronized (mirror) {
// System.out.println(this.name+" got the mirror!");
// Thread.sleep(1000);
// synchronized (lipstick) {
// System.out.println(this.name+" got the lipstick!");
// }
// }
// }
// // 比如選擇0時,只有當得到鏡子后才會釋放口紅的鎖,就會死鎖
// }
private void startMakeup() throws InterruptedException {
if (choice==0) {
synchronized (lipstick) { // 獲得口紅的鎖
System.out.println(this.name+" got the lipstick!");
Thread.sleep(1000);
}
synchronized (mirror) { // 1秒鐘后向獲得鏡子
System.out.println(this.name+" got the mirror!");
}
} else {
synchronized (mirror) {
System.out.println(this.name+" got the mirror!");
Thread.sleep(1000);
}
synchronized (lipstick) {
System.out.println(this.name+" got the lipstick!");
}
}
// 這樣做就是當口紅用完口紅的鎖就釋放
}
}
結合作業系統死鎖產生的條件和避免死鎖的方式,
9.2 Lock(鎖)
-
從JDK5.0開始,Java提供了更強大的執行緒同步機制:通過顯式定義同步鎖物件來實作同步,同步鎖使用Lock物件充當
-
java.utll.concurrent.locks.Lock介面是控制多個執行緒對共享資源進行訪問的工具,鎖提供了對共享資源的獨占訪問,毎次只能有一個執行緒對Lock物件加鎖,執行緒開始訪問共享資源之前應先獲得Lock物件
-
ReentrantLock(可重入鎖)類實作了Lock,它擁有與synchronized相同的并發性和記憶體語意,在實作執行緒安全的控制中,比較常用的是Reentrantlock,可以顯式加鎖、釋放鎖,
9.3 synchronized和Lock的對比
-
Lock是顯式鎖(手動開啟和關閉鎖,別忘記關閉鎖)synchronized是隱式鎖,出了作用域自動釋放
-
Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
-
使用Lock鎖,JwM將花費較少的時間來調度執行緒,性能更好,并且具有更好的擴展性(提供更多的子類)
-
優先使用順序:Lock > 同步代碼塊(已經進入了方法體,分配了相應資源) > 同步方法(在方法體之外)
10、執行緒協作(執行緒通信)
在生產者消費者問題中,僅有synchronized是不夠的
synchronized可阻止并發更新同一個共享資源,實作了同步
synchronized不能用來實作不同執行緒之間的訊息傳遞(通信)
Java提供了幾個方法解決執行緒之間的通信問題
| 方法名 | 作用 |
|---|---|
| wait() | 表示執行緒一直等待,直到其他執行緒通知,與sleep不同,會釋放鎖 |
| wait(long timeout) | 指定等待的毫秒數 |
| notify() | 喚醒一個處于等待狀態的執行緒 |
| notifyAll() | 喚醒同一個物件上所有呼叫wait()方法的執行緒,優先級別高的執行緒優先調度 |
注意:均是Object類的方法,都只能在同步方法或者同步代碼塊中使用否則會拋出例外IlegalMonitorState Exception
10.1 解決方式1:管程法
并發協作模型“生產者/消費者模式”-->管程法
-
生產者:負責生產資料的模塊(可能是方法,物件,執行緒,行程)
-
消費者:負責處理資料的模塊(可能是方法,物件,執行緒,行程)
-
緩沖區:消費者不能直接使用生產者的資料,他們之間有個“緩沖區“
生產者將生產好的資料放入緩沖區,消費者從緩沖區拿出資料
public class TestProductorCustomer {
public static void main(String[] args) {
Buffer buffer = new Buffer();
new Productor(buffer).start();
new Customer(buffer).start();
}
}
// 生產者
class Productor extends Thread {
// 需要一個容器
Buffer buffer;
public Productor(Buffer buffer) {
this.buffer = buffer;
}
// 生產
@Override
public void run() {
for (int i = 1; i < 100; i++) {
buffer.push(new Goods(i));
System.out.println("生產了第"+i+"個商品!");
}
}
}
// 消費者
class Customer extends Thread {
Buffer buffer;
public Customer(Buffer buffer) {
this.buffer = buffer;
}
// 消費
@Override
public void run() {
for (int i = 1; i < 100; i++) {
System.out.println("消費了第" + buffer.pop().getID() + "個商品!");
}
}
}
// 產品
class Goods extends Thread {
private int ID;
public Goods(int id) {
this.ID = id;
}
public int getID() {
return ID;
}
}
// 緩沖區
class Buffer extends Thread {
// 需要一個容器大小
Goods[] goods = new Goods[10];
int count=0;
// 生產者放入產品
public synchronized void push(Goods good) {
// 如果容器滿了,就需要等待消費者消費
if(count==goods.length) {
// 通知消費者消費,生產等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果沒有滿就需要放入產品
goods[count] = good;
count++;
// 可以通知消費者消費了
this.notifyAll();
}
// 消費者消費產品
public synchronized Goods pop() {
// 如果容器是否為空
if(count==0) {
// 通知生產者生產,消費等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果可以消費
count--;
Goods good = goods[count];
// 可以通知生產者生產了
this.notifyAll();
return good;
}
}
10.2 解決方式2:信號燈法
就是長度為1的管程法
public class ProductorCustomerUsingSignal {
public static void main(String[] args) {
Show show = new Show();
new Actor(show).start();
new Audience(show).start();
}
}
// 生產者演員
class Actor extends Thread {
private Show show;
public Actor(Show show) {
this.show = show;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2==0) {
this.show.play("Happy Camp!");
} else {
this.show.play("TikTok!");
}
}
}
}
// 消費者觀眾
class Audience extends Thread {
private Show show;
public Audience(Show show) {
this.show = show;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.show.watch();
}
}
}
// 資源節目
class Show {
// 演員表演,觀眾等待 T
// 觀眾觀看,演員等待 F
private boolean flag = true;
String display; // 表演的節目
// 表演
public synchronized void play(String display) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Actors show the "+display);
// 通知觀眾觀看
this.notifyAll();
this.display = display;
this.flag = !this.flag;
}
// 表演
public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Audience watch the "+display);
// 通知演員表演
this.notifyAll();
this.flag = !this.flag;
}
}
11、執行緒池
背景:經常創建和銷毀、使用量特別大的資源,比如并發情況下的執行緒,對性能影響很大,
思路:提前創建好多個執行緒,放入執行緒池中,使用時直接獲取,使用完放回池中,可以避免頻繁創建銷毀、實作重復利用,類似生活中的公共交通工具(共享單車),
優勢:
-
提高回應速度(減少了創建新執行緒的時間)
-
降低資源消耗(重復利用執行緒池中執行緒,不需要每次都創建)
-
便于執行緒管理(…)
- core Poolsize:核心池的大小
- maximumPoolsize:最大執行緒數
- keepAlive Time:執行緒沒有任務時最多保持多長時間后會終止
public class TestPool {
public static void main(String[] args) {
// 1.創建服務,創建執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(new PoolThread());
executorService.execute(new PoolThread());
executorService.execute(new PoolThread());
executorService.execute(new PoolThread());
// 2.關閉連接
executorService.shutdown();
}
}
class PoolThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
-
JDK5.0起提供了執行緒池相關API:Executor Service和Executors
-
Executor Service:真正的執行緒池介面,常見子類 ThreadPoolExecutor
void execute( Runnable command):執行任務命令,沒有回傳值,一般用來執
行 Runnable<T> Future<T> submit( Callable<T>task):執行任務,有回傳值,一般又來執行
Callable(上面我們介紹Callable介面時測驗過)void shutdown():關閉連接池
-
Executors:工具類、執行緒池的工廠類,用于創建并回傳不同型別的執行緒池
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/58107.html
標籤:Java
上一篇:尋求和我一樣的編程少年
下一篇:讀取U盤隱藏磁區中的檔案
