進程間通信 線程間通信 生產(chǎn)者-消費者模式

進程間通信有哪些方法辫塌?

(1)管道(Pipe):管道可用于具有親緣關(guān)系進程間的通信,允許一個進程和另一個與它有共同祖先的進程之間進行通信狐粱。

(2)命名管道(named pipe):命名管道克服了管道沒有名字的限制撼班,因此,除具有管道所具有的功能外檀训,它還允許無親緣關(guān) 系 進程間的通信。

(3)信號(Signal):信號是比較復(fù)雜的通信方式识虚,用于通知接受進程有某種事件發(fā)生肢扯,除了用于進程間通信外,進程還可以發(fā)送 信號給進程本身

(4) 消息(Message)隊列:消息隊列是消息的鏈接表担锤,包括Posix消息隊列system V消息隊列蔚晨。

(5)共享內(nèi)存:使得多個進程可以訪問同一塊內(nèi)存空間,是最快的可用IPC形式肛循。

(6)內(nèi)存映射(mapped memory):內(nèi)存映射允許任何多個進程間通信铭腕,每一個使用該機制的進程通過把一個共享的文件映射到自己的進程地址空間來實現(xiàn)它。

(7)信號量(semaphore):主要作為進程間以及同一進程不同線程之間的同步手段多糠。

(8)套接口(Socket):更為一般的進程間通信機制累舷,可用于不同機器之間的進程間通信。Linux和System V的變種都支持套接字

線程間通信有哪些方法? 給出一些代碼例子

共享變量

共享變量是使用一個volatile關(guān)鍵字的變量作為標(biāo)記夹孔,來進行線程間的通信被盈。
下面使用一個例子來說明:

package com.Thread.Communication;

//使用volatile變量 來設(shè)置標(biāo)記位  達到線程通信的目的
public class T1 {
   private static volatile boolean flag = false;

   public void setFlag() {
       flag = true;
   }

   private void exe() {
       System.out.println("執(zhí)行主業(yè)務(wù)程序....");
   }

   public void execute() {
       while (!flag) {
           System.out.println("等待放行信號....");
       }

       exe();
   }
}

class Thead1 implements Runnable {

   private T1 t1;;

   public Thead1(T1 t1) {
       // TODO Auto-generated constructor stub
       this.t1 = t1;
   }

   @Override
   public void run() {
       // TODO Auto-generated method stub
       System.out.println("set flag ready");
       t1.setFlag();
       System.out.println("set flag done");
   }

}

class Thread2 implements Runnable {

   private T1 t2;

   public Thread2(T1 t1) {
       // TODO Auto-generated constructor stub
       t2 = t1;
   }

   @Override
   public void run() {
       // TODO Auto-generated method stub
       t2.execute();
   }

}

class Client {
   public static void main(String[] args) throws InterruptedException {
       T1 t1 = new T1();
       Thead1 thead1 = new Thead1(t1);
       Thread2 thread2 = new Thread2(t1);

       Thread tt1 = new Thread(thead1);
       Thread tt2 = new Thread(thread2);

       tt2.start();
       Thread.sleep(3000);
       tt1.start();
   }
}

等待放行信號....
等待放行信號....
等待放行信號....
set flag ready
等待放行信號....
set flag done
執(zhí)行主業(yè)務(wù)程序....

在T1的方法中析孽,我們可以看到,有一個標(biāo)記量flag只怎,然后還有一個主業(yè)務(wù)的執(zhí)行程序方法袜瞬,要實現(xiàn)的功能就是,當(dāng)flag為真時才執(zhí)行主程序的方法身堡。使用了兩個線程邓尤,一個去改變狀態(tài)一個去執(zhí)行方法。通過flag達到線程通信的目的贴谎。

同步方法

直接使用synchronize關(guān)鍵字加在方法上汞扎,使得要執(zhí)行方法必須獲得對象鎖。一次只能一個線程執(zhí)行擅这。

下面是代碼例子:

public class MyObject {

    synchronized public void methodA() {
        //do something....
    }

    synchronized public void methodB() {
        //do some other thing
    }
}

public class ThreadA extends Thread {

    private MyObject object;
//省略構(gòu)造方法
    @Override
    public void run() {
        super.run();
        object.methodA();
    }
}

public class ThreadB extends Thread {

    private MyObject object;
//省略構(gòu)造方法
    @Override
    public void run() {
        super.run();
        object.methodB();
    }
}

public class Run {
    public static void main(String[] args) {
        MyObject object = new MyObject();

        //線程A與線程B 持有的是同一個對象:object
        ThreadA a = new ThreadA(object);
        ThreadB b = new ThreadB(object);
        a.start();
        b.start();
    }
}

