目錄
- 多執行緒
- 并發
- 并行
- 執行緒與行程
- 行程:
- 執行緒:
- 多執行緒好處:
- 執行緒調度
- 分時調度
- 搶占式調度
- 主執行緒
- 創建執行緒類(創建多執行緒)
- Thread類中的常用方法
- 獲取執行緒名稱
- 設定執行緒的名稱
- sleep類
- 創建多執行緒的第二種方式-Runable介面(推薦使用)
- 匿名內部類的方式實作執行緒的創建
- 執行緒安全問題
- 解決執行緒安全問題
- 執行緒同步
- 執行緒同步方式1:同步代碼塊
- 該同步技術的原理
- 執行緒同步方式2:同步方法
- 該同步技術的原理
- 還有一種靜態同步方法
- 執行緒同步方式3:Lock鎖機制
- 執行緒同步方式1:同步代碼塊
- 執行緒同步
多執行緒
并發
指兩個或多個時間再同一個時間段內發生(在多個任務中交替執行,一個一個的執行)
比喻:一個人吃兩個饅頭,一個一個吃
并行
指兩個或多個時間再同一個時刻發生(同時發生)(多個任務同時執行)
比喻:兩個人吃兩個饅頭,同時吃~
所以說,并行的速度更快(但是由于計算機處理事務特別快,所以當事務不是很多的時候,二者速度在我們人來感受起來,相差并不大)
執行緒與行程
行程:
指一個記憶體中正在運行的應用程式,即電腦當中每一個app都算一個行程~
每打開一個app(應用程式),就會進入記憶體產生一個行程~
執行緒:
先了解CPU(中央處理器):指揮電腦中的軟體和硬體干活兒~
CPU的分類:
- AMD-----
- Inter-----Core(核心) i7 8866 四核八執行緒(8執行緒表示可以同時執行8個任務)
- 以電腦管家為例:每次使用電腦管家里的功能進行首頁體檢、病毒查殺、垃圾清理、電腦加速的時候,每一次都會開辟一條執行路徑,這個路徑就叫執行緒~

