多線程知識學習2:多線程鎖以及線程池


多線程學習筆記:

    synchronized關鍵字:既能夠保證原子性也能夠保證可見性。 被synchronized關鍵字修飾的對象會被加上一個互斥鎖。
    
    synchronized是一個非公平鎖,如果多個線程處於等待狀態,那麼這些線程是隨機獲取鎖的。
    
    什麼是互斥鎖:同一時間只能被一個線程持有(注意:只有加了synchronized關鍵字同時會使用到這個對象的線程會呈現等待狀態)
    
    synchronized鎖定一個靜態對象時是直接鎖定該對象的CLASS對象
    
    synchronized所獲得的鎖是可以重入的:同一個class對象裏面多個方法可以重複獲得一個鎖。
    
    子類的同步方法可以調用父類的同步方法,允許這樣。
    
    線程出現異常的時候會直接釋放鎖,這時候在多線程情況下可能會讀出錯誤數據
    
    volatile關鍵字:線程之間可見性,禁止指jmm對計算機指令的重排序
    
    一般來說線程讀取某個對象都是將其放在自己的緩衝區,待對該對象操作完畢之後會將緩衝區更新後的數據寫入堆裏。
    
    如果是加了volatile關鍵字,當某個線程對該對象做了修改之後會提示其他線程,從而更新其他線程緩衝區引用對象的值。
    
    volatile關鍵字的效率比synchronized要高,volatile關鍵字是無鎖同步的方式,但是不能夠保證原子性(數據一致性)
    
    atoMic類裏面能夠保證原子性,但是隻能單一使用atoMic裏面的方法是才具有原子性。多個方法使用時可能會出現問題。
    
    synchronized: 細粒度的鎖效率比粗粒度的鎖效率高
    
    new object() 是新建一個對象,所以不會存在鎖被佔用的問題。最好不要使用string 常量做synchronized的對象
    
    wait() 將加鎖的對象的當前使用線程置爲等待狀態,同時釋放鎖,此時其他線程可以調用此對象。喚醒線程的方法是notify和notifyall
    
    注意: notify 和 notifyAll 不會釋放鎖 
    
    注意:notify 是喚醒沉睡的線程和對象無關。 wait是使當前線程沉睡,!!!!。雖然都是對象的方法,但一定注意的是它操作的是線程!!!
    
    countdownlunch:門閂。不需要加鎖也可實現沉睡和喚醒機制。
    
        latch.await()  等待
        latch.countdownl() 喚醒
    
    reentratlock:手動鎖可重入鎖,使用完畢後必須手動釋放。
    
            // 獲取reentratLock 實例
            Lock lock = new reentratlock();
            // 嘗試獲取鎖
            lock.tryLock(5,TimeUnit.SECCOND); 嘗試獲取鎖(指定時間,指定時間單位)
            // 設置線程可以被打斷 , 設置了這個屬性,主線程可以對該線程進行interrupt()打斷
            lock.lockInterruptibly();
            // reentratLock可以設置公平鎖,這樣能夠保證等待時間最久的線程獲取線程
            
            例子:
            public class ReentratDemo extends Thread{
            
            // 設置公平鎖
            private Lock lock = new ReentrantLock(true);

            public void run(){
                for (int i = 0; i < 100;i++){
                    try {
                        // 加上可重入鎖
                        lock.lock();
                        System.out.println(Thread.currentThread().getName() + "獲得鎖");
                    }catch (Exception e){
                        System.out.println(e);
                    }finally {
                        // 釋放鎖:注意 reentratLock必須設置手動釋放鎖
                        lock.unlock();
                    }
                }
            }

            public static void main(String[] args) {
                ReentratDemo r = new ReentratDemo();
                Thread t1 = new Thread(r);
                Thread t2 = new Thread(r);

                t1.start();
                t2.start();
                }
            }
          
         
         object.wait()方法通常是個while一起使用的;
            
            while(size == max){                                     if(size == max){
              try{                                                    try{
                 this.wait()                                              this.wait()
              }catch(InterruntException e){                             }catch(InterruntException e){
                 sout(e);                                                  sout(e);
              }                                                          }
            }                                                           }
            forEach()                                              forEach()
            
        這裏如果線程是被重新喚醒,還是會繼續執行while中的判斷        這裏線程如果被喚醒,會直接執行wait()後面的代碼,而不會再次去判斷size == max,容易出現錯誤   
    
    
     
    synchronized 鎖對象,可重入的互斥鎖。拋出異常的時候會自動釋放鎖。
    lock:reentratLock: 手動釋放的可重入互斥鎖。tryLck可以嘗試獲取鎖,lockInterruntaby 設置可以被打斷 new 的時候可以指定reentratLock爲公平鎖
    
    wait()  被鎖住的對象釋放鎖,並且使當前線程處於等待狀態
    
    notify/notifyAll  喚醒等待線程/喚醒等待全部的線程  
    
    Condotion condition = reenstratLock.newCondition();  condition可以精確地指定某些或者說某一個線程的沉睡和喚醒。
    
    threadLocal:每一個線程設置的對象只能夠提供給自己的線程使用。線程之間不恭喜。
    
    session裏面的設置就使用了threadLocal
    
    單例模式(singleton):無論如何,線程池裏面只有一個對象。分爲懶漢模式(用到時才new),和餓漢模式(直接new)
    
    concurrent包下的類:
    
    concurrentQueue: 線程安全隊列   add() 如果超過容器容量的最大值會拋出異常。
                                    offer() 也是往容器裏面加,有返回值boolean類型的,達到最大值的時候它便不能夠往裏面加了。
    
    concurrentHashMap 和 HashTable 效率問題:concurrentHashMap效率更高。hashTable是鎖住整個容器
    
    concurrentHashMap1.7 鎖的將容器分成一塊一塊的segament中的其中一個segament稱之爲分段鎖。
    
    concurrentHashMap1.8 鎖的是其中的一個node,採用cas算法來插入或者修改數據。
    
    在你put數據進入concurrentHashMap時,會使用cas算法在內存中計算出一個hash值,如果和你put的對象計算出的hash值一致,則允許寫入,保證了原子性。
    
    concurrentSkipListMap :線程安全的有序的map
    
    copyOnWriteList() 寫的時候會複製,寫的少讀得多的情況下使用
    
    linkedBlockingQueue: 同步阻塞容器。 put() 當容器滿了之後,線程會自動等待。 無界容器。
    ArrayBlockingQueue:同步阻塞隊列 。有界隊列,默認有大小,自己也可以設置大小。
    
    delayQueue:可以指定時間,等待時間長的最先執行。
    
    transferQueue: 提供者直接將消息發送給對應的消費者,這樣能夠提高效率。但是隻有transfer()方法會使消息阻塞。
    
    runnable():運行的是run()方法,沒有返回值、不能夠拋出異常。
    
    callable():運行的是call()方法,有返回值,可以拋出異常。
    
    executors工具類可以創建線程池和task
    
    executorService.submit() 只是將這些任務(task)放入線程池中,但還未真正的運行
    
    // 創建線程池
    ExecuteService service = Executors.newXXXthreadPool();   
    // 將創建好的的任務添加到線程池中,得到一個future類
    Future future = service.submit(callable or implements);
    // future.get() 執行指定線程
    future.get()
    
    線程池demo:
    
    /**
 *  計算1 - 200000 的質數
 */