我們看到 當(dāng)兩個線程想要去訪問一個類的同步方法的時候澈魄,線程B需要等待線程A執(zhí)行完了methodA()方法之后,它才能執(zhí)行methodB()方法蕾哟。這樣一忱,線程A和線程B就實現(xiàn)了通信。

wait/notify機制

使用object類中的wait和notify方法能夠更高效率的進行線程間通信谭确,比第一種方法好的地方在于不用一直在程序中循環(huán)判斷條件帘营,當(dāng)具備可執(zhí)行條件的時候,會由另一個線程發(fā)出通知來告訴你逐哈。這段時間你可以做自己有用的事情芬迄。

下面是一個代碼例子:

package com.Thread.Communication;

import java.util.ArrayList;

//使用wait notify 機制進行線程間通信
public class T2 {
    public static void main(String[] args) {

    }
}

class Thread1 implements Runnable {

    private ArrayList<Integer> list;

    public Thread1(ArrayList<Integer> list) {
        // TODO Auto-generated constructor stub
        this.list = list;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub

        synchronized (list) {
            if (list.size() != 5) {
                System.out.println("ready in to wait()");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("back to life");
            }
            System.out.println("execute after list size turn to 5");
        }
    }
}

class Thread3 implements Runnable {

    private ArrayList<Integer> List;

    public Thread3(ArrayList<Integer> list) {
        // TODO Auto-generated constructor stub
        this.List = list;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        synchronized (List) {
            for (int i = 0; i < 10; i++) {
                List.add(i);
                System.out.println("put element into list" + i);
                if (List.size() == 5) {
                    System.out.println("ready to notify()");
                    List.notify();
                    System.out.println("notify done..");
                }
            }
        }
    }

}

class Client1 {
    public static void main(String[] args) throws InterruptedException {
        ArrayList<Integer> list = new ArrayList<>();
        Thread1 thead1 = new Thread1(list);
        Thread3 thread3 = new Thread3(list);

        Thread tt1 = new Thread(thead1);
        Thread tt2 = new Thread(thread3);

        tt1.start();

        Thread.sleep(1000);

        tt2.start();
    }
}

ready in to wait()
put element into list0
put element into list1
put element into list2
put element into list3
put element into list4
ready to notify()
notify done..
put element into list5
put element into list6
put element into list7

這個例子里面我們可以看到 Thread1 的功能是等list的數(shù)量大于5時,才執(zhí)行相應(yīng)的自己的方法昂秃。而Thread2則是一直不斷的去改變list的大小禀梳,當(dāng)集合中的大小達到指定的數(shù)量,就通過notify方法去喚醒正在等待的線程肠骆。也就是Thread1算途,Thread1在一開始就先去判斷size的大小,發(fā)現(xiàn)不符合則使用wait方法進入阻塞蚀腿。把cpu讓給Thread2去執(zhí)行嘴瓤。

Lock鎖

lock實際上和notify和wait的進程通信情況很像,但是lock比wait notify更加高效和靈活莉钙。不需要同步synchronize塊廓脆,而且不同的方法之間也會因為synchronize鎖導(dǎo)致沖突。

下面是一個代碼例子:

package com.Thread.Communication;

import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//使用Condition 來實現(xiàn)生產(chǎn)者消費者
public class T5 {

    public static Lock lock = new ReentrantLock();
    public static Condition notFull = lock.newCondition();
    public static Condition notEmpty = lock.newCondition();

    public static void main(String[] args) {
        Storge2 storge2 = new Storge2(new ArrayList<>());
        Consumer2 consumer2 = new Consumer2("李四", storge2);
        Producer2 producer1 = new Producer2("王五", storge2);

        new Thread(consumer2).start();
        new Thread(producer1).start();
    }
}

class Storge2 {
    private ArrayList<Integer> list;

    public Storge2(ArrayList<Integer> list) {
        // TODO Auto-generated constructor stub
        this.list = list;
    }

    public void put(int val) {
        list.add(val);
    }

    public int take() {
        return list.remove(0);
    }

    public int size() {
        return list.size();
    }
}

class Consumer2 implements Runnable {

    private String name;
    private Storge2 s;

    public Consumer2(String name, Storge2 s) {
        // TODO Auto-generated constructor stub
        this.name = name;
        this.s = s;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            T5.lock.lock();
            try {
                while (s.size() == 0) {
                    System.out.println(name + "   no product notify producer to procude");
                    T5.notEmpty.await();
                }
                System.out.println(name + "   i am ready consumer...");
                int v = s.take();
                T5.notFull.signal();
                System.out.println(name + "  consumer done   " + v);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                // TODO: handle finally clause
                T5.lock.unlock();
            }

        }
    }
}

