進程間通信 線程間通信 生產者-消費者模式

進程間通信有哪些方法?

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

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

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

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

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

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

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

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

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

共享變量

共享變量是使用一個volatile關鍵字的變量作為標記,來進行線程間的通信。
下面使用一個例子來說明:

package com.Thread.Communication;

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

   public void setFlag() {
       flag = true;
   }

   private void exe() {
       System.out.println("執行主業務程序....");
   }

   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
執行主業務程序....

在T1的方法中,我們可以看到,有一個標記量flag,然后還有一個主業務的執行程序方法,要實現的功能就是,當flag為真時才執行主程序的方法。使用了兩個線程,一個去改變狀態一個去執行方法。通過flag達到線程通信的目的。

同步方法

直接使用synchronize關鍵字加在方法上,使得要執行方法必須獲得對象鎖。一次只能一個線程執行。

下面是代碼例子:

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;
//省略構造方法
    @Override
    public void run() {
        super.run();
        object.methodA();
    }
}

public class ThreadB extends Thread {

    private MyObject object;
//省略構造方法
    @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();
    }
}

我們看到 當兩個線程想要去訪問一個類的同步方法的時候,線程B需要等待線程A執行完了methodA()方法之后,它才能執行methodB()方法。這樣,線程A和線程B就實現了通信。

wait/notify機制

使用object類中的wait和notify方法能夠更高效率的進行線程間通信,比第一種方法好的地方在于不用一直在程序中循環判斷條件,當具備可執行條件的時候,會由另一個線程發出通知來告訴你。這段時間你可以做自己有用的事情。

下面是一個代碼例子:

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的數量大于5時,才執行相應的自己的方法。而Thread2則是一直不斷的去改變list的大小,當集合中的大小達到指定的數量,就通過notify方法去喚醒正在等待的線程。也就是Thread1,Thread1在一開始就先去判斷size的大小,發現不符合則使用wait方法進入阻塞。把cpu讓給Thread2去執行。

Lock鎖

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

下面是一個代碼例子:

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 來實現生產者消費者
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

這個例子也是下面的生產和消費者會用到的例子,我們看到,當產品生成滿了之后就會發出通知去通知消費者進行消費,消費產品到空時就會去通知生產者繼續生產。

生產者-消費者模式 有哪些實現方法 給出例子

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

import java.util.ArrayList;

//使用notify wait 實現生產者 消費者
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();

            }
        }
    }
}

通過這兩個方法去在適當的時候告訴生產者或者消費者該做什么事情了。

BlockingQueue實現

同步包下的阻塞隊列是一個可以實現同步功能的隊列,它的特點是在加入和刪除操作中加入了鎖機制,其實底層原理也是使用ReentrantLock 和Condition來實現的。當隊列滿時,執行加入操作會阻塞。并喚醒擁有刪除操作Condition的線程去執行。當隊列空時,執行刪除操作會阻塞。并喚醒擁有加入操作的線程Condition去執行。

下面就是代碼例子:

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來實現生產者消費者
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

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

在并發編程中,一般推薦使用阻塞隊列,這樣實現可以盡量地避免程序出現意外的錯誤。

阻塞隊列使用最經典的場景就是socket客戶端數據的讀取和解析,讀取數據的線程不斷將數據放入隊列,然后解析線程不斷從隊列取數據解析。還有其他類似的場景,只要符合生產者-消費者模型的都可以使用阻塞隊列。

Condition實現

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

下面是代碼例子:

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 來實現生產者消費者
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區別

1)Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現;

2)synchronized在發生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;

3)Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷;

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

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

阻塞隊列介紹

ArrayBlockingQueue:基于數組實現的一個阻塞隊列,在創建ArrayBlockingQueue對象時必須制定容量大小。并且可以指定公平性與非公平性,默認情況下為非公平的,即不保證等待時間最長的隊列最優先能夠訪問隊列。

LinkedBlockingQueue:基于鏈表實現的一個阻塞隊列,在創建LinkedBlockingQueue對象時如果不指定容量大小,則默認大小為Integer.MAX_VALUE。

PriorityBlockingQueue:以上2種隊列都是先進先出隊列,而PriorityBlockingQueue卻不是,它會按照元素的優先級對元素進行排序,按照優先級順序出隊,每次出隊的元素都是優先級最高的元素。注意,此阻塞隊列為無界阻塞隊列,即容量沒有上限(通過源碼就可以知道,它沒有容器滿的信號標志),前面2種都是有界隊列。

鎖的相關概念介紹

可重入鎖

基于線程的分配,而不是基于方法調用的分配。
舉一個例子來說明:

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

上述代碼中的兩個方法method1和method2都用synchronized修飾了,假如某一時刻,線程A執行到了method1,此時線程A獲取了這個對象的鎖,而由于method2也是synchronized方法,假如synchronized不具備可重入性,此時線程A需要重新申請鎖。但是這就會造成一個問題,因為線程A已經持有了該對象的鎖,而又在申請獲取該對象的鎖,這樣就會線程A一直等待永遠不會獲取到的鎖。

