首页 > 其他分享 >JUC高级篇-第2章 多线程锁

JUC高级篇-第2章 多线程锁

时间:2022-12-03 13:57:25浏览次数:45  
标签:JUC synchronized 高级 线程 公平 ------ new 多线程 public

1.乐观锁与悲观锁

悲观锁

认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。

适合写操作多的场景,先加锁可以保证写操作时数据正确。显式的锁定之后再操作同步资源

//=============悲观锁的调用方式
public synchronized void m1()
{
    //加锁后的业务逻辑......
}

// 保证多个线程使用的是同一个lock对象的前提下
ReentrantLock lock = new ReentrantLock();
public void m2() {
    lock.lock();
    try {
        // 操作同步资源
    }finally {
        lock.unlock();
    }
}

synchronized关键字和Lock的实现类都是悲观锁

乐观锁

乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。乐观锁则直接去操作同步资源,是一种无锁算法,得之我幸不得我命,再抢。乐观锁一般有两种实现方式:采用版本号机制、CAS(Compare-and-Swap,即比较并替换)算法实现。

//=============乐观锁的调用方式
// 保证多个线程使用的是同一个AtomicInteger
private AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.incrementAndGet();

2.Java的8种锁情况

1)Java8锁回顾

package com.atguigu.sync;

import java.util.concurrent.TimeUnit;

class Phone {

    public static 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");
    }
}

/**
 * @Description: 8锁
 *
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

 */

public class Lock_8 {
    public static void main(String[] args) throws Exception {

        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();
    }
}

2)synchronized有三种应用方式

JDK源码(notify方法)举例说明

8种锁的案例实际体现在3个地方

作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
作用于代码块,对括号里配置的对象加锁。
作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;

3)从字节码角度分析synchronized实现

反编译

  • javap -c ***.class文件反编译, -c对代码进行反汇编。

  • 假如你需要更多信息,javap -v ***.class文件反编译 -v -verbose。输出附加信息(包括行号、本地变量表,反汇编等详细信息)

synchronized同步代码块

javap -c ***.class文件反编译

实现使用的是monitorenter和monitorexit指令

一定是一个enter两个exit吗?

synchronized普通同步方法

javap -v ***.class文件反编译

调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。 如果设置了,执行线程会将先持有monitor然后再执行方法, 最后在方法完成(无论是正常完成还是非正常完成)时释放 monitor

synchronized静态同步方法

javap -v ***.class文件反编译

ACC_STATIC, ACC_SYNCHRONIZED访问标志区分该方法是否静态同步方法

3.公平锁和非公平锁

package com.atguigu.juc.senior.test;

import java.util.concurrent.locks.ReentrantLock;

class Ticket
{
    private int number = 30;
    ReentrantLock lock = new ReentrantLock();