class Producer2 implements Runnable {

    private String name;
    private Storge2 s;

    public Producer2(String name, Storge2 s) {
        // TODO Auto-generated constructor stub
        this.name = name;
        this.s = s;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            T5.lock.lock();
            try {
                while (s.size() == 5) {
                    System.out.println(name + "  full can't produce notify to consumer");
                    T5.notFull.await();
                }
                int val = (int) (Math.random() * 1000);
                System.out.println(name + "  i am ready to produce id:   " + val);
                s.put(val);
                T5.notEmpty.signal();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                // TODO: handle finally clause
                T5.lock.unlock();
            }
        }
    }
}


王五  i am ready to produce id:   989
王五  i am ready to produce id:   546
王五  i am ready to produce id:   528
王五  i am ready to produce id:   559
王五  full can't produce notify to consumer
李四   i am ready consumer...
李四  consumer done   279
李四   i am ready consumer...
李四  consumer done   989
李四   i am ready consumer...
李四  consumer done   546
李四   i am ready consumer...
李四  consumer done   528

這個例子也是下面的生產(chǎn)和消費者會用到的例子磁玉,我們看到停忿,當(dāng)產(chǎn)品生成滿了之后就會發(fā)出通知去通知消費者進行消費,消費產(chǎn)品到空時就會去通知生產(chǎn)者繼續(xù)生產(chǎn)蚊伞。

生產(chǎn)者-消費者模式 有哪些實現(xiàn)方法 給出例子

wait()/notify()機制
package com.Thread.Communication;

import java.util.ArrayList;

//使用notify wait 實現(xiàn)生產(chǎn)者 消費者
public class T4 {
    public static void main(String[] args) {
        Storge1 storge = new Storge1(new ArrayList<>());
        Consumer1 consumer1 = new Consumer1("張三", storge);
        Consumer1 consumer2 = new Consumer1("李四", storge);
        Producer1 producer1 = new Producer1("王五", storge);
        Producer1 producer2 = new Producer1("孫六", storge);

        new Thread(consumer2).start();
        new Thread(producer1).start();

    }
}

class Storge1 {
    private ArrayList<Integer> list;

    public Storge1(ArrayList<Integer> list) {
        // TODO Auto-generated constructor stub
        this.list = list;
    }

    public void put(int val) {
        list.add(val);
    }

    public int take() {
        return list.remove(0);
    }

    public int size() {
        return list.size();
    }
}

class Consumer1 implements Runnable {

    private String name;
    private Storge1 s;

    public Consumer1(String name, Storge1 s) {
        // TODO Auto-generated constructor stub
        this.name = name;
        this.s = s;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            synchronized (s) {
                while (s.size() == 0) {
                    System.out.println(name + "   no product notify producer to procude");
                    try {
                        s.wait();

                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                System.out.println(name + "   i am ready consumer...");
                int v = s.take();
                s.notifyAll();
                System.out.println(name + "  consumer done   " + v);

            }
        }
    }
}

class Producer1 implements Runnable {

    private String name;
    private Storge1 s;

    public Producer1(String name, Storge1 s) {
        // TODO Auto-generated constructor stub
        this.name = name;
        this.s = s;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {

            synchronized (s) {
                while (s.size() == 5) {
                    System.out.println(name + "  full can't produce notify to consumer");
                    try {

                        s.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                int val = (int) (Math.random() * 1000);
                System.out.println(name + "  i am ready to produce id:   " + val);
                s.put(val);
                s.notifyAll();

            }
        }
    }
}

通過這兩個方法去在適當(dāng)?shù)臅r候告訴生產(chǎn)者或者消費者該做什么事情了席赂。

BlockingQueue實現(xiàn)

同步包下的阻塞隊列是一個可以實現(xiàn)同步功能的隊列吮铭,它的特點是在加入和刪除操作中加入了鎖機制,其實底層原理也是使用ReentrantLock 和Condition來實現(xiàn)的氧枣。當(dāng)隊列滿時沐兵,執(zhí)行加入操作會阻塞。并喚醒擁有刪除操作Condition的線程去執(zhí)行便监。當(dāng)隊列空時,執(zhí)行刪除操作會阻塞碳想。并喚醒擁有加入操作的線程Condition去執(zhí)行烧董。

下面就是代碼例子:

package com.Thread.Communication;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//使用BlockingQueue來實現(xiàn)生產(chǎn)者消費者
public class T3 {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3);
        Storge storge = new Storge(queue);
        Consumer consumer1 = new Consumer("張三", storge);
        Consumer consumer2 = new Consumer("李四", storge);
        Producer producer1 = new Producer("王五", storge);
        Producer producer2 = new Producer("孫六", storge);

