【Java并发编程学习 5】常用的并发包与并发容器学习

并发包

1 CountDownLatch (计数器)

1.1 什么是CountDownLatch

CountDownLatch 类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他3个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

1.2 CountDownLatch 的原理

CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

1.3 CountDownLatch代码示例

package com.lijie;

        import java.util.concurrent.CountDownLatch;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        //定义CountDownLatch记数器
        CountDownLatch countDownLatch = new CountDownLatch(3);

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName() + ",子线程开始执行");
                countDownLatch.countDown();//计数器值每次减去1
                System.out.println(Thread.currentThread().getName() + ",子线程结束执行");
            }
        }, "线程1").start();

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName() + ",子线程开始执行");
                countDownLatch.countDown();//计数器值每次减去1
                System.out.println(Thread.currentThread().getName() + ",子线程结束执行");
            }
        }, "线程2").start();

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName() + ",子线程开始执行");
                countDownLatch.countDown();//计数器值每次减去1
                System.out.println(Thread.currentThread().getName() + ",子线程结束执行");
            }
        }, "线程3").start();

        countDownLatch.await();// 減去为0时,恢复任务继续执行
        System.out.println("三个子线程执行完毕");
        System.out.println("主线程继续执行");
        for (int i = 0; i < 10; i++) {
            System.out.println("main,i:" + i);
        }
    }
}

2 CyclicBarrier (回环栅栏)

CyclicBarrier它的作用就是会让所有线程都等待完成后才会继续下一步行动。

CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。

CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。

CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。

2.1 CyclicBarrier代码示例

package com.lijie;

import java.util.concurrent.CyclicBarrier;

class Writer extends Thread {
	//定义屏障器
    private CyclicBarrier cyclicBarrier;

    public Writer(CyclicBarrier cyclicBarrier) {
        this.cyclicBarrier = cyclicBarrier;
    }

    public void run() {
        System.out.println("线程" + Thread.currentThread().getName() + ",正在写入数据");
        try {
            //此方法表示线程需要再次同步
            cyclicBarrier.await();
        } catch (Exception e) {

        }
        System.out.println("线程" + Thread.currentThread().getName() + ",写入数据成功");
        try {
            //此方法表示线程需要再次同步
            cyclicBarrier.await();
        } catch (Exception e) {

        }
        System.out.println("所有线程执行完毕..........");
    }
}

public class Test1 {
    public static void main(String[] args) {
        //定义为5,表示这五个线程需要同步
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
        for (int i = 0; i < 5; i++) {
            Writer writer = new Writer(cyclicBarrier);
            writer.start();
        }
    }
}

3 Semaphore (信号量)

Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。就这一点而言,单纯的synchronized 关键字是实现不了的。

Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。它的用法如下:

availablePermits函数用来获取当前可用的资源数量
wc.acquire(); //申请资源
wc.release();// 释放资源

3.1 Semaphore代码示例

10 个人访问, 3 个并发数访问

package com.lijie;

import java.util.Random;
import java.util.concurrent.Semaphore;

class ThreadDemo extends Thread {
	private String name;
	//定义线程并发数量属性(计数信号量)
	private Semaphore hotel;

	public ThreadDemo(String name, Semaphore hotel) {
		this.name = name;
		this.hotel = hotel;
	}

	public void run() {
		// 剩下的资源
		int availablePermits = hotel.availablePermits();
		if (availablePermits > 0) {
			System.out.println(name + "饭店有位置了,我要开动了");
		} else {
			System.out.println(name + "怎么没有位置了,饿死了,等会吧");
		}
		try {
			// 申请资源
			hotel.acquire();
		} catch (InterruptedException e) {

		}
		System.out.println(name + "终于上吃上饭了,舒服" + ",剩下饭店位置:" + hotel.availablePermits());
		try {
			Thread.sleep(new Random().nextInt(1000));
		} catch (Exception e) {

		}
		System.out.println(name + "我吃完啦!,走人");
		// 释放资源
		hotel.release();
	}
}

public class TestSemaphore {

	public static void main(String[] args) {
		//定义线程并发数量
		Semaphore semaphore = new Semaphore(3);
		for (int i = 1; i <= 10; i++) {
			ThreadDemo threadDemo = new ThreadDemo("第" + i + "个人说:", semaphore);
			threadDemo.start();
		}
	}
}

并发容器

容器用的最多的就是ArrayList,LinkedList ,HashMap等等

在多线程开发中就不能乱用容器,如果使用了未加锁(非同步)的的集合,你的数据就会非常的混乱。由此在多线程开发中需要使用的容器必须是加锁(同步)的容器。

常用的同步容器介绍

1 Vector

  1. ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。ArrayList的缺点是每个元素之间不能有间隔。

  2. Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,访问它比访问ArrayList慢很多

  3. ArrayList添加方法源码
    在这里插入图片描述

  4. Vector添加源码(加锁了synchronized关键字)
    在这里插入图片描述

2 HasTable

  1. HashMap是非线程安全的,而HasTable的内部方法都被synchronized修饰了,所以是线程安全的。

  2. 因为线程安全的问题,HashMap要比HashTable效率高一点,HashTable除了在多线程开发中基本被淘汰。

  3. HashMap允许有null值的存在(key只能一个为null,value都可为空),而在HashTable中是都不能为空的

  4. HashMap添加方法的源码
    在这里插入图片描述

  5. HashTable添加方法的源码
    在这里插入图片描述

3 Collections.synchronized *

此接口方法是干什么的呢,他完完全全的可以把List、Map、Set接口底下的集合变成线程安全的集合

Collections.synchronized * :原理是什么,我猜的话是代理模式:Java代理模式理解

在这里插入图片描述

4 ConcurrentHashMap

  1. 我们不是有HashTable来实现线程安全Key/Value储存值了吗,为什么还要他

  2. ConcurrentHashMap是Java5中支持高并发、高吞吐量的线程安全HashMap实现。它由Segment数组结构和HashEntry数组结构组成。Segment数组在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键-值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构;一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素;每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

  3. 总结:
    1、底层采用分段的数组+链表实现,线程安全
    2、通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。
    3、并且读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。
    4、Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
    5、扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
    在这里插入图片描述

发布了68 篇原创文章 · 获赞 56 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_43122090/article/details/104966532