在多线程中如何保证集合的安全

线程和进程

进程(Process)的概念。狭义的进程是正在运行的程序的实例;广义的进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是操作系统动态执行的基本单元。

线程(Thread),有时被称为轻量级进程(LWP),是程序执行流的最小单位;一个标准的线程由线程ID、当前指令指针(PC)、寄存器集合和堆栈组成。

通常情况下,一个进程由一个到多个线程组成,各个线程之间共享程序的内存空间及一些进程级的资源。

在大多数软件应用中,线程的数量都不止一个,多线程程序处在一个多变的环境中,可访问的全局变量和堆数据随时都可能被其他的线程改变,这就将“线程安全”的问题提上了议程。那么,如何确保线程的安全?

确保线程安全的方法

一般说来,基于Linux操作系统,确保线程安全的方法有这几个:竞争与原子操作、同步与锁、可重入、过度优化

竞争与原子操作
多个线程同时访问和修改一个数据,可能造成很严重的后果。出现严重后果的原因是很多操作被操作系统编译为汇编代码之后不止一条指令,因此在执行的时候可能执行了一半就被调度系统打断了而去执行别的代码了。一般将单指令的操作称为原子的(Atomic),因为不管怎样,单条指令的执行是不会被打断的。

因此,为了避免出现多线程操作数据的出现异常,Linux系统提供了一些常用操作的原子指令,确保了线程的安全。但是,它们只适用于比较简单的场合,在复杂的情况下就要选用其他的方法了。

同步与锁
为了避免多个线程同时读写一个数据而产生不可预料的后果,开发人员要将各个线程对同一个数据的访问同步,也就是说,在一个线程访问数据未结束的时候,其他线程不得对同一个数据进行访问。

同步的最常用的方法是使用锁(Lock),它是一种非强制机制,每个线程在访问数据或资源之前首先试图获取锁,并在访问结束之后释放锁;在锁已经被占用的时候试图获取锁时,线程会等待,直到锁重新可用。

二元信号量是最简单的一种锁,它只有两种状态:占用与非占用,它适合只能被唯一一个线程独占访问的资源。对于允许多个线程并发访问的资源,要使用多元信号量(简称信号量)。

可重入
一个函数被重入,表示这个函数没有执行完成,但由于外部因素或内部因素,又一次进入该函数执行。一个函数称为可重入的,表明该函数被重入之后不会产生任何不良后果。可重入是并发安全的强力保障,一个可重入的函数可以在多线程环境下放心使用。

过度优化
在很多情况下,即使我们合理地使用了锁,也不一定能够保证线程安全,因此,我们可能对代码进行过度的优化以确保线程安全。

我们可以使用volatile关键字试图阻止过度优化,它可以做两件事:第一,阻止编译器为了提高速度将一个变量缓存到寄存器而不写回;第二,阻止编译器调整操作volatile变量的指令顺序。

在另一种情况下,CPU的乱序执行让多线程安全保障的努力变得很困难,通常的解决办法是调用CPU提供的一条常被称作barrier的指令,它会阻止CPU将该指令之前的指令交换到barrier之后,反之亦然。

在循环中修改集合数据的问题

看代码:

List asList = Arrays.asList(1,2);
  for(int i:asList){
  asList.add(3);
  System.err.println(i);
}

我们执行这段代码会抛出这个异常 java.lang.UnsupportedOperationException,这个异常告诉我们,不支持在迭代中修改当前的集合(增加或删除元素);

为什么会这样?那我们应该如果避免这个问题呢?

1、foreach循环中是不支持对集合中的元素删除或添加的。

2、使用Arrays.asList产生的集合不支持在循环中更改;使用new ArrayList是可以的;但是我们不应该使用这种写法;因为元素的添加会移除会导致集合的长度发生变化;这极容易导致BUG;并且不易排查。

3、如果确实需要在循环中移除元素; 可以考虑使用迭代器进行操作Iterator

但是如果我们在一个多线程的程序中使用了一个共享的集合;我们怎么能保证其它线和不更改集合中的元素?

  可以使用Collections.synchronizedList获取一个线程安全的集合,要想在循环集合的时候保证线程的安全必须在循环的外层为所需要循环或迭代的集合添加锁控制;即获取当前集合持有的锁进行访问控制;

代码:

final static List asList = Collections.synchronizedList(new ArrayList(Arrays.asList("a","b")));
public static void main(String[] args) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {          Thread.sleep(150);           }        catch (          InterruptedException e) { }
            reload();
        }
        }).start();
    synchronized (asList) {
      for(String e:asList){
            try {Thread.sleep(100);}             catch (InterruptedException e1) {}
           System.out.println(e);
      }
    }
}
public static void reload(){
  asList.clear();
  asList.addAll(Arrays.asList("a","b","c"));
}

注意:如果不使用Collections.synchronizedList获取一个线程安全的集合;就必须在修改集合之前首先获取集合的锁进行同步控制;或者可以使用java.util.concurrent包下的相关线程安全的集合类。

原文地址:https://www.cnblogs.com/huangdabing/p/9251614.html