        ExecutorService service = Executors.newCachedThreadPool();
        service.submit(producer2);
        service.submit(producer1);
        service.submit(consumer1);
        service.submit(consumer2);
        service.shutdown();
    }
}

class Storge {

    private BlockingQueue<Integer> queue;

    public Storge(BlockingQueue<Integer> queue) {
        // TODO Auto-generated constructor stub
        this.queue = queue;
    }

    public void queue_put(int val) throws InterruptedException {
        queue.put(val);
    }

    public int queue_take() throws InterruptedException {
        return queue.take();
    }
}

class Consumer implements Runnable {

    private String name;
    private Storge s;

    public Consumer(String name, Storge s) {
        // TODO Auto-generated constructor stub
        this.name = name;
        this.s = s;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        // while (true) {
        System.out.println(name + "   i am ready consumer...");

        try {
            int val = s.queue_take();
            System.out.println(name + "  i consumer product id :  " + val);
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // }
    }
}

class Producer implements Runnable {

    private String name;
    private Storge s;

    public Producer(String name, Storge s) {
        // TODO Auto-generated constructor stub
        this.name = name;
        this.s = s;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        // while (true) {

        int val = (int) (Math.random() * 1000);
        System.out.println(name + "  i am ready to produce id:   " + val);
        try {
            s.queue_put(val);
            // System.out.println(name + " produce done id: " + val);
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // }
    }
}


張三   i am ready consumer...
李四   i am ready consumer...
王五  i am ready to produce id:   778
孫六  i am ready to produce id:   930
張三  i consumer product id :  778
李四  i consumer product id :  930

使用阻塞隊列很容易就實現(xiàn)了生產(chǎn)者消費者模式。不需要再單獨考慮同步和線程間通信的問題胧奔;

在并發(fā)編程中逊移,一般推薦使用阻塞隊列,這樣實現(xiàn)可以盡量地避免程序出現(xiàn)意外的錯誤龙填。

阻塞隊列使用最經(jīng)典的場景就是socket客戶端數(shù)據(jù)的讀取和解析胳泉,讀取數(shù)據(jù)的線程不斷將數(shù)據(jù)放入隊列,然后解析線程不斷從隊列取數(shù)據(jù)解析岩遗。還有其他類似的場景扇商,只要符合生產(chǎn)者-消費者模型的都可以使用阻塞隊列。

Condition實現(xiàn)

這個和上面的阻塞隊列有異曲同工之處

下面是代碼例子:

package com.Thread.Communication;

import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//使用Condition 來實現(xiàn)生產(chǎn)者消費者
public class T5 {

   public static Lock lock = new ReentrantLock();
   public static Condition notFull = lock.newCondition();
   public static Condition notEmpty = lock.newCondition();

   public static void main(String[] args) {
       Storge2 storge2 = new Storge2(new ArrayList<>());
       Consumer2 consumer2 = new Consumer2("李四", storge2);
       Producer2 producer1 = new Producer2("王五", storge2);

       new Thread(consumer2).start();
       new Thread(producer1).start();
   }
}

class Storge2 {
   private ArrayList<Integer> list;

   public Storge2(ArrayList<Integer> list) {
       // TODO Auto-generated constructor stub
       this.list = list;
   }

   public void put(int val) {
       list.add(val);
   }

   public int take() {
       return list.remove(0);
   }

   public int size() {
       return list.size();
   }
}

class Consumer2 implements Runnable {

   private String name;
   private Storge2 s;

   public Consumer2(String name, Storge2 s) {
       // TODO Auto-generated constructor stub
       this.name = name;
       this.s = s;
   }

   @Override
   public void run() {
       // TODO Auto-generated method stub
       while (true) {
           T5.lock.lock();
           try {
               while (s.size() == 0) {
                   System.out.println(name + "   no product notify producer to procude");
                   T5.notEmpty.await();
               }
               System.out.println(name + "   i am ready consumer...");
               int v = s.take();
               T5.notFull.signal();
               System.out.println(name + "  consumer done   " + v);
           } catch (InterruptedException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
           } finally {
               // TODO: handle finally clause
               T5.lock.unlock();
           }

       }
   }
}

class Producer2 implements Runnable {

   private String name;
   private Storge2 s;

   public Producer2(String name, Storge2 s) {
       // TODO Auto-generated constructor stub
       this.name = name;
       this.s = s;
   }

