Node.js redis client用什么? 需要使用redis连接池么?

底层实现

synchronized底层实现

详细参考:杨晓峰极客时间上的课程《Java核心技术面试精讲》:第16讲 | synchronized底层如何实现?什么是锁的升级、降级

  • synchronized 代码块是由一对儿 monitorenter/monitorexit 指令实现的,Monitor 对象是同步的基本实现单元。

  • 发展历程:

    • JDK6之前,Monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作。(@所以说效率低下嘛)
    • 现代的(Oracle)JDK 中,JVM 对此进行了大刀阔斧地改进,提供了三种不同的 Monitor 实现,也就是常说的三种不同的锁:偏斜锁(Biased Locking)、轻量级锁和重量级锁大大改进了其性能。
  • 偏斜锁:JVM 会利用 CAS 操作(compare and swap),在对象头上的 Mark Word 部分设置线程 ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁

  • 轻量级锁:

    • 如果有另外的线程试图锁定某个已经被偏斜过的对象,JVM 就需要撤销(revoke)偏斜锁,并切换到轻量级锁实现。轻量级锁依赖 CAS 操作 Mark Word 来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。
    • 因为重量级锁性能差,所以轻量级锁又衍生出了一种锁:自旋锁,其实现就是自循环若干次,通过CAS操作MARK WROD试图获取锁

其他锁模型

详细参考:王宝令极客时间上的课程《Java并发编程实战》- 08 | 管程:并发编程的万能钥匙

  • 管程模型:
    • Hasen模型:要求 notify() 放在代码的最后,这样 T2 通知完 T1 后,T2 就结束了,然后 T1 再执行,这样就能保证同一时刻只有一个线程执行。
    • Hoare模型:T2 通知完 T1 后,T2 阻塞,T1 马上执行;等 T1 执行完,再唤醒 T2,也能保证同一时刻只有一个线程执行。但是相比 Hasen 模型,T2 多了一次阻塞唤醒操作。
    • MESA模型(JAVA参考实现):MESA 管程里面,T2 通知完 T1 后,T2 还是会接着执行,T1 并不立即执行,仅仅是从条件变量的等待队列进到入口等待队列里面。这样做的好处是 notify() 不用放到代码的最后,T2 也没有多余的阻塞唤醒操作。但是也有个副作用,就是当 T1 再次执行的时候,可能曾经满足的条件,现在已经不满足了,所以需要以循环方式检验条件变量。—也就是产生假唤醒

锁变化

升级/膨胀

其实就是偏斜锁=》轻量级锁=》重量级锁的过程,见第一节 #底层实现

锁降级

锁降级确实是会发生的,当 JVM 进入安全点(SafePoint)的时候,会检查是否有闲置的 Monitor,然后试图进行降级。

synchronized锁的范围

  • 范围
    • 代码块
    • 方法
    • 对象
  • 对象锁和类
    • 类锁和对象锁是分开的,(现在只是个概念,用来区分对象锁的,是指静态方法的锁),程序中获得类锁的同时也可以获得对象锁。
    • 同一个类锁和同一个类锁是互斥的,同一个对象锁和同一个对象锁互斥。 非静态方法不受类锁的影响
    • 对象锁与实例对象相关, 不同的对象的对象锁不一样,可以同时获取两个不同对象的对象锁
package com.keven;

//类锁和对象锁的测试代码
public class SyncTest {

    public static void main(String[] args) throws Exception {
        runObjectLockTest();
        System.out.println("finished runObjectLockTest");
        runClassLockTest();
        System.out.println("finished runClassLockTest");
        runClassObjectLockTest();
        System.out.println("finished runClassObjectLockTest");
        Thread.sleep(10000);
    }

    //测试对象锁和类锁是否能够同时获取, 可以看到两个线程打印数据不受影响,说明不是同一个锁
    private static void runClassObjectLockTest() {
        new Thread(SyncTest::testClassLock1, "thread1").start();
        new Thread(() -> {
            new SyncTest().testObjectLock();
        }, "thread2").start();
    }

    //测试类锁,显示thread1打印完成,后面thread2才开始打印,从侧面验证获取到的是同一个锁
    private static void runClassLockTest() {
        new Thread(SyncTest::testClassLock1, "thread1").start();
        new Thread(SyncTest::testClassLock2, "thread2").start();

    }

