1.并发下集合(list,map,set)的线程不安全和解决方法

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();
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值