而由于synchronized和Lock都具備可重入性,所以不會發生上述現象。

可中斷鎖

可中斷鎖:顧名思義,就是可以相應中斷的鎖。

在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。

注意,當一個線程獲取了鎖之后,是不會被interrupt()方法中斷的。只能中斷阻塞過程中的線程。

因此當通過lockInterruptibly()方法獲取某個鎖時,如果不能獲取到,只有進行等待的情況下,是可以響應中斷的。

而用synchronized修飾的話,當一個線程處于等待某個鎖的狀態,是無法被中斷的,只有一直等待下去。

如果某一線程A正在執行鎖中的代碼,另一線程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("我準備釋放鎖");
            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語句塊里面。你會發現當lockInterruptibly()方法要拋出異常時,無論是catch還是throws。都會進入到finally語句塊中去執行,此時finally語句塊里面的unlock方法就會報錯java.lang.IllegalMonitorStateException,原因就是線程并沒有獲取到鎖,而你卻要去釋放鎖。如果放在try語句塊外面 ,那么它馬上就會拋出異常,而不進入try語句塊中去。

出錯時的輸出

Thread-0  get the lock
exe 5seconds..
等待2秒 如果拿不到鎖我就自己中斷
我準備釋放鎖
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...
我準備釋放鎖
Thread-0  release lock

正確輸出

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

公平、非公平鎖

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

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

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

而對于ReentrantLock和ReentrantReadWriteLock,它默認情況下是非公平鎖,但是可以設置為公平鎖。

讀寫鎖

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

可以通過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()方法是類似的,只不過區別在于這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。

ReentrantLock

ReentrantLock,意思是“可重入鎖”。ReentrantLock是唯一實現了Lock接口的類,在上面的例子中我們經常看到這個鎖的身影。

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實現了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正在進行讀操作

發現多個線程同步進行讀操作,而如果是synchronize的話則只能一個線程讀,直到它釋放鎖為止。

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

死鎖產生的原因和四個必要條件,預防死鎖的經典算法

四個必要條件

(1) 互斥條件:一個資源每次只能被一個進程使用。
(2) 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
(3) 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
(4) 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之
一不滿足,就不會發生死鎖。

產生死鎖的原因主要是:

(1) 因為系統資源不足。
(2) 進程運行推進的順序不合適。
(3) 資源分配不當等。

預防死鎖算法

銀行家算法

銀行家算法的基本思想是分配資源之前,判斷系統是否是安全的;若是,才分配。它是最具有代表性的避免死鎖的算法。

設進程i提出請求Request[j],則銀行家算法按如下規則進行判斷。
(1) 如果Request[j]≤Need[i,j],則轉向(2),否則認為出錯,因為它所需要的資源數已超過它所宣布的最大值。
(2) 如果Request[j]≤Available[j],則轉向(3);否則表示尚無足夠資源,Pi需等待。
(3) 假設進程i的申請已獲批準,于是修改系統狀態:
Available[j]=Available[j]-Request[i]
Allocation[i,j]=Allocation[i,j]+Request[j]
Need[i,j]=Need[i,j]-Request[j]
(4)系統執行安全性檢查,如安全,則分配成立;否則試探險性分配作廢,系統恢復原狀,進程等待。

安全性檢查算法

(1) 設置兩個工作向量Work=Available;Finish[i]=False
(2) 從進程集合中找到一個滿足下述條件的進程,
Finish [i]=False;
Need[i,j]≤Work[j];
如找到,執行(3);否則,執行(4)
(3) 設進程獲得資源,可順利執行,直至完成,從而釋放資源。
Work[j]=Work[j]+Allocation[i,j];
Finish[i]=True;
go to step 2;
(4) 如所有的進程Finish[i]=true,則表示安全;否則系統不安全。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Java-Review-Note——4.多線程 標簽: JavaStudy PS:本來是分開三篇的,后來想想還是整...
    coder_pig閱讀 1,682評論 2 17
  • 1.解決信號量丟失和假喚醒 public class MyWaitNotify3{ MonitorObject m...
    Q羅閱讀 912評論 0 1
  • 第三章 Java內存模型 3.1 Java內存模型的基礎 通信在共享內存的模型里,通過寫-讀內存中的公共狀態進行隱...
    澤毛閱讀 4,383評論 2 22
  • 師者,所以傳道受業解惑也,這就是為什么我們需要老師。很多問題很多坑,如果自己去摸索,跌的鼻青臉腫還未必知道問題在哪...
    楊一韻閱讀 316評論 0 2
  • 首頁展開,千言萬語也言不盡我心中的情意,說不完那幽怨凄迷的哀腸。帶著一片淚眼的婆娑,我黯然傷神;心中的那盞明燈,卻...
    大魚研習社閱讀 462評論 0 1