时间: 2024-10-15 16:30:08

在多线程中如何保证集合的安全的相关文章

JAVA并发七(多线程环境中安全使用集合API)

在集合API中,最初设计的Vector和Hashtable是多线程安全的.例如:对于Vector来说,用来添加和删除元素的方法是同步的.如果只有一个线程与Vector的实例交互,那么,要求获取和释放对象锁便是一种浪费,另外在不必要的时候如果滥用同步化,也有可能会带来死锁.因此,对于更改集合内容的方法,没有一个是同步化的.集合本质上是非多线程安全的,当多个线程与集合交互时,为了使它多线程安全,必须采取额外的措施. 在Collections类 中有多个静态方法,它们可以获取通过同步方法封装非同步集合

【Java并发编程】之八:多线程环境中安全使用集合API(含代码)

 在集合API中,最初设计的Vector和Hashtable是多线程安全的.例如:对于Vector来说,用来添加和删除元素的方法是同步的.如果只有一个线程与Vector的实例交互,那么,要求获取和释放对象锁便是一种浪费,另外在不必要的时候如果滥用同步化,也有可能会带来死锁.因此,对于更改集合内容的方法,没有一个是同步化的.集合本质上是非多线程安全的,当多个线程与集合交互时,为了使它多线程安全,必须采取额外的措施. 在Collections类 中有多个静态方法,它们可以获取通过同步方法封装非同步集

转:【Java并发编程】之八:多线程环境中安全使用集合API(含代码)

转载请注明出处:http://blog.csdn.net/ns_code/article/details/17200509     在集合API中,最初设计的Vector和Hashtable是多线程安全的.例如:对于Vector来说,用来添加和删除元素的方法是同步的.如果只有一个线程与Vector的实例交互,那么,要求获取和释放对象锁便是一种浪费,另外在不必要的时候如果滥用同步化,也有可能会带来死锁.因此,对于更改集合内容的方法,没有一个是同步化的.集合本质上是非多线程安全的,当多个线程与集合交

Java并发编程(8):多线程环境中安全使用集合API(含代码)

Java并发编程(8):多线程环境中安全使用集合API(含代码)JAVA大数据中高级架构 2018-11-09 14:44:47在集合API中,最初设计的Vector和Hashtable是多线程安全的.例如:对于Vector来说,用来添加和删除元素的方法是同步的.如果只有一个线程与Vector的实例交互,那么,要求获取和释放对象锁便是一种浪费,另外在不必要的时候如果滥用同步化,也有可能会带来死锁.因此,对于更改集合内容的方法,没有一个是同步化的.集合本质上是非多线程安全的,当多个线程与集合交互时

如何保证单例模式在多线程中的线程安全性

对大数据.分布式.高并发等知识的学习必须要有多线程的基础.这里讨论一下如何在多线程的情况下设计单例模式.在23中设计模式中单例模式是比较常见的,在非多线程的情况下写单例模式,考虑的东西会很少,但是如果将多线程和单例模式结合起来,考虑的事情就变多了,如果使用不当(特别是在生成环境中)就会造成严重的后果.所以如何使单例模式在多线程中是安全的显得尤为重要,下面介绍各个方式的优缺点以及可用性: 1.立即加载(饿汉模式) 立即加载模式就是在调用getInstance()方法前,实例就被创建了,例: pub

c#多线程中Lock()关键字的用法小结

本篇文章主要是对c#多线程中Lock()关键字的用法进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助 本文介绍C# lock关键字,C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待. 每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数.这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生.

c#初学-多线程中lock用法的经典实例

本文转载自:http://www.cnblogs.com/promise-7/articles/2354077.html 一.Lock定义     lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断.它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待.这是通过在代码块运行期间为给定对象获取互斥锁来实现的. 在多线程中,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数.这可能带来的问

JAVA集合框架中的常用集合及其特点、适用场景、实现原理简介

JJDK提供了大量优秀的集合实现供开发者使用,合格的程序员必须要能够通过功能场景和性能需求选用最合适的集合,这就要求开发者必须熟悉Java的常用集合类.本文将就Java Collections Framework中常用的集合及其特点.适用场景.实现原理进行介绍,供学习者参考.当然,要真正深入理解Java的集合实现,还是要推荐去阅读JDK的源码. Java提供的众多集合类由两大接口衍生而来:Collection接口和Map接口 Collection接口 Collection接口定义了一个包含一批对

Java核心知识点学习----多线程中的阻塞队列,ArrayBlockingQueue介绍

1.什么是阻塞队列? 所谓队列,遵循的是先进先出原则(FIFO),阻塞队列,即是数据共享时,A在写数据时,B想读同一数据,那么就将发生阻塞了. 看一下线程的四种状态,首先是新创建一个线程,然后,通过start方法启动线程--->线程变为可运行可执行状态,然后通过数据产生共享,线程产生互斥---->线程状态变为阻塞状态---->阻塞状态想打开的话可以调用notify方法. 这里Java5中提供了封装好的类,可以直接调用然后构造阻塞状态,以保证数据的原子性. 2.如何实现? 主要是实现Blo