概述
关于并发下ArrayList及HashMap的问题描述及处理
并发下的ArrayList
ArrayList是一个线程不安全的容器。如果在多线程中使用ArrayList,可能会导致程序出错。首先我们看一下以下代码:
package com.test.arrayList;
import java.util.ArrayList;
/**
* ArrayList是一个线程不安全的容器。如果在多线程中使用ArrayList,可能会导致程序出错。
* @author Anna.
*/
public class ThreadArrayListTest {
public static void main(String[] args) {
Data data = new Data();
AddThread addThread = new AddThread(data);
Thread th = new Thread(addThread);
Thread th2 = new Thread(addThread);
th.setName("甲");
th2.setName("乙");
th.start();
th2.start();
try {
th.join();
th2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("List size:" + data.getList().size());
}
}
class Data {
ArrayList<Integer> list = new ArrayList<Integer>();
public ArrayList<Integer> getList() {
return list;
}
public void setList(ArrayList<Integer> list) {
this.list = list;
}
}
class AddThread implements Runnable {
Data data;
public AddThread(Data data) {
this.data = data;
}
@Override
public void run() {
for (int i = 0; i < 100000; i++){
data.getList().add(i);
}
}
}
可能出现的结果如下:
(1)程序正确结束,ArrayList大小确实为200000个。这说明即使并发程序有问题,也未必会每次表现出来;
(2)程序抛出下标越界异常。如下:
原因分析:当一个线程在ArrayList扩容的过程中,参与进来,线程获取的大小为未扩容时的大小,由于没有锁的保护,导致其内部一致性被破坏,导致出现越界问题。
(3)程序不抛出异常,但是ArrayList集合大小小于200000个,如图:
原因分析:这是由于多线程访问冲突,是的保存容器大小的变量被多线程不正常的访问,同一时间两个线程对ArrayList中的一个位置进行赋值导致的。
解决办法
(1)使用线程安全的Vector代替ArrayList。相对于ArrayList于Vector都是使用数组作为其内部实现,不同的是ArrayList是线程不安全的,Vector是线程安全的。代码如下:
package com.test.arrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
/**
* ArrayList是一个线程不安全的容器。如果在多线程中使用ArrayList,可能会导致程序出错。
* 使用线程安全的Vector代替ArrayList
* @author Anna.
*/
public class ThreadArrayListTest02 {
public static void main(String[] args) {
Data02 data = new Data02();
AddThread02 addThread = new AddThread02(data);
Thread th = new Thread(addThread);
Thread th2 = new Thread(addThread);
th.setName("甲");
th2.setName("乙");
th.start();
th2.start();
try {
th.join();
th2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Vector size:" + data.getVector().size());
}
}
class Data02 {
private Vector vector = new Vector(10);
public Vector getVector() {
return vector;
}
public void setVector(Vector vector) {
this.vector = vector;
}
}
class AddThread02 implements Runnable {
Vector vector;
public AddThread02(Data02 data) {
this.vector = data.getVector();
}
@Override
public void run() {
for (int i = 0; i < 100000; i++){
vector.addElement(i);
}
}
}
Vector与ArrayList之间的相互转换
Vector v = new Vector();
ArrayList ar = new ArrayList();
Collections.copy(ar, v);
(2)使用Collections的synchronizedList方法将其转换为线程安全的容器再使用。同样,LinkedList使用链表结构实现的List,同样可以使用该方法将其转换为安全的容器在使用。代码如下:
package com.test.arrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* ArrayList是一个线程不安全的容器。如果在多线程中使用ArrayList,可能会导致程序出错。
* 使用Collections的synchronizedList方法将其转换为线程安全的容器再使用
* @author Anna.
*/
public class ThreadArrayListTest02 {
public static void main(String[] args) {
Data02 data = new Data02();
AddThread02 addThread = new AddThread02(data);
Thread th = new Thread(addThread);
Thread th2 = new Thread(addThread);
th.setName("甲");
th2.setName("乙");
th.start();
th2.start();
try {
th.join();
th2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// for(int i = 0; i < 200000;i++){
// System.out.println("List :" + data.getList().get(i));
// }
System.out.println("List size:" + data.getList().size());
}
}
class Data02 {
ArrayList<Integer> list = new ArrayList<Integer>();
public ArrayList<Integer> getList() {
return list;
}
public void setList(ArrayList<Integer> list) {
this.list = list;
}
}
class AddThread02 implements Runnable {
List<Integer> list;
public AddThread02(Data02 data) {
this.list = Collections.synchronizedList(data.getList());
}
public List<Integer> getList() {
return list;
}
public void setList(List<Integer> list) {
this.list = list;
}
@Override
public void run() {
for (int i = 0; i < 100000; i++){
list.add(i);
}
}
}
并发下的HashMap
并发下的HashMap同样是线程不安全的。如果在多线程中使用ArrayList,可能会导致程序出错。首先我们看一下以下代码:
package com.test.hashMap;
import java.util.ArrayList;
import java.util.Map;
/**
* HashMap同样是线程不安全的。如果在多线程中使用HashMap,可能会导致程序出错。
* @author Anna.
*/
public class ThreadHashMapTest {
public static void main(String[] args) {
Data data = new Data();
AddThread addThread = new AddThread(data);
Thread th = new Thread(addThread);
Thread th2 = new Thread(addThread);
th.setName("甲");
th2.setName("乙");
th.start();
th2.start();
try {
th.join();
th2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Map size:" + data.getMap().size());
}
}
class Data {
private Map<String,String> map;
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
class AddThread implements Runnable {
Data data;
public AddThread(Data data) {
this.data = data;
}
@Override
public void run() {
for (int i = 0; i < 100000; i++){
data.getMap().put(Integer.toString(i),String.valueOf(i));
}
}
}
可能出现的结果如下:
(1)程序正常结束,并且结果符合预期,HashMap的大小为200000
(2)程序正常结束,但结果不符合预期,而是一个小于200000的值。
(3)程序进入死循环。(可能造成电脑死机,该类较小,谨慎尝试)
原因分析:
出现前两种情况,与ArrayList类似。但是第三种,是因为多线程在遍历HashMap的内部数据时,形成死循环导致。
解决办法
(1)使用Collections.synchronizedMap()方法包装HashMap。如下产生的HashMap就是线程安全的。虽然通过Collections.synchronizedMap 来生成一个线程安全的 Map 实例, 但这是全局锁方式, 性能会有所不及;
public static Map map = Collections.synchronizedMap(new HashMap());
针对上述问题,解决代码如下:
package com.test.hashMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* HashMap同样是线程不安全的。如果在多线程中使用HashMap,可能会导致程序出错。
* 使用Collections.synchronizedMap()方法包装HashMap。产生的HashMap就是线程安全的
* @author Anna.
*/
public class ThreadHashMapTest02 {
public static void main(String[] args) {
Data data = new Data();
AddThread addThread = new AddThread(data);
Thread th = new Thread(addThread);
Thread th2 = new Thread(addThread);
th.setName("甲");
th2.setName("乙");
th.start();
th2.start();
try {
th.join();
th2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// for (int i = 0; i < 100000;i++) {
// System.out.println("Map Value:" + data.getMap().get(Integer.toString(i)));
// }
System.out.println("Map size:" + data.getMap().size());
}
}
class Data {
private Map<String,String> map = new HashMap<String, String>();
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
class AddThread implements Runnable {
Map map;
public AddThread(Data data) {
this.map = Collections.synchronizedMap(data.getMap());
}
@Override
public void run() {
for (int i = 0; i < 100000; i++){
map.put(Integer.toString(i),String.valueOf(i));
}
}
}
(2)使用ConcurrentHashMap类代替HashMap,通过减小锁的粒度(所谓减小锁粒度是指缩小锁定对象的范围,从而降低锁冲突的可能性,进而提高系统的并发能力)的方式削弱多线程竞争。这种技术典型的使用场景就是ConcurrentHashMap类的实现。
ConcurrentHashMap类内不进一步细分为若干个晓得HashMap,称之为段。在默认的情况下,一个ConcurrentHashMap类可以被细分为16段。因此ConcurrentHashMap类可以接受16个线程同时插入(如果都插入不同的段中),从而大大提升其吞吐量。
ConcurrentHashMap类虽然其put()方法很好的分离了锁,但是当试图访问ConcurrentHashMap类的全局信息时,就需要同时取得所有段的方法才能顺利实施,因此,减小粒度的方式,当系统需要获取全局锁时,其消耗的资源是比较多的。
因此,只有在类似于size()方法获取全局信息的方法调用并不频繁时,这种减小锁粒度的方法才能真正意义上提高系统的吞吐量。
具体参考:《Java并发包concurrent——ConcurrentHashMap》
最后
以上就是难过小懒虫为你收集整理的Java并发学习(三)-关于并发下ArrayList及HashMap的问题描述及处理关于并发下ArrayList及HashMap的问题描述及处理的全部内容,希望文章能够帮你解决Java并发学习(三)-关于并发下ArrayList及HashMap的问题描述及处理关于并发下ArrayList及HashMap的问题描述及处理所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复