三、線程同步
阿新 • • 發佈:2018-07-11
第一個 runnable 執行 兩個 rgs 分析 功能 thread his
回到頂部
利用多線程模擬 3 個窗口賣票
回到頂部第一種方法:繼承 Thread 類
創建窗口類 TicketSell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package com.ys.thread;
public class TicketSell extends Thread{
//定義一共有 50 張票,註意聲明為 static,表示幾個窗口共享
private static int num = 50 ;
//調用父類構造方法,給線程命名
public TicketSell(String string) {
super (string);
}
@Override
public void run() {
//票分 50 次賣完
for ( int i = 0 ; i < 50 ;i ++){
if (num > 0 ){
try {
sleep( 10 ); //模擬賣票需要一定的時間
} catch (InterruptedException e) {
// 由於父類的 run()方法沒有拋出任何異常,根據繼承的原則,子類拋出的異常不能大於父類, 故我們這裏也不能拋出異常
e.printStackTrace();
}
System.out.println( this .currentThread().getName()+ "賣出一張票,剩余" +(--num)+ "張" );
}
}
}
}
|
創建主線程測試:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.ys.thread;
public class TestTicket {
public static void main(String[] args) {
//創建 3 個窗口
TicketSell t1 = new TicketSell( "A窗口" );
TicketSell t2 = new TicketSell( "B窗口" );
TicketSell t3 = new TicketSell( "C窗口" );
//啟動 3 個窗口進行買票
t1.start();
t2.start();
t3.start();
}
}
|
結果:這裏我們省略了一些,根據電腦配置,結果會隨機出現不同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
B窗口賣出一張票,剩余 48 張
A窗口賣出一張票,剩余 47 張
C窗口賣出一張票,剩余 49 張
C窗口賣出一張票,剩余 46 張
B窗口賣出一張票,剩余 44 張
A窗口賣出一張票,剩余 45 張
A窗口賣出一張票,剩余 43 張
...
C窗口賣出一張票,剩余 5 張
A窗口賣出一張票,剩余 4 張
B窗口賣出一張票,剩余 3 張
A窗口賣出一張票,剩余 2 張
C窗口賣出一張票,剩余 3 張
B窗口賣出一張票,剩余 1 張
C窗口賣出一張票,剩余 0 張
A窗口賣出一張票,剩余- 1 張
|
第二種方法:實現 Runnable 接口
創建窗口類 TicketSellRunnable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package com.ys.thread;
public class TicketSellRunnable implements Runnable{
//定義一共有 50 張票,繼承機制開啟線程,資源是共享的,所以不用加 static
private int num = 50 ;
@Override
public void run() {
//票分 50 次賣完
for ( int i = 0 ; i < 50 ;i ++){
if (num > 0 ){
try {
//模擬賣一次票所需時間
Thread.sleep( 10 );
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "賣出一張票,剩余" +(--num)+ "張" );
}
}
}
}
|
創建主線程測試:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.ys.thread;
public class TicketSellRunnableTest {
public static void main(String[] args) {
TicketSellRunnable t = new TicketSellRunnable();
Thread t1 = new Thread(t, "A窗口" );
Thread t2 = new Thread(t, "B窗口" );
Thread t3 = new Thread(t, "C窗口" );
t1.start();
t2.start();
t3.start();
}
}
|
結果:同理為了篇幅我們也省略了中間的一些結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
B窗口賣出一張票,剩余 49 張
C窗口賣出一張票,剩余 48 張
A窗口賣出一張票,剩余 49 張
B窗口賣出一張票,剩余 47 張
A窗口賣出一張票,剩余 45 張
......
A窗口賣出一張票,剩余 4 張
C窗口賣出一張票,剩余 5 張
A窗口賣出一張票,剩余 3 張
B窗口賣出一張票,剩余 2 張
C窗口賣出一張票,剩余 1 張
B窗口賣出一張票,剩余 0 張
A窗口賣出一張票,剩余- 2 張
C窗口賣出一張票,剩余- 1 張
|
結果分析:這裏出現了票數為 負數的情況,這在現實生活中肯定是不存在的,那麽為什麽會出現這樣的情況呢?
解決辦法分析:即我們不能同時讓超過兩個以上的線程進入到 if(num>0)的代碼塊中,不然就會出現上述的錯誤。我們可以通過以下三個辦法來解決:
1、使用 同步代碼塊
2、使用 同步方法
3、使用 鎖機制
①、使用同步代碼塊
1 2 3 4 5 6 7 8 9 |
語法:
synchronized (同步鎖) {
//需要同步操作的代碼
}
同步鎖:為了保證每個線程都能正常的執行原子操作,Java 線程引進了同步機制;同步鎖也叫同步監聽對象、同步監聽器、互斥鎖;
Java程序運行使用的任何對象都可以作為同步監聽對象,但是一般我們把當前並發訪問的共同資源作為同步監聽對象
註意:同步鎖一定要保證是確定的,不能相對於線程是變化的對象;任何時候,最多允許一個線程拿到同步鎖,誰拿到鎖誰進入代碼塊,而其他的線程只能在外面等著
|
實例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public void run() {
//票分 50 次賣完
for ( int i = 0 ; i < 50 ;i ++){
//這裏我們使用當前對象的字節碼對象作為同步鎖
synchronized ( this .getClass()) {
if (num > 0 ){
try {
//模擬賣一次票所需時間
Thread.sleep( 10 );
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "賣出一張票,剩余" +(--num)+ "張" );
}
}
}
}
|
②、使用 同步方法
語法:即用 synchronized 關鍵字修飾方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Override
public void run() {
//票分 50 次賣完
for ( int i = 0 ; i < 50 ;i ++){
sell();
}
}
private synchronized void sell(){
if (num > 0 ){
try {
//模擬賣一次票所需時間
Thread.sleep( 10 );
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "賣出一張票,剩余" +(--num)+ "張" );
}
}
|
註意:不能直接用 synchronized 來修飾 run() 方法,因為如果這樣做,那麽就會總是第一個線程進入其中,而這個線程執行完所有操作,即賣完所有票了才會出來。
③、使用 鎖機制
1 |
public interface Lock
|
主要方法:
常用實現類:
1 2 3 |
public class ReentrantLock
extends Object
implements Lock, Serializable<br> //一個可重入互斥Lock具有與使用synchronized方法和語句訪問的隱式監視鎖相同的基本行為和語義,但具有擴展功能。
|
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package com.ys.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketSellRunnable implements Runnable{
//定義一共有 50 張票,繼承機制開啟線程,資源是共享的,所以不用加 static
private int num = 50 ;
//創建一個鎖對象
Lock l = new ReentrantLock();
@Override
public void run() {
//票分 50 次賣完
for ( int i = 0 ; i < 50 ;i ++){
//獲取鎖
l.lock();
try {
if (num > 0 ){
//模擬賣一次票所需時間
Thread.sleep( 10 );
System.out.println(Thread.currentThread().getName()+ "賣出一張票,剩余" +(--num)+ "張" );
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//釋放鎖
l.unlock();
}
}
}
private void sell(){
}
}
|
三、線程同步