day12【線程安全解決、併發包】synchronized關鍵字、售票問題、synchronized同步代碼塊、synchronized同步方法、Lock鎖、併發包

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類的作用
        用於線程間的通信(數據交換)
    
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章