   @Override
   public void run() {
       // TODO Auto-generated method stub
       while (true) {
           T5.lock.lock();
           try {
               while (s.size() == 5) {
                   System.out.println(name + "  full can't produce notify to consumer");
                   T5.notFull.await();
               }
               int val = (int) (Math.random() * 1000);
               System.out.println(name + "  i am ready to produce id:   " + val);
               s.put(val);
               T5.notEmpty.signal();
           } catch (InterruptedException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
           } finally {
               // TODO: handle finally clause
               T5.lock.unlock();
           }
       }
   }
}

王五  i am ready to produce id:   632
王五  i am ready to produce id:   93
王五  i am ready to produce id:   479
王五  i am ready to produce id:   874
王五  full can't produce notify to consumer
李四   i am ready consumer...
李四  consumer done   325
李四   i am ready consumer...
李四  consumer done   632
李四   i am ready consumer...
李四  consumer done   93
李四   i am ready consumer...
李四  consumer done   479
李四   i am ready consumer...
李四  consumer done   874
李四   no product notify producer to procude

synchronize和lock區(qū)別

1)Lock是一個接口宿礁,而synchronized是Java中的關(guān)鍵字案铺,synchronized是內(nèi)置的語言實現(xiàn);

2)synchronized在發(fā)生異常時梆靖,會自動釋放線程占有的鎖控汉,因此不會導(dǎo)致死鎖現(xiàn)象發(fā)生;而Lock在發(fā)生異常時返吻,如果沒有主動通過unLock()去釋放鎖姑子,則很可能造成死鎖現(xiàn)象,因此使用Lock時需要在finally塊中釋放鎖测僵;

3)Lock可以讓等待鎖的線程響應(yīng)中斷街佑,而synchronized卻不行,使用synchronized時恨课,等待的線程會一直等待下去舆乔,不能夠響應(yīng)中斷;

4)通過Lock可以知道有沒有成功獲取鎖剂公,而synchronized卻無法辦到希俩。

5)Lock可以提高多個線程進行讀操作的效率。

阻塞隊列介紹

ArrayBlockingQueue:基于數(shù)組實現(xiàn)的一個阻塞隊列纲辽,在創(chuàng)建ArrayBlockingQueue對象時必須制定容量大小颜武。并且可以指定公平性與非公平性璃搜,默認(rèn)情況下為非公平的,即不保證等待時間最長的隊列最優(yōu)先能夠訪問隊列鳞上。

LinkedBlockingQueue:基于鏈表實現(xiàn)的一個阻塞隊列这吻,在創(chuàng)建LinkedBlockingQueue對象時如果不指定容量大小,則默認(rèn)大小為Integer.MAX_VALUE篙议。

PriorityBlockingQueue:以上2種隊列都是先進先出隊列唾糯,而PriorityBlockingQueue卻不是,它會按照元素的優(yōu)先級對元素進行排序鬼贱,按照優(yōu)先級順序出隊移怯,每次出隊的元素都是優(yōu)先級最高的元素。注意这难,此阻塞隊列為無界阻塞隊列舟误,即容量沒有上限(通過源碼就可以知道,它沒有容器滿的信號標(biāo)志)姻乓,前面2種都是有界隊列嵌溢。

鎖的相關(guān)概念介紹

可重入鎖

基于線程的分配,而不是基于方法調(diào)用的分配蹋岩。
舉一個例子來說明:

class MyClass {
    public synchronized void method1() {
        method2();
    }
     
    public synchronized void method2() {
         
    }
}

上述代碼中的兩個方法method1和method2都用synchronized修飾了赖草,假如某一時刻,線程A執(zhí)行到了method1星澳,此時線程A獲取了這個對象的鎖疚顷,而由于method2也是synchronized方法,假如synchronized不具備可重入性禁偎,此時線程A需要重新申請鎖腿堤。但是這就會造成一個問題,因為線程A已經(jīng)持有了該對象的鎖如暖,而又在申請獲取該對象的鎖笆檀,這樣就會線程A一直等待永遠不會獲取到的鎖。

而由于synchronized和Lock都具備可重入性盒至,所以不會發(fā)生上述現(xiàn)象酗洒。

可中斷鎖

可中斷鎖:顧名思義,就是可以相應(yīng)中斷的鎖枷遂。

在Java中樱衷,synchronized就不是可中斷鎖,而Lock是可中斷鎖酒唉。

注意矩桂,當(dāng)一個線程獲取了鎖之后,是不會被interrupt()方法中斷的痪伦。只能中斷阻塞過程中的線程侄榴。

