Java基础之多线程篇(二)

Java中的锁

从不同的角度看,Java中有许多类型的锁,下面是它们的简单介绍。

  • 从是否锁住同步资源来看

  • 1.1乐观锁

乐观锁认为所有拿到共享数据的线程都不会修改数据,只会查看数据,因此在获取共享数据时不会加锁,适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。

Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

  • 1.1.1 CAS

CAS全称 Compare And Swap。在不使用锁的情况下实现多线程之间的变量同步。

涉及到三个操作数:需要读写的内存值 oldValue,进行比较的值 oldValue2,要写入的新值 newValue。

JDK通过CPU的cmpxchg指令,去比较寄存器中的 oldValue2 和 内存中的值 oldValue。如果相等,就把要写入的新值 newValue 存入内存中。如果不相等,就将内存值 oldValue 赋值给寄存器中的值 oldValue2。然后通过Java代码中的while循环再次调用cmpxchg指令进行重试,直到设置成功为止。

  • 1.2 悲观锁

悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改,适合写操作多的场景,先加锁可以保证写操作时数据正确。

  • 从同步资源的竞争程度来看

  • 2.1 偏向锁

当一段同步代码长时间只被同一个线程访问时,说明该块同步代码竞争程度非常低,没必要频繁的加锁解锁消耗系统资源,所以可以让线程来访问同步代码自动获取锁,这便是偏向锁。

只有存在其他线程竞争偏向锁的时候,持有偏向锁的线程才会释放掉偏向锁;偏向锁在JDK1.6之后是自动开启的,可以通过-XX:-UseBiasedLocking来进行操作。

  • 2.2 轻量级锁

当偏向锁被其他线程竞争时,便会升级为轻量级锁;另外,竞争偏向锁未成功时,该线程会进行自旋操作而不是直接阻塞,从而提高系统性能。

  • 2.3 重量级锁

当竞争偏向锁的线程自旋达到一定次数,或者在它自旋的时候,又来了一个竞争锁的线程,这时,轻量级锁会升级为重量级锁;升级成重量级锁之后,会将除了持有锁之外的线程都阻塞(自旋的停止自旋)。

  • 2.4 各量级锁的转换规则

锁升级的顺序是偏向锁-->轻量级锁-->重量级锁,锁只可以升级不可以降级。

  • 从申请锁的顺序来看

  • 3.1 公平锁

公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

例如:你去食堂吃饭,你看到有人排列,你便自觉的去队尾排列,打饭的阿姨在打完一个饭的时候会按照顺序叫下一个同学来打饭。

  • 3.2 非公平锁

非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

例如:你去食堂打饭,虽然已经有人在排队了,但是阿姨刚打完上一个同学的饭,还没来得及叫下一个人,你可以直接去插队打饭,阿姨就不会给队首的同学打饭,而是先给你打饭。

  • 从锁的共享程度来看

  • 4.1 共享锁

共享锁是指该锁可被多个线程所持有。JDK中的ReentrantReadWriteLock的ReadLock是共享锁。

  • 4.2 排他锁

排他锁,是指该锁一次只能被一个线程所持有。JDK中的synchronized和JUC中Lock的实现类就是互斥锁。

  • 从锁是否可以被一个线程重复获取来看

  • 可重入锁

可重入锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

示例代码

/**
 * 重入锁示例代码
 */
public class ReentrantLockDemo {

    public synchronized void getLock1(){
        System.out.println("获取对象锁的method1");
        getLock2();
    }

    public synchronized void getLock2(){
        System.out.println("获取对象锁的method2");
    }

    public static void main(String[] args) {
        new ReentrantLockDemo().getLock1();
    }
}

在执行getLock1方法时,已经获取了对象锁,执行getLock2方法时并未释放锁;而getLock2方法也是需要获取对象锁的,方法却可以正常执行,这便可以说明synchronized是可重入锁。

  • 不可重入锁

