線程安全問題:java
問題:當某個線程操做共享數據中,還沒有操做完成時,其餘線程參與進來,也操做共享數據。安全
解決方法:當一個線程在操做共享數據時,其餘線程不能參與進來。即便操做中的線程出現阻塞,也不能改變。多線程
例子:建立三個窗口賣票,總票數爲100張,使用實現Runable接口的方式。ide
JAVA 中經過同步機制 來解決線程的安全問題。this
方式1、同步代碼塊
synchronized(同步監視器){spa
//須要被同步的代碼 線程
} code
說明:對象
1.操做共享數據的代碼,爲須要被同步的代碼繼承
2.共享數據,多個線程共同操做的變量,
3.同步監視器,俗稱:鎖。任何一個類的對象,均可以充當鎖。
要求:多個線程必需要共用同一把鎖。
補充:在實現Runable接口建立多線程的方式中,咱們能夠考慮使用this 充當同步監視器
使用實現 Runable方式:
public class TheardWindow { public static void main(String[] args) { TicketWindow w1 = new TicketWindow(); Thread t1=new Thread(w1); Thread t2= new Thread(w1); Thread t3 =new Thread(w1); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start();; t2.start(); t3.start(); } } class TicketWindow implements Runnable{ private int ticket=100; final Object obj=new Object(); @Override public void run() { while (true){ //synchronized (obj){ synchronized (this){ if (ticket>0){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":賣票 票號:"+ticket); ticket--; }else { break; } } } } }
使用繼承方式:
package org.zhanghl; public class WindowB { public static void main(String[] args) { TicketRun t1= new TicketRun(); TicketRun t2= new TicketRun(); TicketRun t3 =new TicketRun(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } } class TicketRun extends Thread{ private static int ticket=100; private static Object obj=new Object(); @Override public void run() { while (true){ //synchronized (obj){ //能夠用當前類作鎖 synchronized (WindowB.class){ if (ticket>0){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":賣票 票號:"+ticket); ticket--; }else { break; } } } } }
使用同步代碼塊 解決繼承Thread類的方式的線程安全問題
注意:
1. 共享數據與鎖對象都應是惟一的,這裏爲static.
2. 用繼承Thread類建立多線程的方式時,慎用this充當同步監視器,考慮使用當前類對象作鎖。Class只會加載一次。
3. 同步的方式,優勢是解決了線程的安全問題,
缺點是操做同步代碼時,只能由一個線程參與,其餘線程等待。先當與是一個線程的過程,效率低。
方式2、同步方法
若是操做共享數據的代碼完整的聲明在一個方法中。咱們不妨將此方法聲明同步的
package org.zhanghl; /* * 使用同步方法解決實現Runable接口的線程安全問題 * */ class WindowC1 implements Runnable { private int ticket = 100; final Object obj = new Object(); @Override public void run() { while (true) { if (ticket > 0) { show(); } else { break; } } } /*同步監視器:this */ private synchronized void show() { if (ticket > 0) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":賣票 票號:" + ticket); ticket--; } } } /* *使用同步方法處理繼承Thread類的方式中的線程安全問題 * */ class WindowC2 extends Thread{ private static int ticket = 100; final Object obj = new Object(); @Override public void run() { while (true) { if (ticket > 0) { show(); } else { break; } } } /*同步監視器:this */ private static synchronized void show() { /*此時同步監視器是 WindowC2.class*/ //private synchronized void show() 此種方式是錯誤的 if (ticket > 0) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":賣票 票號:" + ticket); ticket--; } } } public class WindowC { public static void impl_show(){ WindowC1 w1 = new WindowC1(); Thread t1 = new Thread(w1); Thread t2 = new Thread(w1); Thread t3 = new Thread(w1); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } public static void extd_show(){ WindowC2 t1=new WindowC2(); WindowC2 t2=new WindowC2(); WindowC2 t3=new WindowC2(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } public static void main(String[] args) { /*實現方式,同步方法*/ //impl_show(); /*繼承方式,同步方法*/ extd_show(); } }
總結:
1.同步方法仍然涉及到同步監視器,只是不須要咱們顯示的聲明。
2.非靜態的同步方法,同步監視器是this.
靜態的同步方法,同步監視器是 當前類自己。