因此當(dāng)通過lockInterruptibly()方法獲取某個鎖時雹锣,如果不能獲取到,只有進行等待的情況下癞蚕,是可以響應(yīng)中斷的蕊爵。

而用synchronized修飾的話,當(dāng)一個線程處于等待某個鎖的狀態(tài)桦山,是無法被中斷的攒射,只有一直等待下去。

如果某一線程A正在執(zhí)行鎖中的代碼恒水,另一線程B正在等待獲取該鎖匆篓,可能由于等待時間過長,線程B不想等待了寇窑,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它箩张,這種就是可中斷鎖甩骏。

下面看個例子:

package com.Thread.Communication;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class T6 {
    public static Lock lock = new ReentrantLock();

    public static void main(String[] args) {

        Resorce resorce = new Resorce();
        MyThread thread = new MyThread(resorce);
        MyThread thread2 = new MyThread(resorce);

        thread.start();
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            // TODO: handle exception
        }
        thread2.start();
        try {
            System.out.println("等待2秒 如果拿不到鎖我就自己中斷");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        thread2.interrupt();
    }
}

class Resorce {

    public void dosomething() throws InterruptedException {
        T6.lock.lockInterruptibly();// 注意,如果需要正確中斷等待鎖的線程先慷,必須將獲取鎖放在外面饮笛,然后將InterruptedException拋出
        try {
            // T6.lock.lockInterruptibly();
            System.out.println(Thread.currentThread().getName() + "  get the lock");
            System.out.println("exe 5seconds..");
            long startTime = System.currentTimeMillis();
            Thread.sleep(10000);
            System.out.println("exe done...");
        } finally {
            System.out.println("我準(zhǔn)備釋放鎖");
            T6.lock.unlock();
            System.out.println(Thread.currentThread().getName() + "  release lock");
        }
    }
}

class MyThread extends Thread {

    private Resorce r;

    public MyThread(Resorce r) {
        // TODO Auto-generated constructor stub
        this.r = r;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            r.dosomething();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            System.out.println(Thread.currentThread().getName() + "  收到中斷信號超歌。");
        }
    }

}

這里需要非常注意的一點是 在注釋中說的 如果要正確中斷等待鎖的線程常熙,必須將鎖放在try外面 然后方法要拋出InterruptedException異常冯挎。

為什么要這樣做焕盟?

因為如果放在try語句塊里面欣鳖。你會發(fā)現(xiàn)當(dāng)lockInterruptibly()方法要拋出異常時寸宏,無論是catch還是throws幕袱。都會進入到finally語句塊中去執(zhí)行仪际,此時finally語句塊里面的unlock方法就會報錯java.lang.IllegalMonitorStateException祝谚,原因就是線程并沒有獲取到鎖宪迟,而你卻要去釋放鎖。如果放在try語句塊外面 交惯,那么它馬上就會拋出異常次泽,而不進入try語句塊中去。

出錯時的輸出

Thread-0  get the lock
exe 5seconds..
等待2秒 如果拿不到鎖我就自己中斷
我準(zhǔn)備釋放鎖
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(Unknown Source)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(Unknown Source)
    at java.util.concurrent.locks.ReentrantLock.unlock(Unknown Source)
    at com.Thread.Communication.Resorce.dosomething(T6.java:48)
    at com.Thread.Communication.MyThread.run(T6.java:67)
exe done...
我準(zhǔn)備釋放鎖
Thread-0  release lock

正確輸出

Thread-0  get the lock
exe 5seconds..
等待2秒 如果拿不到鎖我就自己中斷
Thread-1  收到中斷信號席爽。
exe done...
我準(zhǔn)備釋放鎖
Thread-0  release lock

公平意荤、非公平鎖

公平鎖即盡量以請求鎖的順序來獲取鎖。比如同是有多個線程在等待一個鎖只锻,當(dāng)這個鎖被釋放時玖像,等待時間最久的線程(最先請求的線程)會獲得該所,這種就是公平鎖炬藤。

非公平鎖即無法保證鎖的獲取是按照請求鎖的順序進行的御铃。這樣就可能導(dǎo)致某個或者一些線程永遠獲取不到鎖碴里。

在Java中,synchronized就是非公平鎖上真,它無法保證等待的線程獲取鎖的順序咬腋。

而對于ReentrantLock和ReentrantReadWriteLock,它默認(rèn)情況下是非公平鎖睡互,但是可以設(shè)置為公平鎖根竿。

讀寫鎖

讀寫鎖將對一個資源(比如文件)的訪問分成了2個鎖,一個讀鎖和一個寫鎖就珠。ReadWriteLock就是讀寫鎖寇壳,它是一個接口,ReentrantReadWriteLock實現(xiàn)了這個接口妻怎。