    //测试对象锁, 可以看到两个线程打印数据不受影响, 且this对象的hash值不一样
    private static void runObjectLockTest() {
        final SyncTest syncTest = new SyncTest();
        final Thread thread1 = new Thread(() -> {
            syncTest.testObjectLock();
        }, "thread1");

        final Thread thread2 = new Thread(() -> {
            new SyncTest().testObjectLock();
        }, "thread2");
        thread1.start();
        thread2.start();
    }

    private static synchronized void testClassLock1() {
        int i = 100;
        int count = 0;
        while ((i-- > 0) && (count++ < 10)) {
            System.out.println("method testClassLock1--" + Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(50);
            } catch (InterruptedException ie) {
            }
        }
    }

    private static synchronized void testClassLock2() {
        int i = 100;
        int count = 0;
        while ((i-- > 0) && (count++ < 10)) {
            System.out.println("method testClassLock2--" + Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(50);
            } catch (InterruptedException ie) {
            }
        }
    }

    private void testObjectLock() {
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + " : " + this);
            int i = 100;
            int count = 0;
            while ((i-- > 0) && (count++ < 5)) {
                System.out.println("method testObjectLock--" + Thread.currentThread().getName() + " : " + i);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException ie) {
                }
            }
        }
    }

}

synchronized和ReentrantLock有什么区别?

详细可参考:杨晓峰极客时间上的课程《Java核心技术面试精讲》:第15讲 | synchronized和ReentrantLock有什么区别呢?

  • synchronized 和 ReentrantLock 的性能不能一概而论:
    • 早起版本的synchronize在很多场景下性能相差较大
    • 在后续版本进行了较多的改进,在低竞争场景中表现可能由于ReentrantLock
  • 这里所谓的公平性是指在竞争场景中,当公平性为真时,会倾向于将锁赋予等待时间最久的线程。公平性是减少线程“饥饿”(个别线程长期等待锁,但始终无法获取)情况发生的一个办法。
  • ReentrantLock与Synchronized的区别:
    • ReentrantLock
      • 更加的灵活,但必须手动释放锁
        • 可通过条件控制同步
        • 可被中断,并抛出中断异常,释放锁
        • 可选择获取锁的超时时间,尝试获取锁
        • 可选择是否为公平锁
      • 只适合代码块的锁
    • synchronized
      • 无需释放锁,自动处理
      • 可修饰方法,类,代码块
      • 非公平锁,如果阻塞则必须等待cpu调度
  • ReentrantLock与Synchronized的共通点:都是独占锁或者说是排它锁

关联关键词

  • 在上面的代码中,我用的是 notifyAll() 来实现通知机制,为什么不使用 notify() 呢?
    • 这二者是有区别的,notify() 是会随机地通知等待队列中的一个线程,而 notifyAll() 会通知等待队列中的所有线程。
    • 从感觉上来讲,应该是 notify() 更好一些,因为即便通知所有线程,也只有一个线程能够进入临界区。但那所谓的感觉往往都蕴藏着风险,实际上使用 notify() 也很有风险,它的风险在于可能导致某些线程永远不会被通知到。@随机的弊病不就是存在永远不被轮到的弊病么?这跟非公平锁的弊病是一个意思
  • wait与sleep区别在于:
    • wait会释放所有锁而sleep不会释放锁资源.
    • wait只能在同步方法和同步块中使用,而sleep任何地方都可以
    • wait无需捕捉异常,而sleep需要
    • sleep是Thread的方法,而wait是Object类的方法;
    • sleep方法调用的时候必须指定时间
      两者相同点:都会让渡CPU执行时间,等待再次调度!。补充关于二者的区别还可以看知乎的这篇帖子
  • wait()方法与sleep()方法的不同之处在于,wait()方法会释放对象的“锁标志”。当调用某一对象的wait()方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了notify()方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的notifyAll()方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。
  • sleep()方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放“锁标志”,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据

常见面试题

  • synchronized和ReentrantLock的区别 @见笔记
  • 锁什么时候升级/降级?@见笔记
  • 类锁和对象锁的区别? @见笔记
  • 为什么JDK8中ConcurrentHashMap的锁实现要用CAS+synchronized来取代Segment+ReentrantLock呢?
  • 为什么wait必须是在同步块中的呢?@重看了一遍王宝令的课程,发现这是MESA管程模型的设计范式,硬要解释的话可以是这样:
    • wait是跟notify, notifyAll配对的, 是和synchronized关键字一起使用的
    • wait的工作原理就是wait的时候,会进入同步块(synchronized)所对应的条件等待队列,在其他地方使用这个关键字是不可进入的
    • 或者说wait所对应的管程的入口在synchronied处
  • wait与sleep区别是什么? @见上面的笔记
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容