进程间通信 线程间通信 生产者-消费者模式

进程间通信有哪些方法?

(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 list;

    public Thread1(ArrayList 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 List;

    public Thread3(ArrayList 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 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 list;

    public Storge2(ArrayList 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 list;

    public Storge1(ArrayList 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 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 queue;

    public Storge(BlockingQueue 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 list;

   public Storge2(ArrayList 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
死锁的定义、产生原因、必要条件、避免死锁和解除死锁的方法

你可能感兴趣的:(进程间通信 线程间通信 生产者-消费者模式)