文章目錄
- 1. Runnable接口
- 2. 賣票案例
- 3. 同步代碼塊解決數據安全問題
- 4. 同步方法解決數據安全問題
- 5. 線程安全的類
- 6. Lock鎖
1. Runnable接口
?1. 創建線程的另一種方法是聲明一個實現Runnable
接口的類,之后重寫run()
方法,然后可以分配類的實例,在創建Thread
時作為參數傳遞,最后啟動。
?2. 具體實現Runnable
接口:(1) 定義一個MyRunnable實現Runnable接口。 (2) 在MyRunnable中重寫run()方法。 (3) 創建MyRunnable類對象。 (4) 創建Thread類的對象,把MyRunnable對象作為構造方法的參數。 (5) 啟動線程。
public class MyRunnable implements Runnable{@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println(Thread.currentThread().getName()+":"+i);}}
}public class MyRunnableDemo {public static void main(String[] args) {//創建MyRunnable類對象MyRunnable my=new MyRunnable();//創建Thread類對象,把MyRunnable對象作為參數傳進來//Thread(Runnable target)//Thread t1=new Thread(my);//Thread t2=new Thread(my);//Thread(Runnable target, String name)Thread t1=new Thread(my,"線程1");Thread t2=new Thread(my,"線程2");//啟動線程t1.start();t2.start();}
}
?3. 多線程的實現方案有兩種:(1) 繼承Thread
類。 (2) 實現Runnable
接口。
- 實現
Runnable
接口好處:避免了Java單繼承的局限性;適合多個相同程序的代碼去處理同一資源的情況,把線程和程序的代碼、數據有效分離,較好地體現了面向對象的設計思想。
2. 賣票案例
?1. 需求和思路:
?2. 代碼塊:
public class SellTicket implements Runnable{//表示有100張票private int tickets=100;@Overridepublic void run() {while (true){if(tickets>0){System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"張票");tickets--;}}}
}public class SellTicketDemo {public static void main(String[] args) {SellTicket st=new SellTicket();Thread t1=new Thread(st,"窗口1");Thread t2=new Thread(st,"窗口2");Thread t3=new Thread(st,"窗口3");t1.start();t2.start();t3.start();}
}
?3. 問題分析:看上面結果相同的票會出現三次,為什么呢?請參照下圖理解,雖然加上了休眠時間,但是原理是一樣的,都是線程搶占CPU執行權導致的。
3. 同步代碼塊解決數據安全問題
?1. 如何解決多線程安全問題:(1) 把多條語句操作共享數據的代碼給鎖起來,讓任意時刻只能有一個線程執行即可。 (2) Java提供了同步代碼塊的方式來解決。
?2. 同步代碼塊格式:
?3. 代碼塊舉例:
public class SellTicket implements Runnable{//表示有100張票private int tickets=100;private Object obj= new Object();@Overridepublic void run() {while (true){synchronized (obj) {if (tickets > 0) {System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票");tickets--;}}}}
}public class SellTicketDemo {public static void main(String[] args) {SellTicket st=new SellTicket();Thread t1=new Thread(st,"窗口1");Thread t2=new Thread(st,"窗口2");Thread t3=new Thread(st,"窗口3");t1.start();t2.start();t3.start();}
}
?4. 同步的好處和弊端:(1) 好處:解決了多線程的數據安全問題。 (2) 弊端:當線程很多時,因為每個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會降低程序的運行效率。
4. 同步方法解決數據安全問題
?1. 同步方法:就是把synchronized
關鍵字加到方法上。
?2. 格式:修飾符 synchronized 返回值類型 方法名(方法參數){ }
。
?3. 同步方法的鎖對象:this
。
?4. 代碼塊舉例:
public class SellTicket implements Runnable{//表示有100張票private int tickets=100;private Object obj= new Object();private int x=0;@Overridepublic void run() {while (true){if(x%2==0) { //這里得是this,否則會出問題synchronized (this) {if (tickets > 0) {System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票");tickets--;}}}else{sellticket();}x++;}}private synchronized void sellticket(){if (tickets > 0) {System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票");tickets--;}}
}public class SellTicketDemo {public static void main(String[] args) {SellTicket st=new SellTicket();Thread t1=new Thread(st,"窗口1");Thread t2=new Thread(st,"窗口2");Thread t3=new Thread(st,"窗口3");t1.start();t2.start();t3.start();}
}
5. 線程安全的類
6. Lock鎖
?1. 雖然我們可以理解同步代碼塊和同步方法的鎖對象問題,但是我們并沒有直接看到在哪里加上了鎖,在哪里釋放了鎖,為了更清晰的表達如何加鎖和釋放鎖,JDK5以后提供了一個新的鎖對象Lock
。
?2. Lock實現提供比使用synchronized方法和語句可以獲得更廣泛的鎖定操作。
?3. Lock中獲得鎖和釋放鎖的方法:void lock()
:獲得鎖。void unlock()
:釋放鎖。
?4. Lock是接口不能直接實例化,這里采用它的實現類ReentrantLock
來實例化。
?5. 代碼塊舉例:
public class SellTicket implements Runnable{//表示有100張票private int tickets=100;private Lock lock=new ReentrantLock();@Overridepublic void run(){while (true) {lock.lock();if (tickets > 0) {System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票");tickets--;}lock.unlock();}}
}public class SellTicketDemo {public static void main(String[] args) {SellTicket st=new SellTicket();Thread t1=new Thread(st,"窗口1");Thread t2=new Thread(st,"窗口2");Thread t3=new Thread(st,"窗口3");t1.start();t2.start();t3.start();}
}