【Java核心基础知识】05 - 多线程并发(4)

多线程知识点目录

多线程并发(1)- https://www.jianshu.com/p/8fcfcac74033
多线程并发(2)-https://www.jianshu.com/p/a0c5095ad103
多线程并发(3)-https://www.jianshu.com/p/c5c3bbd42c35
多线程并发(4)-https://www.jianshu.com/p/e45807a9853e
多线程并发(5)-https://www.jianshu.com/p/5217588d82ba
多线程并发(6)-https://www.jianshu.com/p/d7c888a9c03c

十一、Java阻塞队列原理

11.1 线程阻塞的两种情况:

  1. 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。


    情况1
  2. 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的为止,线程被自动唤醒。


    情况2

11.2 阻塞队列的主要方法

阻塞队列的主要办法
插入操作
  1. add(E paramE):将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回true,如果当前没有可用空间,则抛出IllegalStateException。如果该元素使NULL,则会抛出NullPointException异常。

  2. offer(E paramE):将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回true,如果当前没有可用空间,则返回false。

  3. put(E paramE) throws InterruptedException:将指定元素插入次队列中,将等待可用的空间(如果有必要),如果队列满了,则线程阻塞等待。

  4. offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。

获取数据操作
  1. poll(time):取走BlockingQueue中排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null。

  2. poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一单有数据可取,则立即返回队列中的数据。否则直到时间超时还没数据可取,返回失败。

  3. take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态,直到BlockingQueue有新的数据被加入。

  4. drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。

11.3 Java中的阻塞队列

    1. ArrayBlockingQueue:由数据结构组成的有界阻塞队列。

这是一个基于数组的实现阻塞队列。它内部使用了一个数组来存储元素,按照先进先出(FIFO)的原则对元素进行排序,并且可以设置最大容量。当队列满时,如果有线程试图向队列中插入元素,线程将会被阻塞直到队列中有空间;当队列空时,如果有线程试图从队列中删除元素,线程将会被阻塞直到队列中有元素。
默认情况下不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素。

    1. LinkedBlockingQueue:由链表结构组成的有界阻塞队列。

这是一个基于链表的实现阻塞队列。它内部使用了一个链表来存储元素,按照先进先出(FIFO)的原则对元素进行排序。与ArrayBlockingQueue不同的是,LinkedBlockingQueue的大小可以动态增长。当队列满时,如果有线程试图向队列中插入元素,线程将会被阻塞直到队列中有空间;当队列空时,如果有线程试图从队列中删除元素,线程将会被阻塞直到队列中有元素。
而 LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

    1. PriorityBlockingQueue:支持优先级排序的无界阻塞队列。

这是一个支持优先级排序的阻塞队列,默认情况下元素采取自然顺序升序排列。它内部使用了一个优先级堆来存储元素,使得高优先级的元素总是先于低优先级的元素出队,需要注意的是不能保证同优先级元素的顺序。

    1. DelayQueue:使用优先级队列实现的无界阻塞队列。

这是一个使用优先级队列实现的阻塞队列。它内部使用了一个优先级堆来存储元素,但与PriorityBlockingQueue不同的是,DelayQueue中的元素只有当其指定的延迟时间到了,才能从队列中删除。

    1. SynchronizedQueue:不存储元素的阻塞队列。

这是一个不存储元素的阻塞队列。它的所有操作都是对另一个阻塞队列的操作进行同步,也就是说,它本身并不存储任何元素。每一个 put 操作必须等待一个 take 操作,否则不能继续添加元素。
SynchronousQueue 的 吞 吐 量 高 于 LinkedBlockingQueue 和 ArrayBlockingQueue。

    1. LinkedTransferQueue:由链表结构组成的无界阻塞队列。

这是一个基于链表的无界阻塞队列。它内部使用了一个链表来存储元素。与LinkedBlockingQueue不同的是,LinkedTransferQueue的大小可以动态增长且支持多生产者多消费者模式。

    1. LinkedBlockingDeque:由链表结构组成的双向阻塞队列。

这是一个基于链表的双向阻塞队列。它内部使用了一个链表来存储元素,支持在两端插入和删除元素。
双向队列指的你可以从队列的两端插入和移出元素。双端队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比其他的阻塞队列,LinkedBlockingDeque多了addFirst,addLast,offerFirst,offerLast,peekFirst,peekLast等方法

