6_多线程锁

Posted root_zhb

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了6_多线程锁相关的知识,希望对你有一定的参考价值。

1、8锁问题

class Phone {
    public synchronized void sendSMS() throws Exception {
        //停留 4 秒
        TimeUnit.SECONDS.sleep(4);
        System.out.println("------sendSMS");
    }
    public synchronized void sendEmail() throws Exception {
        System.out.println("------sendEmail");
    }
    public void getHello() {
        System.out.println("------getHello");
    }
}
public class Lock_8{
    public static void main(String[] args) throws InterruptedException {
        Phone phone=new Phone();
        Phone phone2=new Phone();

        new Thread(()->{
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);
        new Thread(()->{
            try {
                phone.sendEmail();
//                phone.getHello();
//                phone2.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"BB").start();

    }
}
  1. 标准访问:先打印短信还是邮件
    ------sendSMS
    ------sendEmail
  2. 停4秒在短信方法内,先打印短信还是邮件
    ------sendSMS
    ------sendEmail
  3. 新增普通的hello方法,时先打短信还是hello
    ------getHello
    ------sendSMS
  4. 现有两部手机,先打印短信还是邮件
    ------sendEmail
    ------sendSMS
  5. 两个静态同步方法,1部手机,先打印短信还是要邮件
    ------sendSMS
    ------sendEmail
  6. 两个静态同步方法,2部手机,先打印短信还是邮件
    ------sendSMS
    ------sendEmail
  7. 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
    ------sendEmail
    ------sendSMS
  8. 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
    ------sendEmail
    ------sendSMS

1、2 锁的是当前对象
3 hello与锁无关,先执行
4 两部手机,锁的是不同对象,所以先Email后SMS
5 6 static synchronized 锁的范围是 Class 字节码对象
7 8 一个静态同步方法(锁定大写的Class),一个普通同步方法(锁定this),锁的范围不一样

synchronized 实现同步的基础:Java 中的每一个对象都可以作为锁。
具体表现为以下 3 种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的 Class 对象。
对于同步方法块,锁是 Synchonized 括号里配置的对象

2、公平锁和非公平锁

  1. 什么是公平锁和非公平锁?

    • 公平锁:是指多个线程按照申请锁的顺序来获取锁。
    • 非公平锁:是指在多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁,在高并发的情况下,有可能造成优先级反转或者饥饿现象
      synchronized 和 ReentrantLock 默认是非公平锁
  2. 源码解读(ReentrantLock默认是非公平锁)
    公平锁:排序排队,就是判断同步队列是否还有先驱节点的存在(我前面还有人吗?),如果没有先驱节点才能获锁
    非公平锁:先占先得,是不管这个事的,只要能抢获到同步状态就可以
    ReentrantLock默认是非公平锁,公平锁要多一个方法,所以非公平锁的性能更好(aqs源码)

  3. 为什么会有公平锁、非公平锁的设计?为什么默认非公平?

    • 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间存在的还是很明显的,所以非公平锁能更充分的利用CPU的时间片,尽量减少CPU空闲状态时间
    • 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当一个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大了,所以就减少了线程的开销线程的开销
  4. 什么时候用公平?什么时候用非公平?
    如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了。否则那就用公平锁,大家公平使用

3、可重入锁(递归锁)

  1. 可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
  2. ReentrantLock和Synchronized都是可重入锁
  3. 可: 可以
    重: 再次
    入: 进入
    锁: 同步锁
    进入什么:进入同步域(即同步代码块、方法或显示锁锁定的代码)

验证 synchronized

public class Test1 {
    public static void main(String[] args) {
        Object o=new Object();
        new Thread(()->{
            synchronized (o){
                System.out.println(Thread.currentThread().getName()+"  外层");

                synchronized (o){
                    System.out.println(Thread.currentThread().getName()+"  内层");
                }
            }
        },"t1").start();
    }
}

验证Lock

public class Test1 {
    public static void main(String[] args) {
        Lock lock=new ReentrantLock();

        new Thread(()->{
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName()+" 外层");
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName()+" 外层");
                }finally {
                    lock.unlock();
                }
            }finally {
                lock.unlock();
            }
        },"t2").start();
    }
}

4、死锁

  1. 死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去。
  2. 产生死锁的原因:(1) 因为系统资源不足。(2) 进程运行推进的顺序不合适。(3) 资源分配不当等。

4.1、代码

public class DeadLockTest {
    static Object a=new Object();
    static Object b=new Object();
    public static void main(String[] args) {
        new Thread(()->{
            synchronized (a){
                System.out.println(Thread.currentThread().getName()+" 持有a,试图获取b");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b){
                    System.out.println(Thread.currentThread().getName()+" 获取锁b");
                }
            }
        },"A").start();

        new Thread(()->{
            synchronized (b){
                System.out.println(Thread.currentThread().getName()+" 持有b,试图获取a");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a){
                    System.out.println(Thread.currentThread().getName()+" 获取锁a");
                }
            }
        },"B").start();
    }
}

4.2、验证

jps
jstack ID

以上是关于6_多线程锁的主要内容,如果未能解决你的问题,请参考以下文章

JUC高级多线程_07:读写锁与阻塞队列的具体介绍与使用

起底多线程同步锁(iOS)

6.23Java多线程可重入锁实现原理

python-多线程趣味(锁)

Python队列与多线程及文件锁

Linux多线程_(线程池,单例模式,读者写者问题,自旋锁)