1.3 線程間共享於協作-等待通知機制、等待超時機制實現數據庫連接池。wait、notify、notifyAll,join

一、等待與通知機制、等待超時機制

前言

本文主要是講述等待與通知機制和等待與超時機制,三者都是利用wait(),notify(),notifyAll()的使用來實現的。java中的這三個併發屬性是屬於對象的。所以各個線程在操作時候需要對對象的實例方法進行加鎖,進而獲得執行權。所以說是線程來對外方對象來進行加鎖,進而改變條件,通知其他等待在該對象上的線程。舉例實現等待與通知機制-數據庫連接池

1.等待與通知的標準範式

1)等待方

a、獲取對象的鎖
b、循環裏判斷是否滿足條件,不滿足則調用wait()方法
c、條件滿足則執行業務邏輯

2)通知方

a、獲取對象的鎖
b、改變條件
c、通知所有等待在對象上的線程

2.通知:notify和notifyAll該用那個

注視:我們現在做一個例子,設計一個快遞類,有兩個屬性,公里數和城市。現在使用等待與通知機制,當城市變化或者公里數變化時,我們做一些業務動作。

package cn.enjoy.controller.thread;

/**
 * @author:wangle
 * @description:測試notify和notifyall區別類
 * @version:V1.0
 * @date:2020-03-14 21:27
 **/
public class NotifyAndNotifyAll {

    public final static String city = "shanghai";

    private int km ;  /** 快遞運輸的里程**/

    private String site;  /** 快遞當前所在城市**/



    public NotifyAndNotifyAll(){
    }


    public NotifyAndNotifyAll(int km,String city){
        this.km = km;
        this.site = city;
    }

    public synchronized void changeKm(){
        this.km=101;
        notifyAll();
    }

    public synchronized void changeSite(){
        this.site="beijing";
        notifyAll();
    }

    public synchronized void waitSite(){
        while(city.equals(this.site)){
            try {
                System.out.println(Thread.currentThread().getName()+"   正在等待城市變化");
                wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("城市已經從:"+city+"到達了:"+this.site);
    }

    public synchronized void waitKm(){
        while(this.km <= 100){
            try {
                System.out.println(Thread.currentThread().getName()+"   正在等待公里數變化");
                wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("快遞已經距離出發點:"+this.km+"千米");
    }


}
package cn.enjoy.controller.thread;

/**
 * @author:wangle
 * @description:測試
 * @version:V1.0
 * @date:2020-03-14 21:48
 **/
public class TestNotifyAndNotifyAll {

    private static NotifyAndNotifyAll notifyAndNotifyAll = new NotifyAndNotifyAll(0,NotifyAndNotifyAll.city);



    private static class CheckKm extends Thread{
        @Override
        public void run(){
            notifyAndNotifyAll.waitKm();
        }
    }


    private static class CheckSite extends Thread{
        @Override
        public void run(){
            notifyAndNotifyAll.waitSite();
        }
    }

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

        for(int i=0;i<3;++i){
            new CheckSite().start();
        }

        for(int i=0;i<3;++i){
            new CheckKm().start();
        }

        Thread.sleep(1000);
        notifyAndNotifyAll.changeKm();

    }




}

使用notifyAll的結果在這裏插入圖片描述
可以看到,當我們在改變了公里數,通知了所有的wait在該對象上的線程,並且做出了公里數改變了相應的業務。

使用notify的結果
在這裏插入圖片描述
可以看到,我們在改變了條件,使用nitify通知時,隨機選中了一個等待在該對象上的線程(JVM會在等待隊列中取第一個線程進行喚醒),恰巧選中了等待城市變化的線程,但是我們明明改變的是公里數,並且通知。這就是notify,只會從等待中的線程隨機選取一個通知,其他的則不會收到信號。

3、等待超時模式

1)等待超時模式範式

假設需要等待的時間爲T,當前時間加上T以後超時
long overtime = now + T;
long remain = T;
while(result不滿足條件&&remain>0){
wait(remain);
remain = overtime-now;
}
return result;

2)實現一個數據庫連接池

package cn.enjoy.controller.thread.DBPOLL;

import java.sql.Connection;
import java.util.LinkedList;

/**
 * @author:wangle
 * @description:數據庫連接池
 * @version:V1.0
 * @date:2020-03-15 16:30
 **/
public class DBPoll {

    /**連接池大小**/
    private static LinkedList<Connection> poll = new LinkedList<>();

    /**初始化連接池**/
    public DBPoll(int initSize){
        if(initSize>0){
            for(int i =0;i<initSize;++i){
                poll.addLast(SqlConnectImpl.fetchConnection());
            }
        }
    }