可以通過readLock()獲取讀鎖壳炎,通過writeLock()獲取寫鎖。

下面的例子會說逼侦。

java.util.concurrent.locks包下常用的類

Lock
public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

lock():獲取鎖匿辩,如果鎖已被其他線程獲取,則進行等待
unlock():釋放鎖 記得要在finally中釋放 不手動釋放會死鎖

tryLock()方法是有返回值的榛丢,它表示用來嘗試獲取鎖铲球,如果獲取成功,則返回true晰赞,如果獲取失敿诓 (即鎖已被其他線程獲取)掖鱼,則返回false然走,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待锨用。

tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的丰刊,只不過區(qū)別在于這個方法在拿不到鎖時會等待一定的時間,在時間期限之內(nèi)如果還拿不到鎖增拥,就返回false啄巧。如果如果一開始拿到鎖或者在等待期間內(nèi)拿到了鎖,則返回true掌栅。

ReentrantLock

ReentrantLock秩仆,意思是“可重入鎖”。ReentrantLock是唯一實現(xiàn)了Lock接口的類猾封,在上面的例子中我們經(jīng)吵嗡#看到這個鎖的身影。

ReadWriteLock

ReadWriteLock也是一個接口,在它里面只定義了兩個方法:

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading.
     */
    Lock readLock();
 
    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing.
     */
    Lock writeLock();
}

一個用來獲取讀鎖齐莲,一個用來獲取寫鎖痢站。也就是說將文件的讀寫操作分開,分成2個鎖來分配給線程选酗,從而使得多個線程可以同時進行讀操作阵难。下面的ReentrantReadWriteLock實現(xiàn)了ReadWriteLock接口。

ReentrantReadWriteLock

ReentrantReadWriteLock里面提供了很多豐富的方法芒填,不過最主要的有兩個方法:readLock()和writeLock()用來獲取讀鎖和寫鎖呜叫。

下面是一個讀寫鎖的使用例子:

public class Test {
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
     
    public static void main(String[] args)  {
        final Test test = new Test();
         
        new Thread(){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();
         
    }  
     
    public void get(Thread thread) {
        rwl.readLock().lock();
        try {
            long start = System.currentTimeMillis();
             
            while(System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName()+"正在進行讀操作");
            }
            System.out.println(thread.getName()+"讀操作完畢");
        } finally {
            rwl.readLock().unlock();
        }
    }
}

Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作

發(fā)現(xiàn)多個線程同步進行讀操作,而如果是synchronize的話則只能一個線程讀殿衰,直到它釋放鎖為止朱庆。

如果一個線程占用讀鎖,另一個線程申請寫鎖闷祥,則申請寫鎖的線程會一直等待釋放讀鎖娱颊。
如果一個線程占用寫鎖,另一個線程申請讀鎖或者寫鎖凯砍,則這個線程會一直等待釋放寫鎖

死鎖產(chǎn)生的原因和四個必要條件维蒙,預(yù)防死鎖的經(jīng)典算法

四個必要條件

(1) 互斥條件:一個資源每次只能被一個進程使用。
(2) 請求與保持條件:一個進程因請求資源而阻塞時果覆,對已獲得的資源保持不放。
(3) 不剝奪條件:進程已獲得的資源殖熟,在末使用完之前局待,不能強行剝奪。
(4) 循環(huán)等待條件:若干進程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系菱属。
這四個條件是死鎖的必要條件钳榨,只要系統(tǒng)發(fā)生死鎖,這些條件必然成立纽门,而只要上述條件之
一不滿足薛耻,就不會發(fā)生死鎖。

產(chǎn)生死鎖的原因主要是:

(1) 因為系統(tǒng)資源不足赏陵。
(2) 進程運行推進的順序不合適饼齿。
(3) 資源分配不當(dāng)?shù)取?/p>

預(yù)防死鎖算法

銀行家算法

銀行家算法的基本思想是分配資源之前,判斷系統(tǒng)是否是安全的蝙搔;若是缕溉,才分配。它是最具有代表性的避免死鎖的算法吃型。

