当前位置: 首页 > news >正文

java 多线程 安全 源码_Java多线程理解:线程安全的集合对象

1、概念介绍

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

线程不安全就是不提供数据访问保护,多线程先后更改数据会产生数据不一致或者数据污染的情况。

一般使用synchronized关键字加锁同步控制,来解决线程不安全问题。

2、线程安全的集合对象

ArrayList线程不安全,Vector线程安全;

HashMap线程不安全,HashTable线程安全;

StringBuilder线程不安全,StringBuffer线程安全。

3、代码测试

ArrayList线程不安全:

在主线程中新建100个子线程,分别向ArrayList中添加100个元素,最后打印ArrayList的size。

public class Test {

public static void main(String [] args){

// 用来测试的List

List data = new ArrayList<>();

// 用来让主线程等待100个子线程执行完毕

CountDownLatch countDownLatch = new CountDownLatch(100);

// 启动100个子线程

for(int i=0;i<100;i++){

SampleTask task = new SampleTask(data,countDownLatch);

Thread thread = new Thread(task);

thread.start();

}

try{

// 主线程等待所有子线程执行完成,再向下执行

countDownLatch.await();

}catch (InterruptedException e){

e.printStackTrace();

}

// List的size

System.out.println(data.size());

}

}

class SampleTask implements Runnable {

CountDownLatch countDownLatch;

List data;

public SampleTask(List data,CountDownLatch countDownLatch){

this.data = data;

this.countDownLatch = countDownLatch;

}

@Override

public void run() {

// 每个线程向List中添加100个元素

for(int i = 0; i < 100; i++)

{

data.add("1");

}

// 完成一个子线程

countDownLatch.countDown();

}

}

7次测试输出():

9998

10000

10000

ArrayIndexOutOfBoundsException

10000

9967

9936

Vector线程安全:

在主线程中新建100个子线程,分别向Vector中添加100个元素,最后打印Vector的size。

public class Test {

public static void main(String [] args){

// 用来测试的List

List data = new Vector<>();

// 用来让主线程等待100个子线程执行完毕

CountDownLatch countDownLatch = new CountDownLatch(100);

// 启动100个子线程

for(int i=0;i<100;i++){

SampleTask task = new SampleTask(data,countDownLatch);

Thread thread = new Thread(task);

thread.start();

}

try{

// 主线程等待所有子线程执行完成,再向下执行

countDownLatch.await();

}catch (InterruptedException e){

e.printStackTrace();

}

// List的size

System.out.println(data.size());

}

}

class SampleTask implements Runnable {

CountDownLatch countDownLatch;

List data;

public SampleTask(List data,CountDownLatch countDownLatch){

this.data = data;

this.countDownLatch = countDownLatch;

}

@Override

public void run() {

// 每个线程向List中添加100个元素

for(int i = 0; i < 100; i++)

{

data.add("1");

}

// 完成一个子线程

countDownLatch.countDown();

}

}

7次测试输出():

10000

10000

10000

10000

10000

10000

10000

使用synchronized关键字来同步ArrayList:

public class Test {

public static void main(String [] args){

// 用来测试的List

List data = new ArrayList<>();

// 用来让主线程等待100个子线程执行完毕

CountDownLatch countDownLatch = new CountDownLatch(100);

// 启动100个子线程

for(int i=0;i<100;i++){

SampleTask task = new SampleTask(data,countDownLatch);

Thread thread = new Thread(task);

thread.start();

}

try{

// 主线程等待所有子线程执行完成,再向下执行

countDownLatch.await();

}catch (InterruptedException e){

e.printStackTrace();

}

// List的size

System.out.println(data.size());

}

}

class SampleTask implements Runnable {

CountDownLatch countDownLatch;

List data;

public SampleTask(List data,CountDownLatch countDownLatch){

this.data = data;

this.countDownLatch = countDownLatch;

}

@Override

public void run() {

// 每个线程向List中添加100个元素

for(int i = 0; i < 100; i++)

{

synchronized(data){

data.add("1");

}

}

// 完成一个子线程

countDownLatch.countDown();

}

}

7次测试输出():

10000

10000

10000

10000

10000

10000

10000

3、原因分析

ArrayList在添加一个元素的时候,它会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。

在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;