    public void sale()
    {
        lock.lock();
        try
        {
            if(number > 0)
            {
                System.out.println(Thread.currentThread().getName()+"卖出第:\t"+(number--)+"\t 还剩下:"+number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}


/**
 * @auther zzyy
 * @create 2020-05-14 17:26
 */
public class SaleTicketDemo
{
    public static void main(String[] args)
    {
        Ticket ticket = new Ticket();

        new Thread(() -> { for (int i = 0; i <35; i++)  ticket.sale(); },"a").start();
        new Thread(() -> { for (int i = 0; i <35; i++)  ticket.sale(); },"b").start();
        new Thread(() -> { for (int i = 0; i <35; i++)  ticket.sale(); },"c").start();
    }
}

何为公平锁/非公平锁?

公平锁:是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人在队尾排着,这是公平的Lock lock = new ReentrantLock(true);true表示公平锁,先来先得

非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转或者饥饿的状态(某个线程一直得不到锁)
Lock lock = new ReentrantLock(false);false表示非公平锁,后来的也可能先获得锁Lock lock = new ReentrantLock();默认非公平锁

为什么会有公平锁/非公平锁的设计为什么默认非公平?

1恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间。

2使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。

使⽤公平锁会有什么问题

公平锁保证了排队的公平性,非公平锁霸气的忽视这个规则,所以就有可能导致排队的长时间在排队,也没有机会获取到锁,这就是传说中的 “锁饥饿”

什么时候用公平?什么时候用非公平?

如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;
否则那就用公平锁,大家公平使用。

4.可重入锁

定义

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。

如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

种类

隐式锁(即synchronized关键字使用的锁)默认是可重入锁。

Synchronized的重入的实现机理

每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。

当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。

在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。

当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。

显式锁(即Lock)也有ReentrantLock这样的可重入锁。

5.死锁及排查

定义

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。

产生死锁主要原因

系统资源不足
进程运行推进的顺序不合适
资源分配不当

package com.atguigu.juc.senior.prepare;

import java.util.concurrent.TimeUnit;

/**
 * @auther zzyy
 * @create 2020-05-14 10:56
 */
public class DeadLockDemo
{
    public static void main(String[] args)
    {
        final Object objectLockA = new Object();
        final Object objectLockB = new Object();

        new Thread(() -> {
            synchronized (objectLockA)
            {
                System.out.println(Thread.currentThread().getName()+"\t"+"自己持有A,希望获得B");
                //暂停几秒钟线程
                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                synchronized (objectLockB)
                {
                    System.out.println(Thread.currentThread().getName()+"\t"+"A-------已经获得B");
                }
            }
        },"A").start();

        new Thread(() -> {
            synchronized (objectLockB)
            {
                System.out.println(Thread.currentThread().getName()+"\t"+"自己持有B,希望获得A");
                //暂停几秒钟线程
                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                synchronized (objectLockA)
                {
                    System.out.println(Thread.currentThread().getName()+"\t"+"B-------已经获得A");
                }
            }
        },"B").start();

    }
}
 
 

如何排查死锁

  • 纯命令
    • jps -l
    • jstack 进程编号
  • 图形化
    • jconsole

6.写锁、读锁

后面单独一章

7.自旋锁

后面单独一章

8.锁升级

后面单独一章

9.小总结

指针指向monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个monitor与之关联,当一个montor被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)

看了上面笔记,想到一个问题,假设让你自己实现一个锁机制应该从哪几反面入手?

1.公平还是非公平

2.可重入性

3.是否可以拆分成读锁和写锁以提高性能

标签:JUC,synchronized,高级,线程,公平,------,new,多线程,public
From: https://www.cnblogs.com/hanxuefeng/p/16947503.html

相关文章

  • 最新 2022 年 Kubernetes 面试题高级面试题及附答案解析
    题1:KubernetesService都有哪些类型?通过创建Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上。其主要类型......
  • Python笔记-多进程多线程
    日常运维中,经常需要并发来提升工作效率。Python提供了多线程和多进程两种方式。importtimeimportthreadingimportmultiprocessingdefprint_fun(num):print(time.str......
  • JUC面试点汇总
    JUC面试点汇总我们会在这里介绍我所涉及到的JUC相关的面试点内容,本篇内容持续更新我们会介绍下述JUC的相关面试点:线程状态线程池Wait和SleepSynchronized和LockVol......
  • 逼格高又实用的 Linux 高级命令,开发运维都要懂
    前言在运维的坑里摸爬滚打好几年了,我还记得我刚开始的时候,我只会使用一些简单的命令,写脚本的时候,也是要多简单有多简单,所以有时候写出来的脚本又长又臭。像一些高级点的......
  • mysql 高级
    第1篇高级架构篇第1章Linux环境下MySQL的安装与使用1安装并启动好两台虚拟机掌握克隆虚拟机的操作:mac地址主机名ip地址UUID5字符集的相关操作5.2......
  • 多线程
    静态代理各种内部类yieldjoin后是继续执行不是重新开始sleep的性质sleep不会释放锁?wait会每个线程都有自己的工作内存内存都是各自的互不影响是拷贝过去的......
  • MySQL高级SQL语句
    MySQL高级(进阶)SQL语句一、实例准备--制表1.表1mysql-uroot-pabc123usetest;createtablelocation(regionchar(20),store_namechar(20));insertintoloc......
  • <二>强弱指针使用场景之 多线程访问共享对象问题
    代码1#include<iostream>#include<thread>usingnamespacestd;classA{public: A(){cout<<"A()"<<endl;} ~A(){cout<<"~A()"<<endl;} vo......
  • Python高级-元类-笔记
    1.类也是对象在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:>>>classObjectCreator(object):…pass…>>>my_object......
  • Python高级-元类实现ORM-笔记
    1.ORM是什么ORM是python编程语言后端web框架Django的核心思想,“ObjectRelationalMapping”,即对象-关系映射,简称ORM。一个句话理解就是:创建一个实例对象,用创建它的类名......