public class Executor {

    static int myCoreCpu = 4;

    /**
     *  自定義任務
     */
    static class myTask implements Callable{
        int begin;
        int end;

        myTask(int begin,int end){
            this.begin = begin;
            this.end = end;
        }

        @Override
        public List<Integer> call() throws Exception {
            return getPrise(begin,end);
        }
    }

    static List<Integer> getPrise(int from,int end){
        List<Integer> priseList = new ArrayList<>();
        for(int i = from ; i <= end ;i ++){
            boolean prise = isPrise(i);
            if(prise) priseList.add(i);
        }
        return priseList;
    }


   static boolean isPrise(int num){
       for(int i = 2; i < num/2; i++){
           if(num % i == 0){
               return false;
           }
       }
       return true;
   }

    public static void main(String[] args) throws ExecutionException,InterruptedException{

        List<Integer> l1 = new ArrayList<>();
        List<Integer> l2 = new ArrayList<>();

        /**
         *  不使用線程池做任務調度
         */
        long time = new Date().getTime();
        for(int i = 1;i <= 200000;i++){
            boolean prise = isPrise(i);
            if (prise) l1.add(i);
        }
        long end =  new Date().getTime();
        System.out.println("數組大小:" + l1.size() + "-----用時:" + (end - time));

        /**
         *  線程池實現
         */
        ExecutorService service = Executors.newFixedThreadPool(myCoreCpu);
        myTask m1 = new myTask(1,80000);
        myTask m2 = new myTask(80001,130000);
        myTask m3 = new myTask(130001,170000);
        myTask m4 = new myTask(170001,200000);
        /**
         *  將任務提交到線程池
         */
        Future f1 = service.submit(m1);
        Future f2 = service.submit(m2);
        Future f3 = service.submit(m3);
        Future f4 = service.submit(m4);

        long startTime = new Date().getTime();
        /**
         * 啓動線程
         */
        l2.addAll ((List<Integer>)f1.get());
        l2.addAll ((List<Integer>)f2.get());
        l2.addAll ((List<Integer>)f3.get());
        l2.addAll ((List<Integer>)f4.get());

        long endTime = new Date().getTime();

        System.out.println("數組大小:" + l2.size() + "------用時:" + (endTime - startTime));

    }
        /**
         *  關閉service
         */
        service.shutdown();

}

    ExecutorService.submit --> Future --> Future.get();
    
    catchThredPool --> 可以設置線程存活時間aliveTime:默認爲60s
    
    Executors工具類裏面有很多種類型的threadPool,分別適用於不同的場景,詳細的需要看api。
    
    Executors的創建的線程池的底層是 return new ThreadPoolExecutor(int corePoolSise,
        int maxPoolsize,long keepAliveTime,TimeUnit unit,WorkQueue linkedBlockingQueue ThreadFactory tf)
        
    根據裏面參數的值可以分爲不同類型的線程池:
    
    1.指定核心線程數線程池FixedThread:core = max = 指定參數,keepAliveTime = 0 
        
        特點:使用shoutDown()方法可以線程執行完畢之後自動關閉線程池。
            
        public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
    
    2.單例線程數singletonThreadPool:core = max = 1,keepAliveTime = 0;
    
        特點:線程池中只能有一條線程在執行,其他的線程在workQueue裏面等待,能保證線程執行的順序。
        
        public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }
    
    3.緩存線程池,cacheThreadPool: core = 0,max = integer.maxValue(),keepAliveTime = 60L(默認,可自己設定),TimeUnit.SECCOND ,workQueue = SynchronousQueue
    
        SynchronousQueue 是線程安全的queue不經過線程池,直接被調用,如果沒人調用就一直處於阻塞狀態
    
        public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
    4.定時線程:ScheduledThreadPoolExecutor 第三個參數爲開啓時間,workQueue = delayedWorkQueue;(優先級隊列,先進先出)
    
      public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
    
    
    
    mybatis爲什麼我們在dao層使用interface就可以用
    
    動態代理cglib 和 jdk動態代理
    
    
    mysqld.exe –install MySql –defaults-file='C:\Program Files\MySQL\mysql5.7\my-default.ini'
    
    
    
    
    
    
    
    
    
    
    
    
    
    
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章