博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
线程学习(二)-锁的概念、死锁和生产者消费者问题
阅读量:6002 次
发布时间:2019-06-20

本文共 11120 字,大约阅读时间需要 37 分钟。

0.锁的相关概念

Lock和synchronized有以下几点不同:

  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

  5)Lock可以提高多个线程进行读操作的效率。

  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

 

锁的相关概念:

1.可重入锁

  如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。

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都具备可重入性,所以不会发生上述现象。

2.可中断锁

  可中断锁:顾名思义,就是可以相应中断的锁。

  在Java中,synchronized就不是可中断锁,而Lock是可中断锁。

  如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。

  在前面演示lockInterruptibly()的用法时已经体现了Lock的可中断性。

3.公平锁

  公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。

  非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。

  在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。

  而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

 

 

1.死锁问题:

  死锁是由于一个线程锁住一个资源A,等待另一个B;而另一个线程锁住资源B,等待资源A造成的资源竞争引起的问题。

例如:一个死锁的例子:

synchronized实现方法:

package Thread;/** * 测试死锁 *思路:两个线程,每个线程占有不同的资源,等待其他资源* @author: qlq* @date :  2018年6月14日上午10:37:24 */public class DeadLockTest {    public static void main(String[] args) {        MyThread t1 = new MyThread(true);        MyThread t2 = new MyThread(false);        t1.start();        t2.start();    }    }class MyThread extends Thread{    private boolean flag;//标记走哪个线路    static Object A = new Object();//资源A    static Object B = new Object();//资源B    public boolean isFlag() {        return flag;    }    public void setFlag(boolean flag) {        this.flag = flag;    }    protected MyThread(boolean flag) {        super();        this.flag = flag;    }    @Override    public void run() {        if(flag){
//占有资源A,等待资源B synchronized(A){ System.out.println("占有资源A,等待资源B"); synchronized(B){ System.out.println("占有资源B"); } } }else{
//占有资源B,等待资源A synchronized(B){ System.out.println("占有资源B,等待资源A"); synchronized(A){ System.out.println("占有资源A"); } } } }}

 

 

结果:

占有资源B,等待资源A

占有资源A,等待资源B

 

解决办法:最好在一个代码快中对一个对象进行同步上锁,避免重复锁资源。。。。。。。。。。。。。。。。。。。。

 

 

 

 

 

 

 

Lock死锁的例子:

package Thread;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * 测试死锁 思路:两个线程,每个线程占有不同的资源,等待其他资源 *  * @author: qlq * @date : 2018年6月14日上午10:37:24 */public class DeadLockTest1 {    public static void main(String[] args) {        Lock lock1 = new ReentrantLock();        Lock lock2 = new ReentrantLock();        MyThread1 t1 = new MyThread1(true,lock1,lock2);        MyThread1 t2 = new MyThread1(false,lock1,lock2);        t1.start();        t2.start();    }}class MyThread1 extends Thread {    private boolean flag;// 标记走哪个线路    private Lock lock1;//第一把锁    private Lock lock2;//第二把锁    public boolean isFlag() {        return flag;    }    public void setFlag(boolean flag) {        this.flag = flag;    }    protected MyThread1(boolean flag, Lock lock1, Lock lock2) {        super();        this.flag = flag;        this.lock1 = lock1;        this.lock2 = lock2;    }    @Override    public void run() {        if (flag) {
// 占有资源A,等待资源B if(lock1.tryLock()){ try { Thread.sleep(2*1000); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"占有第一把锁,等待第二把锁"); try { if(lock2.tryLock(50*1000,TimeUnit.SECONDS)){ try { Thread.sleep(2*1000); System.out.println(Thread.currentThread().getName()+"占有第二把锁"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"释放第二把锁"); lock2.unlock(); System.out.println(Thread.currentThread().getName()+"释放第一把锁"); lock1.unlock(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } else {
// 占有第二把锁,等待第一把锁 if(lock2.tryLock()){ try { Thread.sleep(2*1000); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"占有第二把锁,等待第一把锁"); try { if(lock1.tryLock(50*1000,TimeUnit.SECONDS)){ try { Thread.sleep(2*1000); System.out.println(Thread.currentThread().getName()+"占有第一把锁"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"释放第一把锁"); lock1.unlock(); System.out.println(Thread.currentThread().getName()+"释放第二把锁"); lock2.unlock(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }}

 

结果:

Thread-0占有第一把锁,等待第二把锁

Thread-1占有第二把锁,等待第一把锁

 

解决办法:在正确的地方释放锁。。。。

 

注意:锁必须是同一把锁才会生效,如果锁作为局部变量是不会生效的,局部变量是每个线程一把锁。。。。。

 

2.生产者消费者问题(线程同步问题):

  生产者生产数据到缓冲区中,消费者从缓冲区中取数据。

  如果缓冲区已经满了,则生产者线程阻塞;

  如果缓冲区为空,那么消费者线程阻塞。

1.方式一:   synchronized、wait和notify   

package Thread;/** * 测试生产者和消费者 *  * @author: qlq * @date : 2018年6月15日上午11:38:59 */public class ProducerConsumerWithNotify {    public static void main(String[] args) {        MyResource myResource = new MyResource();        //多个生产者一个消费者        MyConsumerThread myConsumerThread = new MyConsumerThread(myResource);//        MyConsumerThread myConsumerThread1 = new MyConsumerThread(myResource);//        MyConsumerThread myConsumerThread2 = new MyConsumerThread(myResource);        MyProducerThread myProducerThread = new MyProducerThread(myResource);        MyProducerThread myProducerThread1 = new MyProducerThread(myResource);        MyProducerThread myProducerThread2 = new MyProducerThread(myResource);        myProducerThread.start();        myProducerThread1.start();        myProducerThread2.start();        myConsumerThread.start();//        myConsumerThread1.start();//        myConsumerThread2.start();    }}/** * 资源类 一个加一个减(都有同步锁) *  * @author: qlq * @date : 2018年6月15日上午11:38:37 */class MyResource {    private int num;// 资源数量    private int capacity = 10;// 资源容量    /**     * 同步方法增加资源 如果数量大于容量,线程进入阻塞状态 否则通知消费者进行消费     */    public synchronized void add() {        if (num >= capacity) {
// 大于等于的话进入阻塞状态 try { wait(); System.out.println(Thread.currentThread().getName()+"进入线程等待。。。"); } catch (InterruptedException e) { e.printStackTrace(); } } else { num++;// 生产一件资源 System.out.println(Thread.currentThread().getName() + "生产一件资源,目前剩余资源" + num + "件"); notifyAll();// 通知消费者进行消费 } } /** * 同步方法移除资源 如果num>0,消费资源,通知生产者进行生产 否则的话进入阻塞队列 */ public synchronized void remove() { if (num > 0) { num--; System.out.println(Thread.currentThread().getName() + "消费一件资源,目前剩余" + num + "件"); notifyAll();// 唤醒生产者进行生产 } else { try { wait(); System.out.println(Thread.currentThread().getName()+"进入线程等待。。。"); } catch (InterruptedException e) { e.printStackTrace(); } } }}/** * 生产者类 * * @author: qlq * @date : 2018年6月16日上午11:08:05 */class MyProducerThread extends Thread { private MyResource resource; protected MyProducerThread(MyResource resource) { super(); this.resource = resource; } @Override public void run() { while (true) { try { Thread.sleep(2 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } resource.add(); } }}/** * 消费者线程 * * @author: qlq * @date : 2018年6月16日上午11:09:06 */class MyConsumerThread extends Thread { private MyResource resource; protected MyConsumerThread(MyResource resource) { super(); this.resource = resource; } @Override public void run() { while (true) { try { Thread.sleep(2 * 1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } resource.remove(); } }}

 

 关于生产者的更多实现方式参考:

关于synchronized和lock的区别参考:

 

补充:一个简单的死锁程序:

package cn.xm.exam.test;public class test {    public static void main(String[] args) {        final Integer lock1 = 1;        final Integer lock2 = 2;        Runnable r1 = new Runnable() {            @Override            public void run() {                synchronized (lock1) {                    try {                        System.out.println("lock1-----");                        Thread.sleep(2 * 1000);                        synchronized (lock2) {                            System.out.println("lock1-----lock2");                        }                    } catch (InterruptedException e) {                    }                }            }        };        new Thread(r1).start();        Runnable r2 = new Runnable() {            @Override            public void run() {                synchronized (lock2) {                    try {                        System.out.println("lock2-----");                        Thread.sleep(2 * 1000);                        synchronized (lock1) {                            System.out.println("lock2-----lock1");                        }                    } catch (InterruptedException e) {                    }                }            }        };        new Thread(r2).start();    }}

 

转载地址:http://pgbmx.baihongyu.com/

你可能感兴趣的文章
卸载 mac 自带的php
查看>>
84个SEO面试问题---网络营销新手老手都值得一看
查看>>
《从Servlet、Dubbo、Mybatis聊聊责任链究竟怎么用 》
查看>>
第七次作业——需求规格说明书
查看>>
Prometheus学习系列(十四)之配置规则
查看>>
汉字统计
查看>>
服务器开发中的多进程,多线程及多协程
查看>>
C/C++ 标准输入输出重定向
查看>>
ionic介绍
查看>>
[实战]MVC5+EF6+MySql企业网盘实战(23)——文档列表
查看>>
[译] ES2018(ES9)的新特性
查看>>
二维码生成及扫描
查看>>
Java中的常用集合类型总结
查看>>
理解OAuth 2.0
查看>>
#define 只是字符替换
查看>>
plsql密码过期 永久有效
查看>>
2018年5月17日笔记
查看>>
用navicat链接不上ubuntu中的mysql服务器
查看>>
Javascript基础复习 数据类型
查看>>
C# Selenium 破解腾讯滑动验证
查看>>