钢铁网站模板,美橙互联网站管理后台,东莞网络企业推广,协策网络乐观锁和悲观锁
悲观锁
当一个线程在操作资源的时候#xff0c;会悲观的任务有其他的线程会来抢占该资源#xff0c;因此会在操作资源前进行加锁#xff0c;避免其他线程抢占。 Synchronized关键字和Lock实现类就是悲观锁。 显示的锁定资源后再对资源进行操作。 使用场景会悲观的任务有其他的线程会来抢占该资源因此会在操作资源前进行加锁避免其他线程抢占。 Synchronized关键字和Lock实现类就是悲观锁。 显示的锁定资源后再对资源进行操作。 使用场景
适合写操作多的场景。先加锁能够保证写操作时数据正确
本质 加锁去操作同步资源。
乐观锁
当一个线程去操作资源的时候会乐观的任务其他线程不会来抢占资源因此不会加锁。 java中通过无锁编程来实现只是在对数据进行修改的时候判断其他线程是否对该数据进行修改过
如果没有修改过该线程直接修改数据。如果修改过该线程则根据不同的实现方式执行不同的操作比如放弃修改重试抢锁等等。
原子操作类那些底层的是CAS(Compare And swap)算法也就是乐观锁。 判断规则
版本号机制Version每修改一次版本号递增当前版本号是最大的可以直接修改。不是最大的意味着别人修改过了我的修改要重新处理最常采用的是CAS算法后面会详细讲这里略
使用场景 乐观锁适合读操作多的场景不加锁读操作性能大幅提升 本质 无锁去操作同步资源。
乐观锁和悲观锁举例
乐观锁Synchronized和Lock的实现类 悲观锁原子操作的类
Synchronized
阿里加锁规范
高并发时同步调用时需要考虑加锁性能损耗。能用无锁数据结构就用无锁数据结构。能用块锁就不要锁方法体。能用对象锁就不要用类锁。 尽可能让锁的代码块尽可能小避免锁造成不必要的性能开销
Synchronized三种作用方式
作用于实例方法当前实例加锁进入实例前要获取当前实例的锁对象。 作用于代码块对括号里的对象进行加锁。 作用于静态方法类方法对当前类加锁进去同步代码前要获得当前对象的锁。
Synchronized作用于非静态方法和静态方法的区别重要
类中Synchronized修饰非静态方法对象锁
加的锁为this对象锁。一个对象只有一把对象锁因此多个线程执行一个对象的非静态同步方法时存在竞争关系。先获得对象锁的线程先执行。不同对象不会有竞争不同对象有不同的对象锁线程如果持有不同对象锁线程间无竞争的关系。
类中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();}
}
字节码角度分析Synchronized
查看反汇编
javap -c *.java// -c对代码进行反汇编。 -v (verbose)输出附加信息行号本地变量表反汇编synchronized同步代码块
实现使用的是monitorenter和monitorexit。monitorenter代表获得锁对象monitorexit代表释放锁对象。通常情况下一个monitorenter对应两个monitorexit正常情况下从第一个monitorexit释放锁。异常情况下从第二个monitorexit释放锁。
synchronized普通同步方法
调用指令时先检查ACC_SYNCHRONIZEDAccess标志是否被设置了如果该方法有这个标志代表是同步方法访问的时候要获取锁对象。 方法完成时无论是否正常介数释放锁。
synchronized静态同步方法
调用指令时ACC_STATIC和ACC_SYNCHRONIZED标志。第一个表示是否静态方法第二个表示是否同步方法。
反编译Synchronized锁是什么
为什么任何一个对象都可以成为锁 Java虚拟机支持方法级 什么是管程 管程Monitor:可以看做是一个功能模块他将共享变量和对共享变量的操作封装起来。进程可以调用管程实现进程间的并发控制。 同步指令实现 Java虚拟机支持方法级的同步和方法内部指令序列的同步这两种同步结构都是由管程Monitor或者称为锁来实现的。
方法级的同步通过读取ACC_SYNCHRONIZED判断是否是同步方法如果是同步方法执行线程要求必须持有管程锁。执行完毕后释放锁。方法内部指令序列的同步同步一段指令序列是通过synchronized方法块来表示。java虚拟机指令集中的monitorenter和monitorexit指令实现的。
Monitor的实现 OjectMonitor
每个对象都关联一个ObjectMonitor锁对象。他有一些属性来保证该资源的同步安全。 ower: 持有该锁的线程 waitset存放处于wait状态的线程队列 entrylist存放等待锁的线程队列 recursions递归锁的重入次数 count: 记录该线程获取锁的次数。
公平锁和非公平锁
公平锁(先来先得)
多个线程按照线程请求锁的先后顺序获取锁。默认都是非公平锁公平锁需要设置。
Lock lock new ReentrantLock(true);/l/true表示公平锁,先来先得执行流程 获取锁的时候会将线程自己添加到等待队列中并休眠。当线程使用完锁之后会去唤醒等待队列首部的线程。线程的休眠和恢复需要从用户态转换为内核态线程切换是比较慢的所以公平锁的执行较慢。
非公平锁随机获得锁默认
每个线程获取到锁的顺序是随机的并不会按照先来先得的顺序。所有的线程会竞争获取锁。 执行流程 当线程申请锁时会通过CAS尝试获取锁。如果获取成功就持有锁对象。如果获取失败就进入等待队列。好处是不用遵循先到先得的原则避免了线程的休眠和恢复过程执行更快。
使用场景
默认是非公平锁。能够让程序执行更快追求效率。 非公平锁可能造成线程饿死的情况。
可重入锁递归锁
定义
可重入锁又叫递归锁。一个线程在外部方法中获取到锁的时候。在进入内部方法需要获取锁的时候线程会自动获取到该锁。而不会阻塞。
种类
隐式锁Synchronized关键字修饰的
线程在外部获取锁之后内部自动获取到锁。 实现原理 每个锁对象ObjectMonitor都有一个count计数器和ower持有该锁对象的线程。 当执行monitorenter的时候会看count计数器是否为0如果为0说明该锁对象没有被其他线程占有将count计数器1将ower设置为当前的线程。如果不为0该线程需要等待。 当执行monitorexit的时候会将count计数器减一count为0代表可以释放。将ower清空。
显式锁Lock实现类 lock.lock();//加锁lock.unlock();//解锁加锁和释放锁的次数要一样不然会导致该线程一直持有锁。其他线程无法获取锁。
死锁
一个线程持有某个锁对象有需要申请其他的锁对象。其他锁对象被另一个线程占有。在无外力干扰的情况下一直处于僵持状态。 举例 A线程持有obj1锁对象申请obj2锁对象。B线程持有obj2锁对象申请obj1锁对象。AB线程均被阻塞住处于僵持状态。
手写一个死锁的例子
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();运行结果
检测死锁
第一种方式命令行jpsjstack
jps查看死锁线程编号
jps -ljstack 查看当前时刻的线程快照
jstack 13992第二种jconsole图形化界面