    /**
     * @author wangle25
     * @description 獲取連接
     * @date 16:48 2020-03-15
     * @param time:超時時間
     * @return java.sql.Connection
     **/

    public Connection getConnection(long time)throws InterruptedException{
        synchronized (poll){
            //永遠不超時
            if(time<=0){
                while(poll.isEmpty()){
                    poll.wait();
                }
                return poll.removeFirst();
            }else{
                long overTime = System.currentTimeMillis()+time;
                long remain = time;
                while(poll.isEmpty()&&remain>=0){
                    poll.wait(remain);
                    remain = overTime-System.currentTimeMillis();
                }
                Connection result = null;
                if(!poll.isEmpty()){
                    result = poll.removeFirst();
                }
                return result;
            }
        }
    }

    /**
     * @author wangle25
     * @description 釋放鏈接
     * @date 16:58 2020-03-15
     * @param connection
     * @return void
     **/

    public void releaseConnection(Connection connection){
        synchronized (poll){
            poll.addLast(connection);
            poll.notifyAll();
        }
    }
}

package cn.enjoy.controller.thread.DBPOLL;

import java.sql.Connection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author:wangle
 * @description:測試數據庫連接
 * @version:V1.0
 * @date:2020-03-15 16:59
 **/
public class DBPollTest {

    static DBPoll dBPoll = new DBPoll(10);

    static CountDownLatch end;


    public static void main(String[] arg)throws Exception{

        int threadCount = 50;
        end = new CountDownLatch(threadCount);
        int workCount = 20;

        AtomicInteger got = new AtomicInteger();
        AtomicInteger notGot = new AtomicInteger();
        for(int i=0;i<threadCount;i++){
            Thread thread = new Thread(new Worker(workCount,got,notGot));
            thread.start();
        }
        end.await();

        System.out.println("總共嘗試了:"+threadCount*workCount+"次");
        System.out.println("獲取到次數:"+got);
        System.out.println("未獲取到次數:"+notGot);
    }
    static class Worker implements Runnable{

        int count;
        AtomicInteger got;
        AtomicInteger notGot;

        public Worker( int count,AtomicInteger got,AtomicInteger notGot){
            this.count = count;
            this.got=got;
            this.notGot=notGot;
        }

        @Override
        public void run(){
            while(count>0){
                try {
                    Connection connection = dBPoll.getConnection(1000L);
                    if(null != connection){
                        try{
                            connection.createStatement();
                            connection.commit();
                        }finally {
                            dBPoll.releaseConnection(connection);
                            got.incrementAndGet();
                        }
                    }
                    notGot.incrementAndGet();
                }catch (Exception e){

                }finally {
                    count--;
                }

            }
            end.countDown();
        }

    }
}

結果
在這裏插入圖片描述
闡述:啓動了50個線程、每個線程都獲取數據庫連接20次,有877次成功,123次失敗。以上就是利用等待與通知機制實現的數據庫連接池。

二、join方法

1、作用

可以控制線程執行的順序,例如,線程A執行了線程B的join方法,則線程A需要等待線程B執行完成之後才能繼續執行。下面用一段代碼來驗證

package cn.enjoy.controller.thread.DBPOLL.join方法;

import cn.enjoy.controller.thread.DBPOLL.SleepTools;

/**
 * @author:wangle
 * @description:使用join方法對線程進行串行化
 * @version:V1.0
 * @date:2020-03-19 15:28
 **/
public class UseJoin {

    static class JumpQueue implements Runnable{
        private Thread thread;
        public JumpQueue(Thread thread){
            this.thread = thread;
        }

        @Override
        public void run(){
            try {
                thread.join();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"run over");
        }
    }


    public static void main(String[] args){
        //取到當前主線程
        Thread previous = Thread.currentThread();
        for(int i=0;i<10;i++){
            Thread thread = new Thread(new JumpQueue(previous),String.valueOf(i));
            System.out.println(previous.getName()+"插入到了"+thread.getName()+"前面");
            thread.start();
            previous = thread;
        }
        SleepTools.second(2);
        System.out.println(Thread.currentThread().getName()+"run over");
    }


}

在這裏插入圖片描述
闡述:我們循環起了10個線程,當i=0的時候,主線程插到0線程的前面,當i=1,0線程插入到1線程前,依次類推。線程最終的執行順序應該是 main->0->1->2->3->4->5->6->7->8->9

二、yield,wait,sleep,notify對鎖的影響

yield和sleep後是不釋放鎖的
wait方法調用後會釋放鎖
notify方法調用後不會釋放鎖,等待被同步的代碼塊執行完成之後纔會將鎖釋放,所以一般notify和notifyall再代碼同步塊的最後使用比較合理

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