同時知足如下兩個條件時:java
1,多個線程在操做共享的數據。
2,操做共享數據的線程代碼有多條。面試
當一個線程在執行操做共享數據的多條代碼過程當中,其餘線程參與了運算,就會致使線程安全問題的產生。設計模式
例1:四個線程賣100張票安全
public class TicketDemo implements Runnable { private int tickets = 100; public void run() { while (true) { if (tickets > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "....sale:...." + tickets--); } } } public static void main(String[] args) { TicketDemo ticketDemo = new TicketDemo(); Thread t1 = new Thread(ticketDemo); Thread t2 = new Thread(ticketDemo); Thread t3 = new Thread(ticketDemo); Thread t4 = new Thread(ticketDemo); t1.start(); t2.start(); t3.start(); t4.start(); } }
Thread-3....sale....100 Thread-2....sale....99 Thread-0....sale....97 Thread-1....sale....98 Thread-3....sale....96 Thread-1....sale....94 Thread-0....sale....94 Thread-2....sale....95 Thread-1....sale....93 Thread-0....sale....92 Thread-2....sale....92 Thread-3....sale....92 Thread-0....sale....91 Thread-2....sale....89 Thread-3....sale....90 Thread-1....sale....91 Thread-1....sale....88 Thread-3....sale....86 Thread-0....sale....88 Thread-2....sale....87 Thread-2....sale....84 Thread-3....sale....84 Thread-1....sale....85 Thread-0....sale....83 Thread-1....sale....82 Thread-0....sale....80 Thread-3....sale....79 Thread-2....sale....81 Thread-3....sale....78 Thread-2....sale....75 Thread-1....sale....76 Thread-0....sale....77 Thread-2....sale....74 Thread-1....sale....71 Thread-0....sale....73 Thread-3....sale....72 Thread-1....sale....70 Thread-0....sale....68 Thread-3....sale....69 Thread-2....sale....67 Thread-2....sale....66 Thread-3....sale....64 Thread-0....sale....63 Thread-1....sale....65 Thread-2....sale....62 Thread-0....sale....62 Thread-1....sale....60 Thread-3....sale....61 Thread-2....sale....59 Thread-0....sale....57 Thread-3....sale....58 Thread-1....sale....59 Thread-0....sale....56 Thread-1....sale....56 Thread-3....sale....55 Thread-2....sale....56 Thread-1....sale....54 Thread-2....sale....54 Thread-0....sale....54 Thread-3....sale....53 Thread-0....sale....52 Thread-3....sale....52 Thread-2....sale....50 Thread-1....sale....51 Thread-2....sale....49 Thread-0....sale....49 Thread-3....sale....48 Thread-1....sale....48 Thread-2....sale....46 Thread-0....sale....44 Thread-3....sale....45 Thread-1....sale....47 Thread-1....sale....43 Thread-0....sale....42 Thread-2....sale....42 Thread-3....sale....41 Thread-1....sale....40 Thread-0....sale....39 Thread-3....sale....39 Thread-2....sale....40 Thread-2....sale....38 Thread-1....sale....37 Thread-3....sale....35 Thread-0....sale....36 Thread-3....sale....34 Thread-1....sale....33 Thread-0....sale....32 Thread-2....sale....31 Thread-3....sale....30 Thread-1....sale....29 Thread-0....sale....29 Thread-2....sale....28 Thread-3....sale....27 Thread-0....sale....25 Thread-1....sale....26 Thread-2....sale....24 Thread-1....sale....23 Thread-0....sale....23 Thread-3....sale....22 Thread-2....sale....21 Thread-1....sale....20 Thread-3....sale....20 Thread-0....sale....20 Thread-2....sale....19 Thread-3....sale....16 Thread-0....sale....17 Thread-1....sale....18 Thread-2....sale....15 Thread-0....sale....13 Thread-1....sale....12 Thread-3....sale....14 Thread-2....sale....11 Thread-3....sale....10 Thread-0....sale....8 Thread-1....sale....9 Thread-2....sale....7 Thread-3....sale....6 Thread-0....sale....5 Thread-1....sale....4 Thread-2....sale....3 Thread-1....sale....2 Thread-3....sale....2 Thread-2....sale....1 Thread-0....sale....2
觀察結果,咱們發現會有多個線程賣到同一張票和賣到0號票的狀況,這就是線程安全問題。多線程
將多條操做共享數據的線程代碼封裝起來,當有線程在執行這些代碼的時候,其餘線程不能夠參與運算。ide
當前線程把這些代碼都執行完畢後,其餘線程才能夠參與運算。函數
在java中,用同步代碼塊就能夠解決這個問題。this
同步代碼塊的格式:
synchronized(對象)
{
須要被同步的代碼 ;
}spa
這個對象通常稱爲同步鎖。線程
同步的前提:同步中必須有多 個線程並使用同一個鎖。
同步的好處:解決了線程的安全問題。
同步的弊端:相對下降了效率,由於同步外的線程的都會判斷同步鎖。
解決例1的線程安全問題代碼:
public class TicketDemo implements Runnable { private int tickets = 100; Object obj = new Object(); public void run() { while (true) { synchronized (obj) { if (tickets > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "....sale...." + tickets--); } } } } public static void main(String[] args) { TicketDemo ticketDemo = new TicketDemo(); Thread t1 = new Thread(ticketDemo); Thread t2 = new Thread(ticketDemo); Thread t3 = new Thread(ticketDemo); Thread t4 = new Thread(ticketDemo); t1.start(); t2.start(); t3.start(); t4.start(); } }
同步函數使用的鎖是 this。
靜態的同步函數使用的鎖是該函數所屬 字節碼文件對象 ,能夠用 getClass()方法獲取,也能夠用 當前類名.class 表示。
同步函數和同步代碼塊的區別:
同步函數的鎖是固定的this。
同步代碼塊的鎖是任意的對象。
建議使用同步代碼塊。
1 class Ticket implements Runnable { 2 private static int num = 100; 3 boolean flag = true; 4 5 public void run() { 6 if (flag) 7 while (true) { 8 synchronized (Ticket.class)//(this.getClass())同步代碼塊 9 { 10 if (num > 0) { 11 try { 12 Thread.sleep(10); 13 } catch (InterruptedException e) { 14 } 15 System.out.println(Thread.currentThread().getName() + ".....obj...." + num--); 16 } 17 } 18 } 19 else 20 while (true) 21 this.show(); 22 } 23 24 public static synchronized void show()//同步函數 25 { 26 if (num > 0) { 27 try { 28 Thread.sleep(10); 29 } catch (InterruptedException e) { 30 } 31 System.out.println(Thread.currentThread().getName() + ".....function...." + num--); 32 } 33 } 34 } 35 36 class StaticSynFunctionLockDemo { 37 public static void main(String[] args) { 38 Ticket t = new Ticket(); 39 40 Thread t1 = new Thread(t); 41 Thread t2 = new Thread(t); 42 43 t1.start(); 44 try { 45 Thread.sleep(10); 46 } catch (InterruptedException e) { 47 } 48 t.flag = false; 49 t2.start(); 50 } 51 }
同步嵌套時,兩個線程你拿了個人鎖,我拿了你的鎖,都不釋放,形成死鎖。
能夠記一套死鎖狀況代碼,面試可能用獲得。
死鎖狀況:
class Testa implements Runnable { private boolean flag; Testa(boolean flag) { this.flag = flag; } public void run() { if (flag) { while (true) synchronized (MyLock.locka) { System.out.println(Thread.currentThread().getName() + "..if locka...."); synchronized (MyLock.lockb) { System.out.println(Thread.currentThread().getName() + "..if lockb...."); } } } else { while (true) synchronized (MyLock.lockb) { System.out.println(Thread.currentThread().getName() + "..else lockb...."); synchronized (MyLock.locka) { System.out.println(Thread.currentThread().getName() + "..else locka...."); } } } } } class MyLock { public static final Object locka = new Object(); public static final Object lockb = new Object(); } class DeadLockTest { public static void main(String[] args) { Testa a = new Testa(true); Testa b = new Testa(false); Thread t1 = new Thread(a); Thread t2 = new Thread(b); t1.start(); t2.start(); } }
//餓漢式 class Single { private static final Single s = new Single(); private Single(){} public static Single getInstance() { return s; } } //懶漢式 /* *加入同步是爲了解決多線程安全問題。 * *加入雙重判斷不用每次都判斷是否上鎖,是爲了解決效率問題。 **/ class Single { private static Single s = null; private Single(){} public static Single getInstance() { if(s==null) { synchronized(Single.class) { if(s==null) // -->0 -->1 s = new Single(); } } return s; } }
開發用餓漢式,沒有線程安全問題。——餓漢式在類建立的同時就已經建立好一個靜態的對象供系統使用,之後再也不改變,因此天生是線程安全的。
面試懶漢式,記住如何解決線程安全問題。