1.并发下集合(list,map,set)的线程不安全和解决方法
1.1)方法一:
通过Collections类中的相应的同步方法即可
Set s=Collections.synchronizedSet(new Hashset<…>());
Map m=Collections.synchronizedMap(new HashMap<…>());
List l=Collections.synchronizedList(new ArrayList<…>());
1.2)方法二:
1.2.1) List------>Vector 或者java.util.concurrent.CopyOnWriteArrayList(java并发包下的CopyOnWriteArrayList这个类)
1.2.2)Map---->HashTable或者ConcurrentHashMap
1.2.3)Set------>CopyOnWriteArraySet(详细见:08-java中解决集合set线程安全的方法二.png)
1.3)小结:
list—>Vetor, Collections.synchronizedList(new ArrayList<…>()); java并发包下的CopyOnWriteArrayList这个类
map—>HashTable, Collections.synchronizedMap(new HashMap<…>()); ConcurrentHashMap
set---->Collections.synchronizedSet(new Hashset<…>()); CopyOnWriteArraySet
1.4)Arraylist的线程不安全
package com.rj.bd.threads.thread18;
import java.util.ArrayList;
import java.util.List;
/**
* @desc ArrayList存在线程安全的问题
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
final List<Integer> list = new ArrayList<Integer>(); //使用Final修饰符修饰的对象的特点:该对象的引用地址不能改变(为了更好的演示效果,当然删除也可以)
//final List<Integer> list = new Vector<Integer>();
// 线程A将0-1000添加到list
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 1000 ; i++) {
list.add(i);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
// 线程B将1000-2000添加到列表
new Thread(new Runnable() {
public void run() {
for (int i = 1000; i < 2000 ; i++) {
list.add(i);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
Thread.sleep(1000);
// 打印所有结果
for (int i = 0; i < list.size(); i++) {
System.out.println("第" + (i + 1) + "个元素为:" + list.get(i));
}
}
}
package com.rj.bd.threads.thread18;
import java.util.ArrayList;
import java.util.List;
public class Test02 {
public static void main(String[] args) {
final List<String> list = new ArrayList<String>();//CopyOnWriteArrayList();
for (int i =1; i<=30 ; i++)
{
new Thread(new Runnable()
{
@Override
public void run()
{
list.add("a");
list.add("b");
list.add("c");
list.add("d");
System.out.println(list.toString());
}
}).start();
}
}
}
异常原因: 因为在数据存储过程中有多个线程同时操作list集合,造成并发修改异常
2.并发包
2.1
2.1.1)java中的并发包指的就是java.util.concurrent—>JUC
2.1. 2) 并发包下有一些比较有用的工具类:例如:CountDownLatch,CyclicBarrier
2.1.3)例如可以用CountDownLatch实现计数器?(是什么意思呢)
2.2CountDownLatch概念
CountDownLatch:是java.util.concurrent并发包下的一个工具类,其意思“倒计时门闩”,它允许一个或多个线程一直等待直到其他线程执行完毕才开始执行
翻译成大白话:
多个人等一个信号后继续执行操作:例如5个运动员,等一个发令员的枪响。
一个人等多个人的信号。旅游团等所有人签到完成才开始出发,驾校带队负责人等所有人体检完成才发车回驾校
2.2.1)CountDownLatch的原理和不足(拓展)
1)CountDownLatch是不能够重用的,根据上面的解析,大家也发现,共享锁加锁的操作并不会增加state的值。CountDownLatch中state一旦变成0就没有提供其他方式增长回去了。所以CountDownLatch是一次性消耗品,用完就得换新的。这样设计也是比较正常的,对状态的要求比较严格,例如已经开始比赛了你再告诉裁判说,这次再加5个人,比赛都进行一半了,不会考虑再回去的。随意修改约束值会带来很多逻辑上的问题。
2)CountDownLatch无限等待,countDown没有被调用,那么await就会一直等下去。countDown常见没有被调用的情况:异常中断,线程池拒绝策略。可以使用:public boolean await(long timeout, TimeUnit unit)根据业务情况,增加一个最大等待时间。使用这种方式,需要对失败的各种情况作出业务上的对应处理,否则就出现各种数据不正确的问题即:有一个运动员拉肚子没来,那么比赛就不能开始,显示是不会的,比赛是设定的有时间限制的,没有在规定的时间到场那么就是不用等他,我们继续比赛了。
Test.java
package com.rj.bd.threads.thread19;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* @desc 主线程等待子线程执行完成在执行
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
//1.创建一个固定数量的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
//2.实例化计数器,参数3表明此时子线程的数量为3个
final CountDownLatch latch = new CountDownLatch(3);
//3.创建三个子线程
for (int i = 0; i < 3; i++)
{
Runnable runnable =new Runnable()
{
@Override
public void run()
{
try
{
System.out.println("子线程" + Thread.currentThread().getName() + "开始执行");
TimeUnit.SECONDS.sleep(1);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完成");
latch.countDown();//当前线程调用此方法,则计数器减一
}
catch (InterruptedException e)
{
System.out.println("中断异常.....");
}
}
};
pool.execute(runnable); //将新创建的三个子线程装入线程池中
}
latch.await();//4.阻塞当前线程,直到计数器的值为0
System.out.println("主线程"+Thread.currentThread().getName()+"结束执行...");
pool.shutdown();//5.关闭线程池
}
}
2.3CyclicBarrier
CyclicBarrier:翻译成中文就是”循环栅栏/障碍“,可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,
这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
代码示例
package com.rj.bd.threads.thread20;
/**
* @desc 乘客类
*/
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Tourist extends Thread {
private CyclicBarrier cyclicBarrier;
public Tourist(CyclicBarrier cyclicBarrier){
this.cyclicBarrier=cyclicBarrier;
}
@Override
public void run()
{
try {
System.out.println("乘客:"+getName()+"已经向巴士车走来");
Thread.sleep(2000);
System.out.println("乘客:"+getName()+"已经在巴士上坐下了");
} catch (InterruptedException e)
{
System.out.println("中断异常。。。。");
}
try
{
cyclicBarrier.await();
System.out.println("乘客:"+getName()+":人满发车喽。。。。。。");
}
catch (InterruptedException e)
{
System.out.println("中断异常。。。。");
}
catch (BrokenBarrierException e)
{
System.out.println("打破障碍异常(人没到齐非要走).....");
}
}
}
2.4Semaphore
Semaphore可以用于做流量控制,特别公用资源有限的应用场景,比如数据库连接。假如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发的读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这时我们必须控制只有十个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,我们就可以使用Semaphore来做流控。
定义
Semaphore:是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。
Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态
1)Semaphore是 synchronized 的加强版,作用是控制线程的并发数量
2)Seampore类初始化的时候就可以指定阈值(这个阈值就是线程的并发数量)
3)Seampore经常被用来构建例如资源池,连接池这样的东西(为什么呢。因为这些池是需要一个动态平衡的)
4)Seampore的阈值为1的时候其功能类似于互斥锁
代码示例
package com.rj.bd.threads.thread20;
/**
* @desc 测试类:测试信号量类Semapore
*/
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class Test03 {
public static void main(String[] args) {
//1.创建一个线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//2.实例化信号量对象,且只允许 10个线程同时访问
final Semaphore semaphore = new Semaphore(10);
for (int i=0;i<30;i++)//3.假设程序中有30个进行IO读写的线程
{
//4.将创建的子线程装入到线程池中并执行
executorService.submit(new Runnable()
{
@Override
public void run()
{
try
{
//5.获取许可
semaphore.acquire();
//6.执行
System.out.println("\n获取许可并执行的线程的名字: " + Thread.currentThread().getName());
Thread.sleep(new Random().nextInt(5000)); // 模拟随机执行时长
//7.释放
semaphore.release();
System.out.println("\n释放线程的名字..." + Thread.currentThread().getName());
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
},i);
}
//8.关闭线程池
executorService.shutdown();
}
}