十二、volatile关键字的作用

Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。volatile变量具备两种特性:①变量可见性;②禁止重排。volatile变量不会被缓存在寄存器或者其他处理器不可见的地方,因此在读取volatile类型的变量是总是会返回最新写入的值。

  • ① 变量可见性
    其一是保证该变量对所有线程可见,这里的可见性指的是当一个线程修改了变量的值,那么新的值对于其他线程时可以立即获取的。
  • ② 禁止重排
    volatile禁止了指令重排。

比Synchronized更轻量级的同步锁

在访问volatile变量时不会执行加锁操作,因此也就不会执行线程阻塞,因此,volatile变量是一种比Synchronized关键字更轻量级的同步机制。volatile适合这种场景:一个变量被多个线程共享,现成直接给这个变量赋值。


不同类型变量读取

当对非volatile变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的CPU Cache中。而声明变量是volatile的,JVM保证了每次读变量都从内存中读,跳过CPU Cache这一步。

使用场景

volatile变量的单次读/写操作可以保证原子性,如long和double类型变量,但是,不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作。在某些场景下可以代替Synchronized。但是,volatile不能完全取代Synchronized,只有在一些特殊的场景下才能适用。总的来说,必须同时满足下面两个条件才能保证在并发环境的线程安全:

  1. 对变量的写操作不依赖于当前值(i++依赖当前值,不能保证线程安全),或者说是单纯的变量赋值(boolean flag=true)
  2. 该变量没有包含在具有其他变量的不变式中,也就是说,不同的volatile变量之间,不能互相依赖。只有在状态真正独立于程序内其他内容时,才能使用volatile。

十三、如何在两个线程之间共享数据

Java里面进行多线程通讯的主要方式就是共享内存的方式,共享内存主要的关注点有:①可见性;②有序性;③原子性。
Java内存模型(JMM)解决了可见性和有序性的问题,而锁解决了原子性的问题,理想情况下我们希望坐到“同步”和“互斥”。有以下常规实现方法:

13.1 将数据抽象成一个类,并将数据的操作作为这个类的方法

将数据抽象成一个类,并将对这个数据的操作作为这个类的方法,这么设计可以做到同步,只要在方法上加上“Synchronized”

public class MyThread {
    public static void main(String[] args) throws InterruptedException {
        MyData myData = new MyData();
        Runnable add = new AddRunnable(myData);
        Runnable dec = new DecRunnable(myData);
        for (int i = 0; i < 2; i++) {
            new Thread(add).start();
            new Thread(dec).start();
        }
    }
}

class MyData {
    private int j = 0;

    public synchronized void add() {
        j++;
        System.out.println("[add()]线程" + Thread.currentThread().getName() + "的j的值为:" + j);
    }

    public synchronized void dec() {
        j--;
        System.out.println("[dec()]线程" + Thread.currentThread().getName() + "的j的值为:" + j);
    }

    public int getData() {
        return j;
    }
}

class AddRunnable implements Runnable {
    private MyData myData;

    public AddRunnable(MyData data) {
        this.myData = data;
    }

    public void run() {
        myData.add();
    }
}

class DecRunnable implements Runnable {
    private MyData myData;

    public DecRunnable(MyData data) {
        this.myData = data;
    }

    public void run() {
        myData.dec();
    }
}

13.2 Runnable对象作为一个类的内部类

将Runnable对象作为一个类的内部类,共享数据作为这个类的成员变量,每个线程对共享数据的操作方法也封装在外部类,以便实现对数据的各个操作的同步和互斥,作为内部类的各个Runnable对象调用外部类的这些方法。

public class MyThread {
    public static void main(String[] args) throws InterruptedException {
        final MyData myData = new MyData();
        for (int i = 0; i < 2; i++) {
            new Thread(myData::add).start();
            new Thread(myData::dec).start();
        }
    }
}


class MyData {
    private int j = 0;

    public synchronized void add() {
        j++;
        System.out.println("[add()]线程" + Thread.currentThread().getName() + "的j的值为:" + j);
    }

    public synchronized void dec() {
        j--;
        System.out.println("[dec()]线程" + Thread.currentThread().getName() + "的j的值为:" + j);
    }

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

推荐阅读更多精彩内容