Lock、Synchronized锁区别解析( 二 )


1 public class safe1{ 2public static void main(String[] args) { 3new Thread(new QQ(),"张三").start();4new Thread(new QQ(),"李四").start(); 5new Thread(new QQ(),"王五").start(); 67}89 }10 class QQ implements Runnable{11private static int sum=100;12private static boolean n=true;1314public void test() {15synchronized (QQ.class) {16if(sum<0) {17n=false;18return ;19}20try {21Thread.sleep(200);22} catch (InterruptedException e) {23// TODO 自动生成的 catch 块24e.printStackTrace();25}26System.out.println(Thread.currentThread().getName()+sum--);27}28}29public void run() {30while(n) {31test();32}3334}35 36 }【Lock、Synchronized锁区别解析】可以看到这次是在实例方法里 , 如果修饰在方法上锁住的就是当前类对象 , 不同线程必须拥有同一个对象才能实现同步 , 而在这个例子里 synchronized 锁住的是 .class 类信息 , 所以最后还是能实现同步
Lock、Synchronized锁区别解析文章插图
原理因为 synchronized 是关键字 , 没有具体的类实现 , 所以我们只能在指令集上查看 , 先上代码
public void aa(){synchronized (this){int gg = 4;}}public void bb(){int gg = 4;}在使用 jclass Bytecode viewer 工具将编译后的 class 文件转成可视化的指令集后 , 可以看到指令集如下:
aa 方法:
Lock、Synchronized锁区别解析文章插图
bb 方法:
Lock、Synchronized锁区别解析文章插图
可以看到在 aa 方法中多了一些指令 , 其中比较重要的就是 "monitorenter"、"monitorexit" 。 这两个指令对应的着 "解锁" 和 "加锁", 也就是线程 "获取到锁" 以及代码执行完成后的 "释放锁" 。 "monitor" 就对应着文章开头说的监视器 , 因为 synchronized 就是 java 中的 "监视器" 。 指令中有一次 "monitorenter" 代表线程得到 CPU 调度(也叫做线程得到了锁) , 但是指令里却有两次 "monitorexit" , 这是为了防止线程发生异常没有执行 第一次的 "monitorexit" , 而导致其他线程永远无法得到线程 。
Synchronized 锁升级机制在 JDK 早期的版本 , synchronized 锁的效率是非常低的 , 它的效率远低于 lock 锁 , 但是 sychronized 毕竟是 java 的关键词 , 它不应该就此淘汰 。 所以在 JDK1.6 中对它进行优化 , 其实优化内容不仅仅是与 synchronized 有关的 , 还有 "自适应自旋锁"、"锁消除"、"锁粗化" 等 。 这些优化和各种锁以及多线程的其他知识后面会开单独的一篇多线程的专栏来说 。 这里先简单的提一下 , 关于 synchronized 的优化就是它的升级机制 。 synchronized 也因为这个优化效率变得能和 Lock 锁效率不相上下 。
1.6 之前的 synchronized 都是 "重量级锁" , 什么是重量级锁呢?就是一个线程在获取到 CPU 调度后 , 开始执行 synchronized 修饰的代码块 , 这时其他执行到这里的线程必须进行一次 " 上下文切换 "(下面有解释)(其实在进行上下文切换前会先尝试获取锁资源 , 失败才会进行"上下文切换" , 这是非公平锁的特性 , 下面 Lock 部分有讲解 , 这里比较的是 synchronized 效率问题 , 所以忽略一开始就抢夺到锁资源的情况)和 "加锁 "、"解锁" 操作 。 这就是 "重量级锁" , 这样的锁有严重的弊端 。 "上下文切换" 和 "加锁"、 "解锁" 这些动作虽然在单线程下消耗的时间并不算多 , 但是在一些高并发场景 , 例如百万、千万并发的场景 , 那么这些动作消耗的总时间就比较大了;另外一种情况就是某段代码可能发生多个线程抢占执行的情况 , 但是实际并没有发生这种情况 , 都是一个线程执行完后下一个线程才执行到这段代码 , 这样 "加锁"、解锁" 消耗的时间就浪费了 。 那么有什么方法去解决这个问题呢 , 这就是锁升级机制带来的好处 。