不可重入锁则不会让一个线程获取两次,相对可重入锁来说,更容易造成死锁。

volatile关键字

volatile关键字可以保证顺序性以及可见性,无法保证原子性,所以单纯的使用volatile关键字是无法实现多线程安全的,但是如果设计巧妙的话,也可以较为轻量的解决多线程安全问题,典型就是以“双重校验锁”的方式实现单例模式。

/**
 * 双重校验锁 实现单例模式
 */
public class SinglePattern {

    private static volatile SinglePattern singlePattern;

    private SinglePattern(){}

    public static SinglePattern getInstance(){
        if(singlePattern == null){
            synchronized(SinglePattern.class){
                if(singlePattern == null){
                    singlePattern = new SinglePattern();
                }
            }
        }
        return singlePattern;
    }
}

java内存模型

java内存模型(Java Memory Model,JMM)是java虚拟机规范定义的,用来屏蔽掉java程序在各种不同的硬件和操作系统对内存的访问的差异,这样就可以实现java程序在各种不同的平台上都能达到内存访问的一致性,下面简单介绍下与java内存模型相关的happen-before原则。

  • 单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。
  • 锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。
  • volatile的happen-before原则:对一个volatile变量的写操作happen-before对此变量的任意操作
  • happen-before的传递性原则:如果A操作happen-before B操作,B操作happen-before C操作,那么A操作happen-before C 操作
  • 线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其他方法。
  • 线程中断的happen-before原则:对县城interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
  • 线程终结的happen-before原则:线程中的所有操作都happen-before线程的终止检测。
  • 对象创建的happen-before原则:对象的初始化完成先于他的finalize方法的调用。

ThreadLocal

ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。

Java的锁的理念是“时间换空间”,而ThreadLocal的理念是“用空间换时间”,下面写个例子演示下TheadLocal的用法。

public class ThreadLocalDemo implements Runnable{

    //设置一个threadLocal变量存储Integer
    private ThreadLocal<Integer> formatter = new ThreadLocal();

    @Override
    public void run() {
        System.out.println("Thread Name= "+Thread.currentThread().getId()+" 当前取到的值为 "+formatter.get());

        formatter.set(new Integer(new Random().nextInt(100)));

        System.out.println("Thread Name= "+Thread.currentThread().getId()+" 设置后的值为 = "+formatter.get());


    }

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
        Thread thread1 = new Thread(threadLocalDemo);
        Thread thread2 = new Thread(threadLocalDemo);
        Thread thread3 = new Thread(threadLocalDemo);
        Thread thread4 = new Thread(threadLocalDemo);
        Thread thread5 = new Thread(threadLocalDemo);
        Thread thread6 = new Thread(threadLocalDemo);
        thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        thread3.start();
        thread3.join();
        thread4.start();
        thread4.join();
        thread5.start();
        thread5.join();
        thread6.start();
    }
}

完整工程代码链接:https://github.com/youzhihua/Java-training

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容

  • layout: posttitle: 《Java并发编程的艺术》笔记categories: Javaexcerpt...
    xiaogmail阅读 5,815评论 1 19
  • 1.写出synchronized的使用方式 synchronized的三种应用方式 synchronized关键字...
    wuyuan0127阅读 300评论 0 1
  • 第2章 java并发机制的底层实现原理 Java中所使用的并发机制依赖于JVM的实现和CPU的指令。 2.1 vo...
    kennethan阅读 1,419评论 0 2
  • 现在是凌晨12:57分,我刚从10点半睡了会,这会正和我弟换完班。听着我爸睡的正香的小呼噜声,心里安心不少,听...
    简丹追求阅读 488评论 1 0
  • 郭相麟 莫名喜欢 在说不清楚道不明白中 你的名字 就像天上的星星 闪烁在心中 你的脸庞洋溢青春的阳光 你的眼神透明...
    郭相麟阅读 150评论 0 0