day12【線程安全解決、併發包】
反饋和複習
1.原子類不太懂(CAS機制)
2.建議早上把課程講完,下午自己有時間可以複習
今日內容
1.Synchronized關鍵字【重點】
2.高併發下JDK提供一堆的線程安全有關類【理解】
第一章 synchronized關鍵字【重點】
1.1 AtomicInteger的不足之處
回顧: AtomicInteger能解決什麼問題?
可以保證對"變量"操作(比如++,--)是原子性(有序的,可見性)
無法解決: 多行代碼的原子性
System.out.println("A");
System.out.println("B");
1.2 多行代碼的原子性安全問題–賣票案例
賣票案例:
/**
* 賣票任務
*/
public class TicketTask implements Runnable{
//定義變量,表示初始有100張票
int count = 100;
@Override
public void run() {
while (true){
if (count > 0) {
//先判斷,後賣票
try {Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("賣出第"+count+"張票!");
//票數要減少
count--;
}else {
break;
}
}
}
}
public class TestDemo {
public static void main(String[] args) {
//0.創建賣票任務
TicketTask tt = new TicketTask();
//1.創建賣票窗口
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
Thread t3 = new Thread(tt);
//2.開始賣票
t1.start();
t2.start();
t3.start();
//線程出現安全問題:
//a.可能出現重複數據
//b.可能出現0,甚至-1這種非法數據
}
}
出現了線程安全問題(原子性問題):
//a.可能出現重複數據
原因: 當一個線程執行完賣票後,還沒有來得及對票數-1,被其他線程搶走了CPU,導致其他線程也賣出一樣的票
//b.可能出現0,甚至-1這種非法數據
原因: 當剩下最後一張票時,多線程都經過了if判斷 進入賣票的代碼塊中,然後依次賣出第1張,第0張,第-1
1.3 synchronized關鍵字介紹
a.synchronized是什麼??
synchronized是Java提供的關鍵字
b.synchronized的作用??
可以讓多句代碼具有原子性
(當某個線程執行多句代碼的過程中不被其他線程打斷)
1.4 解決方案1_同步代碼塊
格式:
synchronized(鎖對象){
需要同步的代碼(需要保持原子性的代碼)
}
解決代碼:
/**
* 賣票任務
*/
public class TicketTask implements Runnable{
//定義變量,表示初始有100張票
int count = 100;
Object obj = new Object();
@Override
public void run() {
while (true){
//同步代碼塊
synchronized (obj){
if (count > 0) {
//先判斷,後賣票
System.out.println("賣出第"+count+"張票!");
//票數要減少
count--;
}
}
}
}
}
1.5 解決方案2_同步方法
格式:
public synchronized void 方法名(){
需要同步的代碼(需要保證原子性的代碼)
}
解決代碼:
/**
* 賣票任務
*/
public class TicketTask implements Runnable{
//定義變量,表示初始有100張票
int count = 100;
@Override
public void run() {
while (true){
sellTicket();
}
}
public synchronized void sellTicket(){
if (count > 0) {
//先判斷,後賣票
System.out.println("賣出第"+count+"張票!");
//票數要減少
count--;
}
}
}
注意:
a.同步代碼塊和同步方法其實原理是差不多,同步代碼塊的同步鎖我們需要自己指定,而同步方法的同步鎖
,默認使用當前對象this
b.同步方法能否是靜態方法呢? 可以是static的,如果同步方法是static的,
默認使用當前類的字節碼文件作爲鎖對象
1.6 解決方案3_Lock鎖
Lock是一個接口,我們需要使用其實現類 ReentrantLock
ReentrantLock的API:
public ReentrantLock();
public void lock(); //加鎖
public void unlock();//解鎖(釋放鎖)
格式:
ReentrantLock lock = new ReentrantLock();
lock.lock(); //加鎖
需要同步的代碼塊(需要保持原子性的代碼)
lock.unlock();//解鎖
解決代碼:
/**
* 賣票任務
*/
public class TicketTask implements Runnable{
//定義變量,表示初始有100張票
int count = 100;
//創建一個Lock鎖
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//加鎖
lock.lock();
if (count > 0) {
//先判斷,後賣票
System.out.println("賣出第"+count+"張票!");
//票數要減少
count--;
}
lock.unlock();
}
}
}
第二章 併發包【理解】
什麼是併發包:
這是JDK提供的,包含一個在高併發情況使用集合或者工具,使用這些集合或者工具類時,能保證高併發情況下是安全的
2.1 CopyOnWriteArrayList
-
ArrayList是線程不安全的
public class MyThread extends Thread { public static List<Integer> list = new ArrayList<>();//線程不安全的 @Override public void run() { for (int i = 0; i < 10000; i++) { list.add(i); } System.out.println("添加完畢!"); } } public class TestArrayList { public static void main(String[] args) throws InterruptedException { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); t2.start(); Thread.sleep(1000); System.out.println("最終集合的長度:" + MyThread.list.size()); } }
-
CopyOnWriteArrayList是線程安全的
public class MyThread extends Thread { // public static List<Integer> list = new ArrayList<>();//線程不安全的 public static List<Integer> list = new CopyOnWriteArrayList<>();//保證線程安全 @Override public void run() { for (int i = 0; i < 10000; i++) { list.add(i); } System.out.println("添加完畢!"); } }
2.2 CopyOnWriteArraySet
-
HashSet是線程不安全的
public class MyThread extends Thread { public static Set<Integer> set = new HashSet<>();//線程不安全的 @Override public void run() { for (int i = 0; i < 10000; i++) { set.add(i); } System.out.println("添加完畢!"); } } public class TestSet { public static void main(String[] args) throws InterruptedException { MyThread t1 = new MyThread(); t1.start(); //主線程也添加10000個 for (int i = 10000; i < 20000; i++) { MyThread.set.add(i); } Thread.sleep(1000 * 3); System.out.println("最終集合的長度:" + MyThread.set.size()); } }
-
CopyOnWriteArraySet是線程安全的
public class MyThread extends Thread { //public static Set<Integer> set = new HashSet<>();//線程不安全的 public static Set<Integer> set = new CopyOnWriteArraySet<>();//線程安全的 @Override public void run() { for (int i = 0; i < 10000; i++) { set.add(i); } System.out.println("添加完畢!"); } }
2.3 ConcurrentHashMap
-
HashMap是線程不安全的
public class MyThread extends Thread { public static Map<Integer, Integer> map = new HashMap<>(); @Override public void run() { for (int i = 0; i < 10000; i++) { map.put(i, i);//0,0 1,1 2,2 3,3 ... 9999,9999 } } } public class TestMap { public static void main(String[] args) throws InterruptedException { MyThread t1 = new MyThread(); t1.start(); for (int i = 10000; i < 20000; i++) { MyThread.map.put(i, i); } Thread.sleep(1000 * 2); System.out.println("map最終大小:" + MyThread.map.size()); } }
-
Hashtable是線程安全的,但效率低
public class MyThread1 extends Thread { public static Map<Integer, Integer> map = new Hashtable<>(); //線程是安全 @Override public void run() { long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { map.put(i, i); } long end = System.currentTimeMillis(); System.out.println((end - start) + " 毫秒"); } } public class TestMap1 { public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { new MyThread1().start();//開啓1000個線程 } Thread.sleep(1000 * 20);//由於每個線程執行時間稍長,所以這裏多停頓一會 System.out.println("map的最終大小:" + MyThread1.map.size()); } }
-
ConcurrentHashMap既安全又效率高
public class MyThread1 extends Thread { // public static Map<Integer, Integer> map = new Hashtable<>(); //線程是安全,但是效率低 public static Map<Integer, Integer> map = new ConcurrentHashMap<>(); //線程是安全,但是效率高 @Override public void run() { long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { map.put(i, i); } long end = System.currentTimeMillis(); System.out.println((end - start) + " 毫秒"); } } 小結: HashMap 線程不安全(多線程不能操作同一個HashMap) HashTable 線程安全的(多線程可以操作同一個HashTable),但是效率較低 ConcurrentHashMap 線程安全的,並且效率比較高
-
爲什麼Hashtable效率低而ConcurrentHashMap效率高?
因爲HashTable對哈希表進行全表加鎖 而ConcurrentHashMap只對某個桶(鏈表)局部加鎖,並且也同時使用CAS機制
2.4 CountDownLatch
-
CountDownLatch的作用
允許當前線程,等待其他線程完成某種操作之後,當前線程繼續執行
-
CountDownLatch的API
構造方法: public CountDownLatch(int count); 需要傳入計數器,需要等待的線程數 成員方法: public void await() throws InterruptedException// 讓當前線程等待 public void countDown() // 計數器進行減1
-
CountDownLatch的使用案例
需求: 線程1要執行打印:A和C,線程2要執行打印:B 我們需要這樣的結果: 線程1 先打印A 線程2打印B 之後 線程1再打印C A B C public class TestDemo { public static void main(String[] args) throws InterruptedException { //0.創建一個CountDownLatch CountDownLatch latch = new CountDownLatch(1); //1.創建兩個線程 Thread t1 = new MyThread1(latch); Thread t2 = new MyThread2(latch); t1.start(); Thread.sleep(5000); t2.start(); } } public class MyThread1 extends Thread { private CountDownLatch latch; public MyThread1(CountDownLatch latch){ this.latch = latch; } @Override public void run() { System.out.println("A...."); try { latch.await();//讓當前線程等待 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("C...."); } } public class MyThread2 extends Thread { private CountDownLatch latch; public MyThread2(CountDownLatch latch){ this.latch = latch; } @Override public void run() { System.out.println("B...."); //讓latch的計數器減少1 latch.countDown(); } }
2.5 CyclicBarrier
-
CyclicBarrier的作用
讓多個線程,都到達了某種要求之後,新的任務才能執行
-
CyclicBarrier的API
構造方法: public CyclicBarrier(int parties, Runnable barrierAction); 需要多少個線程 所有線程都滿足要求了,執行的任務 成員方法: public int await();當某個線程達到了,需要調用await()
-
CyclicBarrier的使用案例
需求: 部門開會,假設部門有五個人,五個人都到達了才執行開會這個任務 public class TestPersonThread { public static void main(String[] args) throws InterruptedException { //0.創建一個CyclicBarrier CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() { @Override public void run() { System.out.println("人都齊了,開會吧"); } }); //1.創建五個線程 PersonThread p1 = new PersonThread(barrier); PersonThread p2 = new PersonThread(barrier); PersonThread p3 = new PersonThread(barrier); PersonThread p4 = new PersonThread(barrier); PersonThread p5 = new PersonThread(barrier); //2.開啓 p1.start(); p2.start(); p3.start(); p4.start(); p5.start(); //Thread.sleep(6000); //System.out.println("人都到了,開會吧..."); //要求,人沒到不開會,都到了立刻開會!!! } } public class PersonThread extends Thread { private CyclicBarrier barrier; public PersonThread(CyclicBarrier barrier){ this.barrier = barrier; } @Override public void run() { try { Thread.sleep(new Random().nextInt(6)*1000); System.out.println(Thread.currentThread().getName() + " 到了! "); //調用 barrier的await 表示線程到了 try { barrier.await(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } catch (InterruptedException e) { e.printStackTrace(); } } } 補充: Math的靜態方法 public static double random(); //獲取一個0(包括)到1(不包括)的正小數
2.6 Semaphore
-
Semaphore的作用
用於控制併發線程的數量!!
-
Semaphore的API
構造方法: public Semaphore(int permits); //參數 permits 表示最多允許有多少個線程併發執行 成員方法: public void acquire(); //獲取線程的許可證 public void release(); //釋放線程的許可證
-
Semaphore的使用案例
public class MyThread extends Thread { private Semaphore semaphore; public MyThread(Semaphore semaphore) { this.semaphore = semaphore; } @Override public void run(){ //從Semaphore獲取線程的許可 try { semaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 進入 時間=" + System.currentTimeMillis()); try { Thread.sleep(100*new Random().nextInt(10)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 結束 時間=" + System.currentTimeMillis()); //歸還semaphore線程的許可 semaphore.release(); } } public class TestDemo { public static void main(String[] args) { //0.創建Semaphore Semaphore semaphore = new Semaphore(3); //最多的併發線程數量爲1 for (int i = 0; i < 10; i++) { new MyThread(semaphore).start(); } } }
2.7 Exchanger
-
Exchanger的作用
用於線程間的數據交換
-
Exchanger的API
構造方法: public Exchanger<V>(); 成員方法: public V exchange(V x);//參數爲發給別的線程的數據,返回值別的線程發過來的數據
-
Exchanger的使用案例
public class TestExchanger { public static void main(String[] args) throws InterruptedException { //0.創建一個線程間數據交互對象 Exchanger<String> exchanger = new Exchanger<String>(); //1.創建線程A ThreadA aThread = new ThreadA(exchanger); aThread.start(); //休眠 Thread.sleep(5000); ThreadB bThread = new ThreadB(exchanger); bThread.start(); } } public class ThreadA extends Thread { private Exchanger<String> exchanger; public ThreadA(Exchanger<String> exchanger) { this.exchanger = exchanger; } @Override public void run() { System.out.println("線程A,要將禮物AAA,送給線程B..."); //調用exchanger String result = null; try { result = exchanger.exchange("AAA");//阻塞 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線程A,獲取到線程B的禮物:"+result); } } public class ThreadB extends Thread { private Exchanger<String> exchanger; public ThreadB(Exchanger<String> exchanger) { this.exchanger = exchanger; } @Override public void run() { System.out.println("線程B,要將禮物BBB,送給線程A..."); String result = null; try { result = exchanger.exchange("BBB"); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線程B,獲取到線程A的禮物:"+result); } }
總結:
能夠使用同步代碼塊解決線程安全問題【重點】 synchronized(鎖對象){ 需要同步的代碼(需要保證原子性的代碼) } 鎖對象,可以是任意對象 能夠使用同步方法解決線程安全問題牌【重點】 public synchronized void 方法名(){ 需要同步的代碼(需要保證原子性的代碼) } 能夠使用Lock鎖解決線程安全問題牌【重點】 Lock lock = new ReentrantLock(); lock.lock(); 需要同步的代碼(需要保證原子性的代碼) lock.unlock(); 能夠說明volatile關鍵字和synchronized關鍵字的區別 volatile 能解決有序性和可見性 原子類 能解決變量操作的原子性(有序性和可見性) synchronized 能解決多句代碼的原子性(有序性和可見性) 能夠描述CopyOnWriteArrayList類的作用 代替多線程的情況,線程安全的ArrayList集合 能夠描述CopyOnWriteArraySet類的作用 代替多線程的情況,線程安全的HashSet集合 能夠描述ConcurrentHashMap類的作用 代替多線程的情況,線程安全的HashMap集合(比HashTable效率更好) 能夠描述CountDownLatch類的作用 可以允許一個線程等待另外一個線程執行完畢後再繼續執行 能夠描述CyclicBarrier類的作用 讓一組線程都到達某種條件後再執行某個任務 能夠表述Semaphore類的作用 控制多線程併發的最大數量 能夠描述Exchanger類的作用 用於線程間的通信(數據交換)