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<String> 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<String> data; public SampleTask(List<String> 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<String> 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<String> data; public SampleTask(List<String> 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<String> 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<String> data; public SampleTask(List<String> 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<String> 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<String> data;
public SampleTask(List<String> 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只在这一个线程中使用,那么是没问题的。 -
线程‘安全’的集合对象
较复杂的操作下,线程安全的集合对象也无法保证数据的同步,仍然需要我们来处理。
文/梦工厂(简书作者)
原文链接:http://www.jianshu.com/p/eccb5f350c12
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
原文链接:http://www.jianshu.com/p/eccb5f350c12
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
相关推荐
理解并熟练运用这些线程安全集合是构建健壮、高性能的多线程Java应用程序的基础。它们能帮助开发者编写出更安全、更高效的代码,避免因并发问题导致的错误。同时,根据具体场景选择合适的集合类型,可以极大地提高...
Java多线程是Java编程中的重要概念,尤其在如今的多核处理器环境下,理解并熟练掌握多线程技术对于提高程序性能和响应速度至关重要。本资料详细讲解了Java多线程的原理,并提供了丰富的实战代码,非常适合Java初学者...
Java多线程是Java编程语言中一个非常重要的概念,它允许开发者在一个程序中创建多个执行线程并行运行,以提高程序的执行效率和响应速度。在Java中,线程的生命周期包含五个基本状态,分别是新建状态(New)、就绪...
本知识点将深入探讨Java多线程设计以及如何利用“不可变对象”(immutable objects)来避免多线程环境中的非安全问题。 一、Java多线程基础 1. 线程的创建:Java提供了两种创建线程的方式——继承Thread类和实现...
Java多线程编程是Java开发中的...以上内容只是《Java多线程编程核心技术》教程中的一部分核心知识点,实际学习中还需要结合具体示例和实践来深入理解和掌握。通过学习,开发者可以编写出高效、稳定的多线程Java程序。
### Java多线程运算集合知识点解析 #### 一、Java多线程概念与原理 - **操作系统中的线程与进程**: - **进程**:指的是一个正在运行的应用程序,每个进程都拥有独立的内存空间。 - **线程**:是进程中的一个...
在面试中,对Java多线程的理解和熟练运用往往成为衡量开发者技能水平的重要标准。以下是对Java多线程面试题59题集合中可能涉及的一些关键知识点的详细解析。 1. **线程的创建方式** - 继承Thread类:创建一个新的...
《深入学习:Java多线程编程》是一本专注于Java并发技术的专业书籍,旨在帮助开发者深入理解和熟练运用Java中的多线程编程。Java多线程是Java编程中的核心部分,尤其在现代高性能应用和分布式系统中不可或缺。理解并...
基于Java多线程与线程安全实践是一个旨在展示如何在Java环境中高效、安全地使用多线程技术的系统。该系统通过结合源码示例和详细的使用文档,帮助开发者深入理解并实践线程安全的概念。 主要功能 线程安全示例:...
Java多线程是Java编程中的核心概念,它允许程序同时执行多个任务,提高了系统的效率和响应性。...通过这些题目,你可以检验自己对Java多线程的理解程度,并通过解答参考答案来查漏补缺,进一步提升自己的编程能力。
综上所述,"java多线程查询数据库"是一个涉及多线程技术、线程池管理、并发控制、分页查询等多个方面的复杂问题。通过理解和掌握这些知识点,我们可以有效地提高数据库操作的效率和系统的响应速度。
Java 集合类线程安全 Java 集合框架是由 Java 平台标准版 1.2 引入的通用数据结构与算法框架。其灵活的面对对象设计受到了广大 Java 程序员的一致青睐,为 Java 平台的成熟奠定了坚实的基础。 线程安全不是一个...
总结起来,“JAVA多线程编程技术PDF”涵盖了多线程的基本概念、同步机制、线程通信、死锁避免、线程池以及线程安全的集合类等内容。通过深入学习这份资料,开发者可以全面掌握Java多线程编程技术,提升程序的并发...
通过这些实例,学习者能够深入理解Java多线程编程,提高解决实际并发问题的能力。每个章节的源码都是一个独立的案例,可以逐一研究,实践和调试,以便更好地掌握Java多线程编程技巧。在学习过程中,结合理论知识与...
Java多线程设计模式是Java开发中的重要领域,它涉及到如何在并发环境下高效、安全地管理资源和控制程序执行流程。本资料集包含了清晰完整的PDF版书籍和源代码,为学习和理解Java多线程设计模式提供了丰富的素材。 ...
Java多线程编程是Java...在实际开发中,理解和熟练掌握Java多线程编程能够帮助开发者写出高效、稳定的并发程序,提升系统性能。通过学习本教程,你将对Java多线程编程有更深入的理解,能更好地应对并发编程中的挑战。
Java多线程是Java编程语言中的一个重要特性,它允许程序同时执行多个任务,极大地提高了程序的效率和响应性。在现代计算机系统中,多核处理器的普及使得多线程技术成为提升性能的关键手段。本篇将深入探讨Java多线程...
这个“JAVA-多线程 所有文件”压缩包很可能包含了一系列关于Java多线程学习的源代码示例和相关文档。下面我们将深入探讨Java多线程的相关知识点。 1. **线程的概念**:线程是操作系统分配CPU时间的基本单位,一个...
Java多线程是Java编程中不可或缺的一部分,它允许程序同时执行多个任务,提高了程序的效率和响应速度。本文将深入探讨Java多线程的相关概念、线程类和接口的使用,以及线程的同步与互斥。 首先,我们需要理解进程与...
Java多线程设计模式是Java开发中的核心概念,它涉及到如何高效、安全地在多个执行线程之间共享资源和协调任务。设计模式是解决特定问题的成熟方案,它们是编程经验的结晶,可以帮助开发者在面临多线程挑战时快速找到...