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++实现的)
标签:JUC,synchronized,高级,线程,公平,------,new,多线程,public From: https://www.cnblogs.com/hanxuefeng/p/16947503.html看了上面笔记,想到一个问题,假设让你自己实现一个锁机制应该从哪几反面入手?
1.公平还是非公平
2.可重入性
3.是否可以拆分成读锁和写锁以提高性能