設(shè)進程i提出請求Request[j]证鸥,則銀行家算法按如下規(guī)則進行判斷。
(1) 如果Request[j]≤Need[i,j],則轉(zhuǎn)向(2)枉层,否則認(rèn)為出錯泉褐,因為它所需要的資源數(shù)已超過它所宣布的最大值。
(2) 如果Request[j]≤Available[j]鸟蜡,則轉(zhuǎn)向(3)膜赃;否則表示尚無足夠資源,Pi需等待矩欠。
(3) 假設(shè)進程i的申請已獲批準(zhǔn)财剖,于是修改系統(tǒng)狀態(tài):
Available[j]=Available[j]-Request[i]
Allocation[i,j]=Allocation[i,j]+Request[j]
Need[i,j]=Need[i,j]-Request[j]
(4)系統(tǒng)執(zhí)行安全性檢查,如安全癌淮,則分配成立躺坟;否則試探險性分配作廢,系統(tǒng)恢復(fù)原狀乳蓄,進程等待咪橙。

安全性檢查算法

(1) 設(shè)置兩個工作向量Work=Available;Finish[i]=False
(2) 從進程集合中找到一個滿足下述條件的進程虚倒,
Finish [i]=False;
Need[i,j]≤Work[j];
如找到美侦,執(zhí)行(3);否則魂奥,執(zhí)行(4)
(3) 設(shè)進程獲得資源菠剩,可順利執(zhí)行,直至完成耻煤,從而釋放資源具壮。
Work[j]=Work[j]+Allocation[i,j];
Finish[i]=True;
go to step 2;
(4) 如所有的進程Finish[i]=true,則表示安全哈蝇;否則系統(tǒng)不安全棺妓。

參考文章:
JAVA多線程之線程間的通信方式
Java多線程-并發(fā)協(xié)作(生產(chǎn)者消費者模型)
Java并發(fā)編程:線程間協(xié)作的兩種方式:wait、notify炮赦、notifyAll和Condition
Java并發(fā)編程:阻塞隊列
Java并發(fā)編程:Lock
死鎖的定義怜跑、產(chǎn)生原因、必要條件吠勘、避免死鎖和解除死鎖的方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末性芬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子剧防,更是在濱河造成了極大的恐慌批旺,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诵姜,死亡現(xiàn)場離奇詭異汽煮,居然都是意外死亡搏熄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門暇赤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來心例,“玉大人,你說我怎么就攤上這事鞋囊≈购螅” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵溜腐,是天一觀的道長译株。 經(jīng)常有香客問我,道長挺益,這世上最難降的妖魔是什么歉糜? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮望众,結(jié)果婚禮上匪补,老公的妹妹穿的比我還像新娘。我一直安慰自己烂翰,他們只是感情好夯缺,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著甘耿,像睡著了一般踊兜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上佳恬,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天润文,我揣著相機與錄音,去河邊找鬼殿怜。 笑死,一個胖子當(dāng)著我的面吹牛曙砂,可吹牛的內(nèi)容都是我干的头谜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鸠澈,長吁一口氣:“原來是場噩夢啊……” “哼柱告!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起笑陈,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤际度,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后涵妥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乖菱,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了窒所。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹉勒。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖吵取,靈堂內(nèi)的尸體忽然破棺而出禽额,到底是詐尸還是另有隱情,我是刑警寧澤皮官,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布脯倒,位于F島的核電站,受9級特大地震影響捺氢,放射性物質(zhì)發(fā)生泄漏藻丢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一讯沈、第九天 我趴在偏房一處隱蔽的房頂上張望郁岩。 院中可真熱鬧,春花似錦缺狠、人聲如沸问慎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽如叼。三九已至,卻和暖如春穷劈,著一層夾襖步出監(jiān)牢的瞬間笼恰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工歇终, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留社证,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓评凝,卻偏偏與公主長得像追葡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子奕短,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java-Review-Note——4.多線程 標(biāo)簽: JavaStudy PS:本來是分開三篇的宜肉,后來想想還是整...
    coder_pig閱讀 1,653評論 2 17
  • 1.解決信號量丟失和假喚醒 public class MyWaitNotify3{ MonitorObject m...
    Q羅閱讀 879評論 0 1
  • 第三章 Java內(nèi)存模型 3.1 Java內(nèi)存模型的基礎(chǔ) 通信在共享內(nèi)存的模型里,通過寫-讀內(nèi)存中的公共狀態(tài)進行隱...
    澤毛閱讀 4,355評論 2 22
  • 師者翎碑,所以傳道受業(yè)解惑也谬返,這就是為什么我們需要老師。很多問題很多坑日杈,如果自己去摸索遣铝,跌的鼻青臉腫還未必知道問題在哪...
    楊一韻閱讀 303評論 0 2
  • 首頁展開佑刷,千言萬語也言不盡我心中的情意,說不完那幽怨凄迷的哀腸翰蠢。帶著一片淚眼的婆娑项乒,我黯然傷神;心中的那盞明燈梁沧,卻...
    大魚研習(xí)社閱讀 448評論 0 1