執行緒屬于行程
是行程中的一個執行單元,負責程式的執行
假如CPU是單核心單執行緒CPU,它只能同時執行一個執行緒,如果要執行電腦管家,它就會在多個任務之間,高速的切換,輪流執行多個執行緒~
由于速度很快,就看起來像同時執行~(效率較低)
假如CPU是4核心8執行緒CPU,它就能同時執行8個執行緒,如果要執行電腦管家,它就會是8個執行緒在多個任務之間,高速的切換,那么它的速度就會是單核心單執行緒CPU的8倍(每個任務被執行到的幾率都被提高8倍)!
由于速度很快,就看起來像同時執行~(效率較高)
多執行緒好處:
- 效率較高
- 多個執行緒之間互不影響
執行緒調度
分時調度
所有執行緒輪流使用CPU,每個執行緒使用CPU的時間平均分~(AA制)
搶占式調度
把執行緒分優先級,優先級高的執行緒優先使用CPU(如果優先級一樣,CPU就隨機選一個執行緒)(VIP制)
CPU都是在多個行程的多個執行緒之間進行高速切換,誰的優先級高,呢么被執行的機會就大一些~
主執行緒
指執行主方法(main方法)的執行緒~
單執行緒程式:Java程式中只有一個執行緒
程式從main方法開始,從上到下,按順序執行
Person.java
public class Person {
private String name;
public void run(){
//輸出10次name
for (int i = 1; i <= 10; i++) {
System.out.println("第" + i +"次name:"+name+i);
}
}
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
PersonTest.java
public class PersonTest {
public static void main(String[] args) {
Person person1 = new Person("牛牛");
Person person2 = new Person("niniu");
person1.run();
person2.run();
}
}
運行結果:

這就是一個單執行緒程式~??
假如在牛牛的run()方法和niuniu的run()方法之間加一個例外(這里我用運算例外:除法分母不能為零的例外),會發現,niuniu就不列印了,如下圖所示:
public class PersonTest {
public static void main(String[] args) {
Person person1 = new Person("牛牛");
Person person2 = new Person("niniu");
person1.run();
//加入一個運算例外
System.out.println(1/0);
person2.run();
}
}
運行結果:

由于單執行緒有弊端,所以我們學習創建多執行緒!??
創建執行緒類(創建多執行緒)
Java使用java.lang.Thread類代表執行緒
Java虛擬機允許應用程式并發的運行多個執行執行緒-------->并發:同時發生
創建新執行執行緒有兩種方法:
- 一種是將類宣告為Thread的子類------------------->該子類應該重寫Thread類的run方法,接著該子類就可以分配并啟動該子類的實體,
即創建一個自定義的子類去繼承Thread類,然后重寫里面的run();方法,然后在測驗類中new剛剛創建的Thread的子類物件,并使用它去呼叫start();方法,如下代碼和運行結果:
代碼:
ChildreThread.java
public class ChildreThread extends Thread{
private String name;
//構造器
public ChildreThread(String name) {
this.name = name;
}
//重寫run方法
public void run(){
//輸出100次name
for (int i = 1; i <= 100; i++) {
System.out.println("第" + i +"次name:"+name+i);
}
}
}
ChildreThreadTest.java
public class ChildreThreadTest {
public static void main(String[] args) {
ChildreThread childreThread2 = new ChildreThread("start創建的多執行緒");
childreThread2.start();//start創建的多執行緒
//輸出100次"主執行緒"
for (int i = 1; i <= 100; i++) {
System.out.println("第" + i +"次主執行緒");
}
}
}
運行結果:

發現兩個執行緒在互相爭搶運行~成功!??
產生多執行緒的原理:
本來main函式可以自上而下執行一條main執行緒,但是中途出現了一個“childreThread2.start();//start創建的多執行緒”,于是main方法這條路徑上就出現了一條新的執行緒路徑(這條路徑指向繼承Thread的子類中的run();方法),于是乎,CPU就會對這兩條執行緒路徑進行隨機選擇,也可以說是這兩條執行緒路徑在爭搶CPU的使用權,所以就出現了主執行緒和新創建的執行緒交替執行的畫面~??
多執行緒的記憶體圖解:

優點:
每一個執行緒都在一個新的堆疊里,所以他們互不干擾~??
- 另一種是實作宣告實作Runnable介面的類------>略
Thread類中的常用方法
public String getName();--------->獲取當前執行緒名;public void start();--------->導致該執行緒開始執行;Java虛擬機呼叫此執行緒的run方法;public void run();--------->此執行緒要執行的任務在此處定義代碼;public static void sleep(long millis);--------->使當前正在執行的執行緒以指定的毫秒數暫停(暫時停止執行);public static Thread currentThread();--------->回傳對當前正在執行的執行緒物件的參考;
獲取執行緒名稱
public String getName();--------->獲取當前執行緒名:
MyThread.java
/*獲取執行緒的名稱:
* 1.使用Thread類中的getName()方法
* String getName() 回傳該執行緒的名稱
* 2.可以先獲取到當前正在執行的執行緒,使用執行緒中的方法getName()獲取執行緒的名稱
* static Thread currentThread() 回傳對當前正在執行的執行緒物件的參考,
* */
//第一步:定義一個Thread類的子類
public class MyThread extends Thread{
//第二步:重寫Thread類中的run方法,設定執行緒任務
@Override
public void run() {
//獲取執行緒的名稱
String name = getName();
System.out.println(name);
}
}
MyThreadTest.java
public class MyThreadTest {
public static void main(String[] args) {
//創建子類物件
MyThread myThread = new MyThread();
//呼叫start方法開啟一個新執行緒
myThread.start();//創建的新執行緒會去執行它自己重寫的run方法,即MyThread類中的run方法
}
}
運行結果:

如果再創建一個新執行緒:
public class MyThreadTest {
public static void main(String[] args) {
//創建子類物件
MyThread myThread = new MyThread();
//呼叫start方法開啟一個新執行緒
myThread.start();//創建的新執行緒會去執行它自己重寫的run方法,即MyThread類中的run方法
//如果再創建一個新執行緒
new MyThread().start();//新執行緒的名稱就會是Thread-1
//如果又創建一個新執行緒
new MyThread().start();//新執行緒的名稱就會是Thread-2
//以此類推.....還會有Thread-3,Thread-4,Thread-5,Thread-6...Thread-n
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
}
運行結果:

在重寫的run方法中,換另外一種方式獲取執行緒名,即使用Thread類中的currentThread()方法,直接就可以獲取到當前正在執行的執行緒,然后使用getName方法得到執行緒的名稱!
代碼如下:
MyThread.java
/*獲取執行緒的名稱:
* 1.使用Thread類中的getName()方法
* String getName() 回傳該執行緒的名稱
* 2.可以先獲取到當前正在執行的執行緒,使用執行緒中的方法getName()獲取執行緒的名稱
* static Thread currentThread() 回傳對當前正在執行的執行緒物件的參考,
* */
//第一步:定義一個Thread類的子類
public class MyThread extends Thread{
//第二步:重寫Thread類中的run方法,設定執行緒任務
@Override
public void run() {
//獲取執行緒的名稱
// String name = getName();
// System.out.println(name);
Thread thread = Thread.currentThread();//currentThread()或取當前執行緒
System.out.println(thread+"------這是執行緒");//列印當前執行緒
String threadName = thread.getName();//getName()獲取執行緒名稱
System.out.println(threadName+"---------------------這是執行緒名稱");//列印上一步獲取到的執行緒名稱
}
}
MyThreadTest.java
public class MyThreadTest {
public static void main(String[] args) {
//創建子類物件
MyThread myThread = new MyThread();
//呼叫start方法開啟一個新執行緒
myThread.start();//創建的新執行緒會去執行它自己重寫的run方法,即MyThread類中的run方法
//如果再創建一個新執行緒
new MyThread().start();//新執行緒的名稱就會是Thread-1
//如果又創建一個新執行緒
new MyThread().start();//新執行緒的名稱就會是Thread-2
//以此類推.....還會有Thread-3,Thread-4,Thread-5,Thread-6...Thread-n
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
}
運行結果:

代碼優化:
使用一行代碼(鏈式編程):
MyThread.java
/*獲取執行緒的名稱:
* 1.使用Thread類中的getName()方法
* String getName() 回傳該執行緒的名稱
* 2.可以先獲取到當前正在執行的執行緒,使用執行緒中的方法getName()獲取執行緒的名稱
* static Thread currentThread() 回傳對當前正在執行的執行緒物件的參考,
* */
//第一步:定義一個Thread類的子類
public class MyThread extends Thread{
//第二步:重寫Thread類中的run方法,設定執行緒任務
@Override
public void run() {
//獲取執行緒的名稱
// String name = getName();
// System.out.println(name);
/* Thread thread = Thread.currentThread();//currentThread()或取當前執行緒
System.out.println(thread+"------這是執行緒");//列印當前執行緒
String threadName = thread.getName();//getName()獲取執行緒名稱
System.out.println(threadName+"---------------------這是執行緒名稱");//列印上一步獲取到的執行緒名稱*/
System.out.println(Thread.currentThread()+"------這是執行緒!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
System.out.println(Thread.currentThread().getName()+"---------------------這是執行緒名稱");
}
}
MyThreadTest.java
public class MyThreadTest {
public static void main(String[] args) {
//創建子類物件
MyThread myThread = new MyThread();
//呼叫start方法開啟一個新執行緒
myThread.start();//創建的新執行緒會去執行它自己重寫的run方法,即MyThread類中的run方法
//如果再創建一個新執行緒
new MyThread().start();//新執行緒的名稱就會是Thread-1
//如果又創建一個新執行緒
new MyThread().start();//新執行緒的名稱就會是Thread-2
//以此類推.....還會有Thread-3,Thread-4,Thread-5,Thread-6...Thread-n
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
}
運行結果:

獲取主執行緒名稱:
public class MyThreadTest {
public static void main(String[] args) {
//創建子類物件
MyThread myThread = new MyThread();
//呼叫start方法開啟一個新執行緒
myThread.start();//創建的新執行緒會去執行它自己重寫的run方法,即MyThread類中的run方法
//如果再創建一個新執行緒
new MyThread().start();//新執行緒的名稱就會是Thread-1
//如果又創建一個新執行緒
new MyThread().start();//新執行緒的名稱就會是Thread-2
//以此類推.....還會有Thread-3,Thread-4,Thread-5,Thread-6...Thread-n
new MyThread().start();
new MyThread().start();
new MyThread().start();
//獲取主執行緒名稱
System.out.println(Thread.currentThread().getName());
}
}
運行結果:

設定執行緒的名稱
兩種方式:
- 使用Thread類中的setName(自定義執行緒名)方法

- .創建一個帶引數的構造方法,引數傳遞執行緒的名稱;然后呼叫父類的帶引數的構造方法,把執行緒名稱傳遞個父類,讓父類Thread給子執行緒起一個名字!
- 使用Thread類中的setName(自定義執行緒名)方法
MyThread.java
/*設定執行緒的名稱:
* 1.使用Thread類中的setName()方法
* void setName(String name) 改變執行緒名稱,使之與引數 name 相同,
* 2.創建一個帶引數的構造方法,引數傳遞執行緒的名稱;
* 然后呼叫父類的帶引數的構造方法,把執行緒名稱傳遞個父類,讓父類Thread給子執行緒起一個名字!
* */
public class MyThread extends Thread{
//方式2
public MyThread() {
}
public MyThread(String name) {
super(name);//將name傳遞給父類
}
@Override
public void run() {
//獲取并列印執行緒名稱
System.out.println(Thread.currentThread().getName());
}
}
MyThreadTest.java
public class MyThreadTest {
public static void main(String[] args) {
//new一個子類物件,開啟新執行緒
MyThread myThread = new MyThread();
//先自定義一下執行緒名稱,然后再開啟新執行緒
myThread.setName("niu_niu");
myThread.start();
//new一個子類物件并直接傳入自定義執行緒名稱引數,開啟新執行緒
MyThread myThread1 = new MyThread("傳入自定義引數更改執行緒名稱");
myThread1.start();
//獲取主執行緒名稱
System.out.println(Thread.currentThread().getName());
}
}
運行結果:

sleep類
public static void sleep(long millis);--------->使當前正在執行的執行緒以指定的毫秒數暫停(暫時停止執行);
public class MyThreadTest {
public static void main(String[] args) {
//模擬秒表
for (int i = 0; i <= 60; i++) {
System.out.println(i);
//使用Thread類的sleep方法讓程式睡眠一秒鐘
try {
Thread.sleep(1000);//靜態方法通過類名直接呼叫,括號里單位為毫秒,1000毫秒==1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
運行結果:(每過一秒列印一個數字)

創建多執行緒的第二種方式-Runable介面(推薦使用)
撰寫java.lang.Runable介面的實作類,并重寫run()方法,該run()方法的方法體同樣是該執行緒的執行緒執行體,
但是!java.lang.Runable介面里沒有點start()方法,那么要怎么開啟執行緒呢?
不急,這么來就行-------->先創建一個java.lang.Runable介面的實作類的實作類物件,然后把實作類物件當作引數傳到Thread類里面,最后再去點start();這樣就開啟了一個新執行緒了~??
public class MyThread implements Runnable{
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("新執行緒名:"+name);
}
}
public class MyThreadTest {
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
}
//輸出100次"主執行緒"
for (int i = 1; i <= 10; i++) {
System.out.println("第" + i +"次主執行緒");
}
}
}
運行結果:

實作Runable介面創建多執行緒的好處:
- 避免了單繼承的局限性----------------一個類只能繼承一個類(一個人只能有一個親爹!),類繼承了Thread類就不能繼承其他的類,用Runable介面創建多執行緒之后,子類還可以繼承其他的類,實作其他的而介面!
- 增強了程式的擴展性,降低了程式的耦合性(解耦)----------實作Runable介面的方式,把設定執行緒任務和開啟新執行緒進行了分離(解耦)實作類中,重寫的run方法就是用來設定執行緒要執行的任務;而創建Threrad類物件,呼叫start()方法,就是用來開啟新執行緒;這樣我們可以傳給Thread類物件不同的引數,就可以控制執行緒執行不同的任務了~
匿名內部類的方式實作執行緒的創建
格式:
new 父類/介面(){
? 重寫父類/介面中的方法;
};
AnonymousInnerClass.java
public class AnonymousInnerClass {
public static void main(String[] args) {
//匿名內部類:執行緒的父類是Thread的方式
new Thread(){
@Override
public void run() {
System.out.println("新執行緒1:"+Thread.currentThread().getName());
}
}.start();
//匿名內部類:執行緒的介面是Runable的方式
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("新執行緒2:" + Thread.currentThread().getName());
}
};
new Thread(runnable).start();
}
}
運行結果:

執行緒安全問題
/**
* 實作多執行緒賣票案例
*/
public class MovieTicket implements Runnable{
//首先定義一個共享票源
private int ticket = 100;
//設定執行緒任務:賣票
@Override
public void run() {
//使用死回圈讓賣票操作一直重復
while (true){
//先判斷電影票是否存在
if (ticket>0){
//我在這里為了使程式在實驗的票數較少的情況下,出現重復賣票的幾率增加,特意添加一個睡眠陳述句
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//有票,(票遞減)===>ticket--
System.out.println(Thread.currentThread().getName()+"號視窗正在賣第"+ticket+"張票");
ticket--;
}
}
}
}
import junit.framework.TestCase;
public class MovieTicketTest extends TestCase {
public static void main(String[] args) {
//new一個Runable的實作類物件--->MovieTicket
MovieTicket movieTicket = new MovieTicket();
//用一個MovieTicket往三個執行緒的Thread里傳,模擬電影院3個賣票視窗一起賣100張電影票的效果
Thread thread1 = new Thread(movieTicket);
Thread thread2 = new Thread(movieTicket);
Thread thread3 = new Thread(movieTicket);
//開啟執行緒
thread1.start();
thread2.start();
thread3.start();
}
}
運行結果:

出現了重復的票和不存在的票!!!!!!!!!!
即出現了執行緒安全的問題!!!!!!!!!!
出現執行緒安全問題的原理

解決執行緒安全問題
執行緒同步
執行緒同步方式1:同步代碼塊
synchronized關鍵字可以用于方法中的某個區塊中,表示只對這個區塊中的資源進行互斥訪問,
格式:
synchroniuzed(同步鎖){
需要同步操作的代碼(可能會出現執行緒安全問題的代碼)(訪問了共享資料的代碼)
}
什么是同步鎖?
物件的同步鎖只是一個概念,可以想象為在物件上標記了一個鎖,
? 1.鎖物件可以是任意型別,
? 2.必須保證多個執行緒物件要使用同一把鎖,
? 3.鎖物件的作用:把同步代碼塊”鎖住“,只讓一個執行緒在代碼塊中執行
注意:在任何時候,最多允許一個執行緒擁有同步鎖,誰拿到同步鎖,誰就進入代碼塊,此時其他的執行緒只能在外等候(BLOCKED)
/**
* 實作多執行緒賣票案例
*/
public class MovieTicket implements Runnable{
//首先定義一個共享票源
private int ticket = 100;
//創建一個鎖物件(在run方法外面創建鎖物件是因為,如果在run方法里面創建鎖物件,那么每次開啟一條執行緒就會創建一個鎖隨想,這樣就不能保證鎖物件唯一了)
Object obj = new Object();
//設定執行緒任務:賣票
@Override
public void run() {
//使用死回圈讓賣票操作一直重復
while (true){
//創建同步代碼塊
synchronized (obj){
//先判斷電影票是否存在
if (ticket>0){
//我在這里為了使程式在實驗的票數較少的情況下,出現重復賣票的幾率增加,特意添加一個睡眠陳述句
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//有票,(票遞減)===>ticket--
System.out.println(Thread.currentThread().getName()+"號視窗正在賣第"+ticket+"張票");
ticket--;
}
}
}
}
}
import junit.framework.TestCase;
public class MovieTicketTest extends TestCase {
public static void main(String[] args) {
//new一個Runable的實作類物件--->MovieTicket
MovieTicket movieTicket = new MovieTicket();
//用一個MovieTicket往三個執行緒的Thread里傳,模擬電影院3個賣票視窗一起賣100張電影票的效果
Thread thread1 = new Thread(movieTicket);
Thread thread2 = new Thread(movieTicket);
Thread thread3 = new Thread(movieTicket);
//開啟執行緒
thread1.start();
thread2.start();
thread3.start();
}
}
運行結果:沒有出現重復票和不存在的票了,說明synchronized關鍵字有效解決了執行緒安全問題??????

該同步技術的原理
原理:使用了鎖物件,這個鎖物件又叫同步鎖或物件鎖,也叫物件監視器
個人理解:
當一個執行緒搶奪到CPU的執行權后,執行run方法時,會碰到synchronized代碼塊;
此時該執行緒就會檢查synchronized代碼塊是否有鎖物件,如果有,則會獲取到鎖物件,接著就執行同步代碼塊;
與此同時,另一個執行緒雖然也搶奪到了CPU的執行權,但是鎖物件此時已將被占用,所以其他搶奪到CPU使用權的執行緒就無法獲得鎖物件,這就導致沒有獲得鎖物件的執行緒無法進入同步代碼塊;
無法獲得鎖物件的執行緒就會進入阻塞狀態,只能等待那個已經獲取到鎖物件的執行緒執行完畢后把鎖物件釋放出來
于是乎,當多執行緒執行時,大家都去爭CPU并且還去爭著獲取唯一的鎖物件,當某一個執行緒獲取到鎖物件的時候,其它執行緒就會進入阻塞狀態,就只能干巴巴的等著鎖物件被釋放出來(獲取到鎖物件的執行緒執行完畢后就會把鎖物件釋放出來),然后大家再去搶奪被釋放出來的、唯一的鎖物件??????
這樣就達到了同步代碼塊每次只能由某一個執行緒執行一次,這樣模擬賣票程式就不會出現多個視窗賣同一張票或賣不存在的票的情況了~
執行緒同步方式2:同步方法
格式:
public synchroniuzed void method(){
需要同步操作的代碼(可能會出現執行緒安全問題的代碼)(訪問了共享資料的代碼)
}
MovieTicket.java
/**
* 實作多執行緒賣票案例
*/
public class MovieTicket implements Runnable{
//首先定義一個共享票源
private int ticket = 100;
//創建一個鎖物件(在run方法外面創建鎖物件是因為,如果在run方法里面創建鎖物件,那么每次開啟一條執行緒就會創建一個鎖隨想,這樣就不能保證鎖物件唯一了)
Object obj = new Object();
//設定執行緒任務:賣票
@Override
public void run() {
//使用死回圈讓賣票操作一直重復
while (true){
sellTickets();
}
}
//定義一個同步方法sellTickets(賣票)
public synchronized void sellTickets(){
//先判斷電影票是否存在
if (ticket>0){
//我在這里為了使程式在實驗的票數較少的情況下,出現重復賣票的幾率增加,特意添加一個睡眠陳述句
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//有票,(票遞減)===>ticket--
System.out.println(Thread.currentThread().getName()+"號視窗正在賣第"+ticket+"張票");
ticket--;
}
}
}
MovieTicketTest.java
import junit.framework.TestCase;
public class MovieTicketTest extends TestCase {
public static void main(String[] args) {
//new一個Runable的實作類物件--->MovieTicket
MovieTicket movieTicket = new MovieTicket();
//用一個MovieTicket往三個執行緒的Thread里傳,模擬電影院3個賣票視窗一起賣100張電影票的效果
Thread thread1 = new Thread(movieTicket);
Thread thread2 = new Thread(movieTicket);
Thread thread3 = new Thread(movieTicket);
//開啟執行緒
thread1.start();
thread2.start();
thread3.start();
}
}
運行結果:

該同步技術的原理
原理:同步方法也會把方法內部的代碼鎖住,只讓一個執行緒執行,它的鎖物件就是執行緒的實作類物件
還有一種靜態同步方法
/**
* 實作多執行緒賣票案例
*/
public class MovieTicket implements Runnable{
//首先定義一個共享票源
private static int ticket = 100;//靜態變數
//創建一個鎖物件(在run方法外面創建鎖物件是因為,如果在run方法里面創建鎖物件,那么每次開啟一條執行緒就會創建一個鎖隨想,這樣就不能保證鎖物件唯一了)
Object obj = new Object();
//設定執行緒任務:賣票
@Override
public void run() {
//使用死回圈讓賣票操作一直重復
while (true){
sellTicketsStatic();
}
}
//定義一個同步方法sellTickets(賣票)
public static synchronized void sellTicketsStatic(){//靜態方法
//先判斷電影票是否存在
if (ticket>0){
//我在這里為了使程式在實驗的票數較少的情況下,出現重復賣票的幾率增加,特意添加一個睡眠陳述句
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//有票,(票遞減)===>ticket--
System.out.println(Thread.currentThread().getName()+"號視窗正在賣第"+ticket+"張票");
ticket--;
}
}
}
import junit.framework.TestCase;
public class MovieTicketTest extends TestCase {
public static void main(String[] args) {
//new一個Runable的實作類物件--->MovieTicket
MovieTicket movieTicket = new MovieTicket();
//用一個MovieTicket往三個執行緒的Thread里傳,模擬電影院3個賣票視窗一起賣100張電影票的效果
Thread thread1 = new Thread(movieTicket);
Thread thread2 = new Thread(movieTicket);
Thread thread3 = new Thread(movieTicket);
//開啟執行緒
thread1.start();
thread2.start();
thread3.start();
}
}
運行結果:

靜態同步方法的鎖物件是實作類物件.class;(比如MovieTicket.classs)
執行緒同步方式3:Lock鎖機制
Lock 實作提供了比使用 synchronized 方法和陳述句可獲得的更廣泛的鎖定操作,此實作允許更靈活的結構,可以具有差別很大的屬性,可以支持多個相關的 Condition 物件,
Lock介面中的常用方法:
| 方法摘要 | |
|---|---|
void |
lock() 獲取鎖, |
void |
lockInterruptibly() 如果當前執行緒未被中斷,則獲取鎖, |
Condition |
newCondition() 回傳系結到此 Lock 實體的新 Condition 實體, |
boolean |
tryLock() 僅在呼叫時鎖為空閑狀態才獲取該鎖, |
boolean |
[tryLock](../../../../java/util/concurrent/locks/Lock.html#tryLock(long, java.util.concurrent.TimeUnit))(long time, TimeUnit unit) 如果鎖在給定的等待時間內空閑,并且當前執行緒未被中斷,則獲取鎖, |
void |
unlock() 釋放鎖, |
lock()獲取鎖和unlock()釋放鎖使用步驟:3步
- 在成員位置創建一個
ReentrantLock物件 - 在可能出現執行緒安全問題的代碼前面呼叫
lock()方法獲取鎖 - 在可能出現執行緒安全問題的代碼后面呼叫
unlock()方法釋放鎖
代碼如下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 實作多執行緒賣票案例
*/
public class MovieTicket implements Runnable{
//首先定義一個共享票源
private int ticket = 100;
//在成員位置創建一個`ReentrantLock`物件
Lock lock = new ReentrantLock();
//設定執行緒任務:賣票
@Override
public void run() {
//使用死回圈讓賣票操作一直重復
while (true){
//在可能出現執行緒安全問題的代碼前面呼叫`lock()`方法獲取鎖
lock.lock();
//先判斷電影票是否存在
if (ticket>0){
//我在這里為了使程式在實驗的票數較少的情況下,出現重復賣票的幾率增加,特意添加一個睡眠陳述句
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//有票,(票遞減)===>ticket--
System.out.println(Thread.currentThread().getName()+"號視窗正在賣第"+ticket+"張票");
ticket--;
}
//在可能出現執行緒安全問題的代碼后面呼叫`unlock()`方法釋放鎖
lock.unlock();
}
}
}
結果略,
上述代碼提高效率寫法:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 實作多執行緒賣票案例
*/
public class MovieTicket implements Runnable{
//首先定義一個共享票源
private int ticket = 100;
//在成員位置創建一個`ReentrantLock`物件
Lock lock = new ReentrantLock();
//設定執行緒任務:賣票
@Override
public void run() {
//使用死回圈讓賣票操作一直重復
while (true){
//在可能出現執行緒安全問題的代碼前面呼叫`lock()`方法獲取鎖
lock.lock();
//先判斷電影票是否存在
if (ticket>0){
//我在這里為了使程式在實驗的票數較少的情況下,出現重復賣票的幾率增加,特意添加一個睡眠陳述句
try {
Thread.sleep(100);
//有票,(票遞減)===>ticket--
System.out.println(Thread.currentThread().getName()+"號視窗正在賣第"+ticket+"張票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//在可能出現執行緒安全問題的代碼后面呼叫`unlock()`方法釋放鎖
lock.unlock();//這樣寫,無論程式是否例外都會釋放鎖,提高程式效率
}
}
}
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/276069.html
標籤:Java
