当一个线程在操作资源的时候,会悲观的任务有其他的线程会来抢占该资源,因此会在操作资源前进行加锁,避免其他线程抢占。
Synchronized关键字和Lock实现类就是悲观锁。
显示的锁定资源后再对资源进行操作。
使用场景:
本质:
加锁去操作同步资源。
当一个线程去操作资源的时候,会乐观的任务其他线程不会来抢占资源,因此不会加锁。
java中通过无锁编程来实现,只是在对数据进行修改的时候,判断其他线程是否对该数据进行修改过
原子操作类那些底层的是CAS(Compare And swap)算法,也就是乐观锁。
判断规则:
使用场景:
乐观锁适合读操作多的场景,不加锁读操作性能大幅提升
本质:
无锁去操作同步资源。
乐观锁:Synchronized和Lock的实现类
悲观锁:原子操作的类
高并发时,同步调用时需要考虑加锁性能损耗。能用无锁数据结构就用无锁数据结构。能用块锁,就不要锁方法体。能用对象锁,就不要用类锁。
(尽可能让锁的代码块尽可能小,避免锁造成不必要的性能开销)
作用于实例方法:当前实例加锁,进入实例前要获取当前实例的锁对象。
作用于代码块:对括号里的对象进行加锁。
作用于静态方法(类方法):对当前类加锁,进去同步代码前要获得当前对象的锁。
类中Synchronized修饰非静态方法(对象锁)
类中Synchronized修饰静态方法(类锁)
类中无Syncronize修饰的方法(和锁无关)
线程执行该方法不需要获得锁,直接执行就行了。
class Phone //资源类
{
public static synchronized void sendEmail()
{
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----sendEmail");
}
public static synchronized void sendSMS()
{
System.out.println("-----sendSMS");
}
public void hello()
{
System.out.println("-------hello");
}
}
/**
* 题目:谈谈你对多线程锁的理解,8锁案例说明
* 口诀:线程 操作 资源类
* 8锁案例说明:
* 1 标准访问有ab两个线程,请问先打印邮件还是短信
* 2 sendEmail方法中加入暂停3秒钟,请问先打印邮件还是短信
* 3 添加一个普通的hello方法,请问先打印邮件还是hello
* 4 有两部手机,请问先打印邮件还是短信
* 5 有两个静态同步方法,有1部手机,请问先打印邮件还是短信
* 6 有两个静态同步方法,有2部手机,请问先打印邮件还是短信
* 7 有1个静态同步方法,有1个普通同步方法,有1部手机,请问先打印邮件还是短信
* 8 有1个静态同步方法,有1个普通同步方法,有2部手机,请问先打印邮件还是短信
*
* 笔记总结:
* 1-2(对象锁)
* * * 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
* * * 其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一的一个线程去访问这些synchronized方法
* * * 锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
* 3-4
* * 加个普通方法后发现和同步锁无关
* * 换成两个对象后,不是同一把锁了,情况立刻变化。
*
* 5-6(类锁) 都换成静态同步方法后,情况又变化
* 三种 synchronized 锁的内容有一些差别:
* 对于普通同步方法,锁的是当前实例对象,通常指this,具体的一部部手机,所有的普通同步方法用的都是同一把锁——>实例对象本身,
* 对于静态同步方法,锁的是当前类的Class对象,如Phone.class唯一的一个模板
* 对于同步方法块,锁的是 synchronized 括号内的对象
*
* * 7-8
* * 当一个线程试图访问同步代码时它首先必须得到锁,正常退出或抛出异常时必须释放锁。
* * *
* * * 所有的普通同步方法用的都是同一把锁——实例对象本身,就是new出来的具体实例对象本身,本类this
* * * 也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁。
* * *
* * * 所有的静态同步方法用的也是同一把锁——类对象本身,就是我们说过的唯一模板Class
* * * 具体实例对象this和唯一模板Class,这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞态条件的
* * * 但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。
*/
public class Lock8Demo
{
public static void main(String[] args)//一切程序的入口
{
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone.sendEmail();
},"a").start();
//暂停毫秒,保证a线程先启动
try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
phone.sendSMS();
// phone.hello();
// phone2.sendSMS();
},"b").start();
}
}
javap -c *.java// -c对代码进行反汇编。 -v (verbose)输出附加信息行号,本地变量表,反汇编
调用指令时,先检查ACC_SYNCHRONIZED(Access)标志是否被设置了,如果该方法有这个标志,代表是同步方法,访问的时候要获取锁对象。
方法完成时(无论是否正常介数)释放锁。
调用指令时,ACC_STATIC,和ACC_SYNCHRONIZED标志。第一个表示是否静态方法,第二个表示是否同步方法。
为什么任何一个对象都可以成为锁?
Java虚拟机支持方法级
什么是管程?
管程(Monitor):可以看做是一个功能模块,他将共享变量和对共享变量的操作封装起来。进程可以调用管程实现进程间的并发控制。
同步指令实现?
Java虚拟机支持方法级的同步和方法内部指令序列的同步,这两种同步结构都是由管程(Monitor或者称为锁)来实现的。
每个对象都关联一个ObjectMonitor锁对象。他有一些属性来保证该资源的同步安全。
ower: 持有该锁的线程
waitset:存放处于wait状态的线程队列
entrylist:存放等待锁的线程队列
recursions(递归):锁的重入次数
count: 记录该线程获取锁的次数。
多个线程按照线程请求锁的先后顺序获取锁。默认都是非公平锁,公平锁需要设置。
Lock lock = new ReentrantLock(true);/l/true表示公平锁,先来先得
执行流程:
获取锁的时候,会将线程自己添加到等待队列中并休眠。当线程使用完锁之后,会去唤醒等待队列首部的线程。线程的休眠和恢复需要从用户态转换为内核态,线程切换是比较慢的,所以公平锁的执行较慢。
每个线程获取到锁的顺序是随机的,并不会按照先来先得的顺序。所有的线程会竞争获取锁。
执行流程:
当线程申请锁时,会通过CAS尝试获取锁。如果获取成功,就持有锁对象。如果获取失败,就进入等待队列。好处是不用遵循先到先得的原则,避免了线程的休眠和恢复过程,执行更快。
默认是非公平锁。能够让程序执行更快(追求效率)。
非公平锁可能造成线程饿死的情况。
可重入锁又叫递归锁。一个线程在外部方法中获取到锁的时候。在进入内部方法需要获取锁的时候,线程会自动获取到该锁。而不会阻塞。
线程在外部获取锁之后,内部自动获取到锁。
实现原理
每个锁对象ObjectMonitor都有一个count计数器和ower持有该锁对象的线程。
当执行monitorenter的时候:会看count计数器是否为0,如果为0说明该锁对象没有被其他线程占有,将count计数器+1,将ower设置为当前的线程。如果不为0,该线程需要等待。
当执行monitorexit的时候:会将count计数器减一,count为0代表可以释放。将ower清空。
lock.lock();//加锁
lock.unlock();//解锁
加锁和释放锁的次数要一样,不然会导致该线程一直持有锁。其他线程无法获取锁。
一个线程持有某个锁对象,有需要申请其他的锁对象。其他锁对象被另一个线程占有。在无外力干扰的情况下,一直处于僵持状态。
举例: A线程持有obj1锁对象,申请obj2锁对象。B线程持有obj2锁对象,申请obj1锁对象。A,B线程均被阻塞住,处于僵持状态。
final Object obj1 = new Object();
final Object obj2 = new Object();
new Thread(() -> {
synchronized (obj1) {
System.out.println(Thread.currentThread().getName() + ":" + "拿到了obj1锁对象");
System.out.println("等待obj2锁对象...");
synchronized (obj2){
System.out.println(Thread.currentThread().getName() + ":" + "拿到了obj2锁对象");
}
}
}, "t1").start();
new Thread(() -> {
synchronized (obj2){
System.out.println(Thread.currentThread().getName() + ":" + "拿到了obj2锁对象");
System.out.println("等待obj1锁对象...");
synchronized (obj1){
System.out.println(Thread.currentThread().getName() + ":" + "拿到了obj1锁对象");
}
}
}, "t1").start();
jps查看死锁线程编号
jps -l
jstack 13992