① 操做共享數據的代碼,即爲須要被同步的代碼。(不能包含代碼多了,也不能包含代碼少了)
② 共享數據:多個線程共同操做的變量。好比:ticket就是共享數據。
③ 同步監視器,俗稱:鎖。任何一個類的對象,均可以充當鎖。
要求:多個線程必需要共用同一把鎖。
補充:在實現Runnable接口建立多線程的方式中,咱們能夠考慮使用this充當同步監視器。java
class RWindow2 extends Thread { public static int ticket = 100; private static Object obj = new Object(); @Override public void run() { while (true) { //正確的 // synchronized (obj){ synchronized (RWindow2.class) {//Class clazz = Window2.class,Window2.class只會加載一次 // synchronized (this) {//錯誤的方式:this表明着t1,t2,t3三個對象 if (ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":賣票,票號:" + ticket); ticket--; } else { break; } } } } } public class WindowTest2 { public static void main(String[] args) { RWindow2 rWindow1 = new RWindow2(); RWindow2 rWindow2 = new RWindow2(); RWindow2 rWindow3 = new RWindow2(); rWindow1.setName("窗口1"); rWindow2.setName("窗口2"); rWindow3.setName("窗口3"); rWindow1.start(); rWindow2.start(); rWindow3.start(); } }
class RWindow implements Runnable { private int ticket = 100; // Object obj = new Object(); @Override public void run() { while(true) { // synchronized(obj) {//方法一 synchronized(this) { //方式二:此時的this:惟一的RWindow的對象 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":賣票,票號:" + ticket); ticket--; } else { break; } } } } } public class WindowTest1 { public static void main(String[] args) { RWindow rWindow = new RWindow(); Thread thread1 = new Thread(rWindow); thread1.setName("窗口一"); thread1.start(); Thread thread2 = new Thread(rWindow); thread2.setName("窗口二"); thread2.start(); Thread thread3 = new Thread(rWindow); thread3.setName("窗口三"); thread3.start(); } }
① 同步方法仍然涉及到同步監視器,只是不須要咱們顯式的聲明。
② 非靜態的同步方法,同步監視器是:this。
③ 靜態的同步方法,同步監視器是:當前類自己。算法
class RWindow2 implements Runnable { private int ticket = 100; @Override public void run() { while (true) { show(); } } public synchronized void show() {//同步監視器:this // synchronized(this) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":賣票,票號:" + ticket); ticket--; } // } } } public class WindowTest2 { public static void main(String[] args) { RWindow2 rWindow = new RWindow2(); Thread thread1 = new Thread(rWindow); thread1.setName("窗口一"); thread1.start(); Thread thread2 = new Thread(rWindow); thread2.setName("窗口二"); thread2.start(); Thread thread3 = new Thread(rWindow); thread3.setName("窗口三"); thread3.start(); } }
class RWindow3 extends Thread { private static int ticket = 100; @Override public void run() { while (true) { show(); } } private static synchronized void show() {//同步監聽器:Window4.class // private synchronized void show() {//同步監聽器:rWindow1,rWindow2,rWindow3 此種解決方式是錯誤的 if (ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":賣票,票號:" + ticket); ticket--; } } public class WindowTest3 { public static void main(String[] args) { RWindow3 rWindow1 = new RWindow3(); RWindow3 rWindow2 = new RWindow3(); RWindow3 rWindow3 = new RWindow3(); rWindow1.setName("窗口一"); rWindow1.start(); rWindow2.setName("窗口二"); rWindow2.start(); rWindow3.setName("窗口三"); rWindow3.start(); } }
死鎖:安全
解決方法:多線程
/** 演示死鎖問題 */ public class ThreadTest { public static void main(String[] args) { StringBuffer s1 = new StringBuffer(); StringBuffer s2 = new StringBuffer(); new Thread(){ @Override public void run() { synchronized(s1){ s1.append("a"); s2.append("1"); try { Thread.currentThread().sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s2) { s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); } } } }.start(); new Thread(new Runnable() { @Override public void run() { synchronized(s2){ s1.append("c"); s2.append("3"); try { Thread.currentThread().sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s1) { s1.append("d"); s2.append("4"); System.out.println(s1); System.out.println(s2); } } } }).start(); } }
解決線程安全問題方式三:Lock(鎖)
---JDK5.0新增併發
synchronized
與lock
的異同相同點:兩者均可以解決線程安全問題。app
不相同:synchronized
機制在執行完相應的同步代碼之後,自動的釋放同步監視器,Lock
須要手動的啓動同步(lock()
),同時結束同步也須要手動實現(unlock()
)。ide
建議(優先使用順序):Lock
同步代碼塊(已經進入了方法體,分配了相應資源)同步方法 (在方法體以外)。工具
class Window implements Runnable { private int ticket = 100; //1.實例化ReentrantLock private ReentrantLock lock = new ReentrantLock(true); @Override public void run() { while (true) { try { //2.調用clock() lock.lock(); //2.調用Lock() if (ticket > 0) { try { Thread.currentThread().sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":售票,票號爲" + ticket); ticket--; } else { break; } } finally { //3.調用解鎖方法:unLock lock.unlock(); } } } } public class LockTest { public static void main(String[] args) { Window window = new Window(); Thread t1 = new Thread(window); Thread t2 = new Thread(window); Thread t3 = new Thread(window); t1.setName("窗口一"); t2.setName("窗口二"); t3.setName("窗口三"); t1.start(); t2.start(); t3.start(); } }
涉及到的三個方法:性能
wait()
:一旦執行此方法,當前線程就進入阻塞狀態,並釋放同步監視器。notify()
:一旦執行此方法,就會喚醒被wait的一個線程。若是有多個線程被wait,就喚醒優先級高的那個。notifyAll()
:一旦執行此方法,就會喚醒全部被wait的線程。1)wait()
、notify()
、notifyAll()
三個方法必須使用在同步代碼塊或同步方法中。
2)wait()
、notify()
、notifyAll()
三個方法的調用者必須是同步代碼塊或同步方法中的同步監視器。不然,會出現IllegalMonitorStateException
異常。
3)wait()
、notify()
、notifyAll()
三個方法是定義在java.lang.Object
類中。this
sleep() 和 wait()的異同?
相同點:
1) 一旦執行方法,均可以使得當前的線程進入阻塞狀態。
不一樣點:
1)兩個方法聲明的位置不一樣:Thread
類中聲明sleep()
, Object
類中聲明wait()
。
2)調用的要求不一樣:sleep()
能夠在任何須要的場景下調用。 wait()
必須使用在同步代碼塊或同步方法中。
3)關因而否釋放同步監視器:若是兩個方法都使用在同步代碼塊或同步方法中,sleep()
不會釋放鎖,wait()
會釋放鎖。
class Number { private int number = 1; private Object obj = new Object(); public Number(int number) { this.number = number; } public int getNumber() { return number; } // 累加數字 public void printer(int number){ synchronized (obj) { obj.notify(); if (number > 0) { System.out.println(Thread.currentThread().getName() + "添加成功。當前數字:" + this.number); try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } this.number += number; try { //使得調用以下wait() 方法的線程進入阻塞狀態 obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } class Preson implements Runnable { private Number number; public Preson(Number number) { this.number = number; } @Override public void run() { while (true) { if (this.number.getNumber() <= 100) { number.printer(1); }else { break; } } } } public class CommunicationTest { public static void main(String[] args) { Number number = new Number(1); Preson preson = new Preson(number); Thread thread = new Thread(preson); thread.setName("甲"); thread.start(); Thread thread2 = new Thread(preson); thread2.setName("乙"); thread2.start(); } }
新增方式一:實現Callable接口
1)與使用Runnable
相比, Callable
功能更強大些。
2)相比run()
方法,能夠有返回值 。
3)方法能夠拋出異常。
4)支持泛型的返回值 。
5)須要藉助FutureTask
類,好比獲取返回結果。
class NumberThread implements Callable{ /** 2.實現Call方法,將此線程須要執行的操做聲明在此方法Call()當中 */ @Override public Object call() throws Exception { int sum = 0; for (int i = 1; i < 100; i++) { if (i % 2 == 0) { System.out.println(i); sum += i; } } return sum; } } public class ThreadNew { public static void main(String[] args) { /** 3.建立Callable接口實現類的對象 */ NumberThread numberThread = new NumberThread(); /** 4.將此Callable接口實現類的對象做爲參數傳遞到FutureTask構造器中,建立FutureTaskd的對象 */ FutureTask futureTask = new FutureTask(numberThread); /** 5.將此FutureTask的對象做爲參數傳遞到Thread類的構造器中,建立Thread對象,並調用start()方法 */ new Thread(futureTask).start(); try { /** 6.獲取Callable中call() 方法的返回值 */ /** get()返回值即爲FutureTask構造器參數Callable實現類重寫的call()的返回值 */ Object sum = futureTask.get(); System.out.println("總和爲:" + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
如何理解實現Callable
接口的方式建立多線程比實現Runnable
接口建立多線程方式強大?
call()
能夠有返回值。
call()
能夠拋出異常,被外面的操做捕獲,獲取異常的信息。
Callable
是支持泛型的
新增方式二:使用線程池
背景:常常建立和銷燬、使用量特別大的資源,好比並髮狀況下的線程, 對性能影響很大。
思路:提早建立好多個線程,放入線程池中,使用時直接獲取,使用完 放回池中。能夠避免頻繁建立銷燬、實現重複利用。相似生活中的公共交 通工具。
好處:
1)提升響應速度(減小了建立新線程的時間)。
2)下降資源消耗(重複利用線程池中線程,不須要每次都建立)。
3)便於線程管理 :
corePoolSize:
核心池的大小 。
maximumPoolSize:
最大線程數 。
keepAliveTime:
線程沒有任務時最多保持多長時間後會終止。
class NumberThread1 implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 != 0) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } } class NumberThread2 implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadPool { public static void main(String[] args) { /** 1.提供指定線程數量的線程池 */ ExecutorService service = Executors.newFixedThreadPool(10); /** 設置線程池的屬性 */ /** 因爲service是ExecutorService接口,須要使用service的實現類對他進行屬性設置 */ System.out.println(service.getClass());//查看service的實現類 /** 強轉方式一: */ ThreadPoolExecutor service1 = (ThreadPoolExecutor)service; // service1.setCorePoolSize(15); /** 強轉方式二: */ // ((ThreadPoolExecutor) service).setKeepAliveTime(1000); /** 2.執行指定的線程的操做。須要提供實現Runnable接口或Callable接口實現類的對象 */ service.execute(new NumberThread1());//適合使用於Runnable service.execute(new NumberThread2());//適合使用於Runnable service.shutdown(); } }