而如果是在多线程情况下,比如有两个线程,线程 A 先将元素1存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B向此 ArrayList 添加元素2,因为此时 Size 仍然等于 0 (注意,我们假设的是添加一个元素是要两个步骤,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值,结果Size都等于1。

最后,ArrayList中期望的元素应该有2个,而实际元素是在0位置,造成丢失元素,故Size 等于1。导致“线程不安全”。

ArrayList源码:

@Override public boolean add(E object) {

Object[] a = array;

int s = size;

if (s == a.length) {

Object[] newArray = new Object[s +

(s < (MIN_CAPACITY_INCREMENT / 2) ?

MIN_CAPACITY_INCREMENT : s >> 1)];

System.arraycopy(a, 0, newArray, 0, s);

array = a = newArray;

}

a[s] = object;

size = s + 1;

modCount++;

return true;

}

Vector的所有操作方法都被同步了,既然被同步了,多个线程就不可能同时访问vector中的数据,只能一个一个地访问,所以不会出现数据混乱的情况,所以是线程安全的。

Vector源码:

@Override

public synchronized boolean add(E object) {

if (elementCount == elementData.length) {

growByOne();

}

elementData[elementCount++] = object;

modCount++;

return true;

}

4、线程安全的集合并不安全

分析以下场景:

synchronized(map){

Object value = map.get(key);

if(value == null)

{

value = new Object();

map.put(key,value);

}

return value;}

由于线程安全的集合对象是基于单个方法的同步,所以即使map是线程安全的,也会产生不同步现象。

在非单个方法的场景下,我们仍然需要使用synchronized加锁才能保证对象的同步。

代码测试:

public class Test {

public static void main(String [] args){

// 用来测试的List

List data = new Vector<>();

// 用来让主线程等待100个子线程执行完毕

CountDownLatch countDownLatch = new CountDownLatch(100);

// 启动100个子线程

for(int i=0;i<1000;i++){

SampleTask task = new SampleTask(data,countDownLatch);

Thread thread = new Thread(task);

thread.start();

}

try{

// 主线程等待所有子线程执行完成,再向下执行

countDownLatch.await();

}catch (InterruptedException e){

e.printStackTrace();

}

// List的size

System.out.println(data.size());

}

}

class SampleTask implements Runnable {

CountDownLatch countDownLatch;

List data;

public SampleTask(List data,CountDownLatch countDownLatch){

this.data = data;

this.countDownLatch = countDownLatch;

}

@Override

public void run() {

// 每个线程向List中添加100个元素

int size = data.size();

data.add(size,"1");

// 完成一个子线程

countDownLatch.countDown();

}

}

997

993

995

996

997

998

997

5、总结

如何取舍

线程安全必须要使用synchronized关键字来同步控制,所以会导致性能的降低。

当不需要线程安全时,可以选择ArrayList,避免方法同步产生的开销;

当多个线程操作同一个对象时,可以选择线程安全的Vector;

线程不安全!=不安全

有人在使用过程中有一个不正确的观点:我的程序是多线程的,不能使用ArrayList要使用Vector,这样才安全。

线程不安全并不是多线程环境下就不能使用。

注意线程不安全条件:多线程操作同一个对象。比如上述代码就是在多个线程操作同一个ArrayList对象。

如果每个线程中new一个ArrayList,而这个ArrayList只在这一个线程中使用,那么是没问题的。

线程‘安全’的集合对象

较复杂的操作下,线程安全的集合对象也无法保证数据的同步,仍然需要我们来处理。

[2015-08]

相关文章:

  • dir file list.file list.dirs
  • 后缀是php,怎么修改php后缀
  • inurl faq.php,使用 PHP
  • r语言中六种方法查看R函数源代码—— 鼠标放在函数上,按下F2
  • php mysqli_affected_rows(),Mysqli_num_rows与PHP中mysqli_affected_rows的区别
  • R语言字符串替换:gsub()
  • matlab实现数据压缩,【Matlab】Huffman编码如何实现数据压缩
  • getcurrenttime java,getcurrenttime
  • java nio netty 教程,4. 彤哥说netty系列之Java NIO实现群聊(自己跟自己聊上瘾了),netty实现...
  • java ucs 2,【字符编码系列】JavaScript使用的编码-UCS-2
  • R语言-文件管理系统操作 list.files dir.file dir
  • metaweblog php,.NET_在Asp.Net中创建MetaWeblog API, 概念   metaweblog是基于XML - phpStudy...
  • php android okgo上传图片,Android OkHttp的封装类OkGo的用法
  • find与xargs连用复制移动位置
  • linux命令行模式kvm,Linux虚拟机-使用命令行管理虚拟机---KVM一些常用的命令
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • Android Studio:GIT提交项目到远程仓库
  • Angular2开发踩坑系列-生产环境编译
  • Centos6.8 使用rpm安装mysql5.7
  • ES6语法详解(一)
  • git 常用命令
  • IDEA常用插件整理
  • Mysql优化
  • Rancher如何对接Ceph-RBD块存储
  • ucore操作系统实验笔记 - 重新理解中断
  • 笨办法学C 练习34:动态数组
  • 大型网站性能监测、分析与优化常见问题QA
  • 多线程事务回滚
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 基于 Ueditor 的现代化编辑器 Neditor 1.5.4 发布
  • 云大使推广中的常见热门问题
  • HanLP分词命名实体提取详解
  • UI设计初学者应该如何入门?
  • 阿里云ACE认证之理解CDN技术
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • #我与Java虚拟机的故事#连载07:我放弃了对JVM的进一步学习
  • #我与Java虚拟机的故事#连载13:有这本书就够了
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • $L^p$ 调和函数恒为零
  • (10)STL算法之搜索(二) 二分查找
  • (办公)springboot配置aop处理请求.
  • (新)网络工程师考点串讲与真题详解
  • (转)Google的Objective-C编码规范
  • .net oracle 连接超时_Mysql连接数据库异常汇总【必收藏】
  • .NET WebClient 类下载部分文件会错误?可能是解压缩的锅
  • .NET 分布式技术比较
  • .NET 服务 ServiceController
  • .netcore 6.0/7.0项目迁移至.netcore 8.0 注意事项
  • .NET中的十进制浮点类型,徐汇区网站设计
  • /*在DataTable中更新、删除数据*/
  • ;号自动换行
  • [04]Web前端进阶—JS伪数组
  • [8481302]博弈论 斯坦福game theory stanford week 1
  • [BZOJ 3531][Sdoi2014]旅行(树链剖分+线段树)
  • [ERROR] ocp-server-ce-py_script_start_check-4.2.1 RuntimeError: ‘tenant_name‘