public class Tickets implements Runnable { private int num = 100; //(1) @Override public void run() { // (2) // 死循環,一直處於能夠售票狀態 while(true) { // (3) if(num>0) { // (4) System.out.println(Thread.currentThread().getName()+" 第 "+ num-- + " 張票售出"); //(5) } } } } public class TicketsDemo { public static void main(String[] args) { //(6) Tickets t = new Tickets(); // (7) new Thread(t).start(); // (8) new Thread(t).start(); // (9) new Thread(t).start(); // (10) } }
若每一個線程中對全局變量、靜態變量只有讀操做,而無寫操做,通常來講,這個全局變量是線程安全的;如有多個線程同時執行寫操做,通常都須要考慮線程同步,不然的話就可能影響線程安全。java
synchronized (鎖對象) { 可能會產生線程安全問題的代碼 }
同步代碼塊中的鎖對象能夠是任意的對象;但多個線程時,要使用同一個鎖對象纔可以保證線程安全。設計模式
使用同步代碼塊,對電影院賣票案例中Ticket類進行以下代碼修改:安全
/* 經過線程休眠,出現安全問題 解決安全問題,Java程序,提供同步技術 公式: syncronized (任意對象){ 線程要操做的共享數據 } */ public class Tickets implements Runnable { // 定義出售的票數 private int num = 100; Object obj = new Object(); // 建立對象,用於同步 @Override public void run() { // 死循環,一直處於能夠售票狀態 while(true) { // 線程共享數據,保證安全,加入同步代碼塊 synchronized (obj) { if(num>0) { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" 第 "+ num-- + " 張票售出"); } } } } }
public synchronized void method(){ 可能會產生線程安全問題的代碼 }
/* 採用同步方法的形式解決線程安全問題 好處:代碼量少,簡潔 作法:將線程共享數據和同步抽取到方法中 */ public class Tickets implements Runnable { private int num = 100; @Override public void run() { // 死循環,一直處於能夠售票狀態 while(true) { payTicket(); } } public synchronized void payTicket() { if(num>0) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" 第 "+ num-- + " 張票售出"); } } }
public static synchronized void method(){ // 可能會產生線程安全問題的代碼 }
ReentrantLock
void lock()
:得到鎖。void unlock()
:釋放鎖。import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* 使用JDK1.5+的接口Locl,替換同步代碼塊,實現線程安全 具體使用: Lock接口中的方法: lock(); // 獲取鎖 unlock(); // 釋放鎖 實現類:ReentrantLock */ public class Tickets implements Runnable { // 存儲票數 private static int num = 100; //在類的成員位置,建立Lock接口的實現類對象 private Lock lock = new ReentrantLock(); @Override public void run() { // 死循環,一直處於能夠售票狀態 while(true) { // 調用Lock接口中的方法,獲取鎖 lock.lock(); try { if(num>0) { Thread.sleep(200); System.out.println(Thread.currentThread().getName()+" 第 "+ num-- + " 張票售出"); } }catch (InterruptedException e) { e.printStackTrace(); }finally { // 釋放鎖,調用unlock方法 lock.unlock(); } } } }
鎖的嵌套狀況以下:多線程
synchronzied(A鎖){ synchronized(B鎖){ } } synchronzied(B鎖){ synchronized(A鎖){ } }
兩個線程每一個得到一個鎖,且都須要對方的鎖才能繼續執行,所以都會一直除以阻塞狀態,沒法恢復,出現死鎖。ide
咱們進行下死鎖狀況的代碼演示:測試
// 定義鎖對象類 /* 不容許任何類建立該對象 只能經過類名調用靜態成員調用,不容許new 保證了鎖的惟一性 */ public class LockA { private LockA() {} public final static LockA locka = new LockA(); } public class LockB { private LockB() {} public final static LockB lockb = new LockB(); } // 線程任務類 public class DeadLock implements Runnable{ private int i = 0; @Override public void run() { while(true) { if(i%2==0) { // 先進入A同步,再進入B同步 synchronized (LockA.locka) { System.out.println(i+" --> if...locka"); synchronized (LockB.lockb) { System.out.println(i+" --> if...lockb"); } } }else { // 先進入B同步,再進入B同步 synchronized (LockB.lockb) { System.out.println(i+" --> else...lockb"); synchronized (LockA.locka) { System.out.println(i+" --> else...locka"); } } } i++; } } } // 測試類 public class DeadLockDemo { public static void main(String[] args) { DeadLock deadLock = new DeadLock(); new Thread(deadLock).start(); new Thread(deadLock).start(); } } // 運行結果: 0 --> if...locka 0 --> if...lockb 1 --> else...lockb 1 --> if...locka
Resource.javathis
/* 定義資源類,有2個成員變量: name,sex 同時有兩個線程,對資源中的變量操做 1個對name,sex賦值 1個對name,sex作變量的輸出打印 */ public class Resource { public String name; public String sex; }
Input.java線程
/* 輸入線程: 對資源對象Resource中的成員變量賦值 要求: 一次賦值:張三,男 另外一次:李四,女 */ public class Input implements Runnable { private Resource r; public Input(Resource r) { this.r = r; } @Override public void run() { int i = 0; while(true) { if(i%2==0) { r.name = "張三"; r.sex = "男"; }else { r.name = "lisi"; r.sex = "nv"; } i++; } } }
Output.java設計
/* 輸出線程:對資源對象Resource中的成員變量輸出值 */ public class Output implements Runnable { private Resource r; public Output(Resource r) { this.r = r; } @Override public void run() { while(true) { System.out.println("姓名:"+r.name + ", 性別:"+r.sex); } } }
ThreadDemo.javacode
/* 開啓輸入線程和輸出線程,實現賦值和打印 */ public class ThreadDemo { public static void main(String[] args) { Resource r = new Resource(); //共享數據 Input in = new Input(r); Output out = new Output(r); new Thread(in).start(); new Thread(out).start(); } }
此時會出現問題:打印出的結果並非想要的結果
姓名:lisi, 性別:nv 姓名:張三, 性別:nv 姓名:lisi, 性別:男 姓名:lisi, 性別:nv 姓名:lisi, 性別:nv 姓名:張三, 性別:男
Input.java修改
/* 輸入線程: 對資源對象Resource中的成員變量賦值 要求: 一次賦值:張三,男 另外一次:李四,女 */ public class Input implements Runnable { private Resource r; public Input(Resource r) { this.r = r; } @Override public void run() { int i = 0; while(true) { synchronized (r) { if(i%2==0) { r.name = "張三"; r.sex = "男"; }else { r.name = "lisi"; r.sex = "nv"; } } i++; } } }
Input.java修改
/* 輸入線程: 對資源對象Resource中的成員變量賦值 要求: 一次賦值:張三,男 另外一次:李四,女 */ public class Input implements Runnable { private Resource r; public Input(Resource r) { this.r = r; } @Override public void run() { int i = 0; while(true) { synchronized (r) { if(i%2==0) { r.name = "張三"; r.sex = "男"; }else { r.name = "lisi"; r.sex = "nv"; } } i++; } } }
Output.java修改:
/* 輸出線程:對資源對象Resource中的成員變量輸出值 */ public class Output implements Runnable { private Resource r; public Output(Resource r) { this.r = r; } @Override public void run() { while(true) { synchronized (r) { System.out.println("姓名:"+r.name + ", 性別:"+r.sex); } } } }
此時還有問題:輸出沒有交替進行
姓名:張三, 性別:男 姓名:張三, 性別:男 姓名:張三, 性別:男 姓名:lisi, 性別:nv 姓名:lisi, 性別:nv 姓名:lisi, 性別:nv 姓名:lisi, 性別:nv
Resource.java修改
/* 定義資源類,有2個成員變量: name,sex 同時有兩個線程,對資源中的變量操做 1個對name,sex賦值 1個對name,sex作變量的輸出打印 */ public class Resource { public String name; public String sex; public boolean flag = false; }
Input.java修改
/* 輸入線程: 對資源對象Resource中的成員變量賦值 要求: 一次賦值:張三,男 另外一次:李四,女 */ public class Input implements Runnable { private Resource r; public Input(Resource r) { this.r = r; } @Override public void run() { int i = 0; while(true) { synchronized (r) { if(r.flag) { // 標記是true,等待 try { r.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } if(i%2==0) { r.name = "張三"; r.sex = "男"; }else { r.name = "lisi"; r.sex = "nv"; } // 標記改成true,將對方線程喚醒 r.flag = true; r.notify(); } i++; } } }
Output.java修改
/* 輸出線程:對資源對象Resource中的成員變量輸出值 */ public class Output implements Runnable { private Resource r; public Output(Resource r) { this.r = r; } @Override public void run() { while(true) { synchronized (r) { if(!r.flag) { // 判斷標記,false,等待 try { r.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("姓名:"+r.name + ", 性別:"+r.sex); r.flag = false; r.notify(); } } } }
同步代碼塊
synchronized (鎖對象){ 可能產生線程安全問題的代碼 }
同步方法
public synchronized void method() 可能產生線程安全問題的代碼 } // 同步方法中的鎖對象是 this
靜態同步方法
public synchronized void method() 可能產生線程安全問題的代碼 } // 靜態同步方法中的鎖對象是 類名.class