Java线程安全问题,java线程不安全详解_1

  Java线程安全问题,java线程不安全详解

  00-1010 I、不可变对象II、线程闭包III、线程不安全类和编写IV、线程安全-同步容器1。数组列表向量,堆栈2。Hashmap-Hashtable (key,Value不能为null) 3。收藏。Synchronized XXX (list,Set,Map) V .线程安全-并发容器j . u . C1 . ArrayList-CopyonWriteArrayList 2 .HashSet,TreeSet-CopyOnWriteArraySet,ConcurrentSkipListSet3。HashMap,TreeMap-ConcurrentHashMap,ConcurrentskiplistMap 4。ConcurrentskiplistMap与ConcurrentHashMap的比较如下:6 .安全共享对象的策略-摘要

  00-1010不可变对象需要满足的条件

  (1)对象创建后,其状态不可修改。

  (2)对象的所有域都是最终类型。

  (3)正确创建了对象(在创建对象期间,此引用没有溢出)

  对于不可变对象,您可以在JDK看到String类。

  最终关键词:类、方法、变量

  (1)装饰类:该类不能继承。基本类型的String类和wrapper类(如Integer、Long等。)都是final类型。final类中的成员变量可以根据需要设置为final类型,但是final类中的所有成员方法都将被隐式指定为final方法。

  (2)修改方法:锁方法不被继承的类修改;效率。注意:类的私有方法被隐式指定为最终方法。

  (3)修改变量:基本数据类型变量(初始化后不能修改值)和引用类型变量(初始化后不能指向其他对象)。

  JDK提供了一个集合类,该类提供了许多以不可修改开头的方法,如下所示:

  Collections.unmodifiableXXX:集合、列表、集合、映射…

  Collections.unmodifiableXXX方法中的XXX可以是Collection、List、Set、Map…

  此时,我们自己创建的集合、列表、集合和映射被传递给Collections.unmodifiableXXX方法,该方法变得不可变。此时,如果修改了集合、列表、集合和映射中的元素,将会抛出java.lang.unsupported操作异常exception。

  在谷歌的番石榴中,有很多以不可变开头的类,如下:

  ImmutableXXX,XXX可以是集合,列表,集合,映射…

  注意:要使用Google的Guava,需要在Maven中添加以下依赖包:

  依赖性groupIdcom.google.guava/groupId artifactId guava/artifactId版本23.0/版本/依赖性

  00-1010 (1)临时线程关闭:程序控制实现,最差,忽略

  (2)栈闭包:局部变量,无并发问题。

  (3)ThreadLocal线程闭包:一种特别好的闭包方法。

  

目录

1. StringBuilder - StringBuffer

 

  StringBuilder:线程不安全;

  StringBuffer:线程不安全;

  字符串拼接涉及多线程操作时使用StringBuffer。

  在具体的方法中,定义一个字符串拼接对象,可以通过StringBuilder实现。因为局部变量在方法中定义使用时,属于封闭堆栈,只有一个线程会使用该变量,不涉及多线程对变量的操作。用StringBuilder就行了。

  2. SimpleDateFormat - JodaTime

  SimpleDateFormat:线程是不安全的,可以将其对象的实例化放到具体的时间格式化方法中实现线程安全:JodaTime: thread safety。

  SimpleDateFormat线程不安全的代码示例如下:

  包io.binghe.concurrency

  .example.commonunsafe;import lombok.extern.slf4j.Slf4j;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;@Slf4jpublic class DateFormatExample { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); //请求总数 public static int clientTotal = 5000; //同时并发执行的线程数 public static int threadTotal = 200; public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for(int i = 0; i < clientTotal; i++){ executorService.execute(() -> { try{ semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } public static void update(){ try { simpleDateFormat.parse("20191024"); } catch (ParseException e) { log.error("parse exception", e); } }}修改成如下代码即可。

  

package io.binghe.concurrency.example.commonunsafe;import lombok.extern.slf4j.Slf4j;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;@Slf4jpublic class DateFormatExample2 { //请求总数 public static int clientTotal = 5000; //同时并发执行的线程数 public static int threadTotal = 200; public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for(int i = 0; i < clientTotal; i++){ executorService.execute(() -> { try{ semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } public static void update(){ try { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); simpleDateFormat.parse("20191024"); } catch (ParseException e) { log.error("parse exception", e); } }}

对于JodaTime需要在Maven中添加如下依赖包:

 

  

<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.9</version></dependency>

示例代码如下:

 

  

package io.binghe.concurrency.example.commonunsafe;import lombok.extern.slf4j.Slf4j;import org.joda.time.DateTime;import org.joda.time.format.DateTimeFormat;import org.joda.time.format.DateTimeFormatter;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;@Slf4jpublic class DateFormatExample3 { //请求总数 public static int clientTotal = 5000; //同时并发执行的线程数 public static int threadTotal = 200; private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd"); public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for(int i = 0; i < clientTotal; i++){ final int count = i; executorService.execute(() -> { try{ semaphore.acquire(); update(count); semaphore.release(); }catch (Exception e){ log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } public static void update(int i){ log.info("{} - {}", i, DateTime.parse("20191024", dateTimeFormatter)); }}

3. ArrayList、HashSet、HashMap等Collections集合类为线程不安全类

 

  4. 先检查再执行:if(condition(a)){handle(a);}

  注意:这种写法是线程不安全的!!!!!

  两个线程同时执行这种操作,同时对if条件进行判断,并且a变量是线程共享的,如果两个线程均满足if条件,则两个线程会同时执行handle(a)语句,此时,handle(a)语句就可能不是线程安全的。

  不安全的点在于两个操作中,即使前面的执行过程是线程安全的,后面的过程也是线程安全的,但是前后执行过程的间隙不是原子性的,因此,也会引发线程不安全的问题。

  实际过程中,遇到if(condition(a)){handle(a);}类的处理时,考虑a是否是线程共享的,如果是线程共享的,则需要在整个执行方法上加锁,或者保证if(condition(a)){handle(a);}的前后两个操作(if判断和代码执行)是原子性的。

  

 

  

四、线程安全-同步容器

 

  

1. ArrayList -> Vector, Stack

ArrayList:线程不安全;

 

  Vector:同步操作,但是可能会出现线程不安全的情况,线程不安全的代码示例如下:

  

public class VectorExample { private static Vector<Integer> vector = new Vector<>(); public static void main(String[] args) throws InterruptedException { while (true){ for(int i = 0; i < 10; i++){ vector.add(i); } Thread thread1 = new Thread(new Runnable() { @Override public void run() { for(int i = 0; i < vector.size(); i++){ vector.remove(i); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { for(int i = 0; i < vector.size(); i++){ vector.get(i); } } }); thread1.start(); thread2.start(); } }}

Stack:继承自Vector,先进后出。

 

  

 

  

2. HashMap -> HashTable(Key, Value都不能为null)

HashMap:线程不安全;

 

  HashTable:线程安全,注意使用HashTable时,Key, Value都不能为null;

  

 

  

3. Collections.synchronizedXXX(List、Set、Map)

注意:在遍历集合的时候,不要对集合进行更新操作。当需要对集合中的元素进行删除操作时,可以遍历集合,先对需要删除的元素进行标记,集合遍历结束后,再进行删除操作。例如,下面的示例代码:

 

  

public class VectorExample3 { //此方法抛出:java.util.ConcurrentModificationException private static void test1(Vector<Integer> v1){ for(Integer i : v1){ if(i == 3){ v1.remove(i); } } } //此方法抛出:java.util.ConcurrentModificationException private static void test2(Vector<Integer> v1){ Iterator<Integer> iterator = v1.iterator(); while (iterator.hasNext()){ Integer i = iterator.next(); if(i == 3){ v1.remove(i); } } } //正常 private static void test3(Vector<Integer> v1){ for(int i = 0; i < v1.size(); i++){ if(i == 3){ v1.remove(i); } } } public static void main(String[] args) throws InterruptedException { Vector<Integer> vector = new Vector<>(); vector.add(1); vector.add(2); vector.add(3); //test1(vector); //test2(vector); test3(vector); }}

 

  

五、线程安全-并发容器J.U.C

J.U.C表示的是java.util.concurrent报名的缩写。

 

  

 

  

1. ArrayList -> CopyOnWriteArrayList

ArrayList:线程不安全;

 

  CopyOnWriteArrayList:线程安全;

  写操作时复制,当有新元素添加到CopyOnWriteArrayList数组时,先从原有的数组中拷贝一份出来,然后在新的数组中进行写操作,写完之后再将原来的数组指向到新的数组。整个操作都是在锁的保护下进行的。

  CopyOnWriteArrayList缺点:

  (1)每次写操作都需要复制一份,消耗内存,如果元素特别多,可能导致GC;

  (2)不能用于实时读的场景,适合读多写少的场景;

  CopyOnWriteArrayList设计思想:

  (1)读写分离

  (2)最终一致性

  (3)使用时另外开辟空间,解决并发冲突

  注意:CopyOnWriteArrayList读操作时,都是在原数组上进行的,不需要加锁,写操作时复制,当有新元素添加到CopyOnWriteArrayList数组时,先从原有的集合中拷贝一份出来,然后在新的数组中进行写操作,写完之后再将原来的数组指向到新的数组。整个操作都是在锁的保护下进行的。

  

 

  

2.HashSet、TreeSet -> CopyOnWriteArraySet、ConcurrentSkipListSet

CopyOnWriteArraySet:线程安全的,底层实现使用了CopyOnWriteArrayList。

 

  ConcurrentSkipListSet:JDK6新增的类,支持排序。可以在构造时,自定义比较器,基于Map集合。在多线程环境下,ConcurrentSkipListSet中的contains()方法、add()、remove()、retain()等操作,都是线程安全的。但是,批量操作,比如:containsAll()、addAll()、removeAll()、retainAll()等操作,并不保证整体一定是原子操作,只能保证批量操作中的每次操作是原子性的,因为批量操作中是以循环的形式调用的单步操作,比如removeAll()操作下以循环的方式调用remove()操作。如下代码所示:

  

//ConcurrentSkipListSet类型中的removeAll()方法的源码public boolean removeAll(Collection<?> c) { // Override AbstractSet version to avoid unnecessary call to size() boolean modified = false; for (Object e : c) if (remove(e)) modified = true; return modified;}

所以,在执行ConcurrentSkipListSet中的批量操作时,需要考虑加锁问题。

 

  注意:ConcurrentSkipListSet类不允许使用空元素(null)。

  

 

  

3. HashMap、TreeMap -> ConcurrentHashMap、ConcurrentSkipListMap

ConcurrentHashMap:线程安全,不允许空值

 

  ConcurrentSkipListMap:是TreeMap的线程安全版本,内部是使用SkipList跳表结构实现

  

 

  

4.ConcurrentSkipListMap与ConcurrentHashMap对比如下

(1)ConcurrentSkipListMap中的Key是有序的,ConcurrentHashMap中的Key是无序的;

 

  (2)ConcurrentSkipListMap支持更高的并发,对数据的存取时间和线程数几乎无关,也就是说,在数据量一定的情况下,并发的线程数越多,ConcurrentSkipListMap越能体现出它的优势。

  注意:在非对线程下尽量使用TreeMap,另外,对于并发数相对较低的并行程序,可以使用Collections.synchronizedSortedMap,将TreeMap进行包装;对于高并发程序,使用ConcurrentSkipListMap提供更高的并发度;在多线程高并发环境中,需要对Map的键值对进行排序,尽量使用ConcurrentSkipListMap。

  

 

  

六、安全共享对象的策略-总结

(1)线程限制:一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改

 

  (2)共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它。

  (3)线程安全对象:一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它

  (4)被守护对象:被守护对象只能通过获取特定的锁来访问

  到此这篇关于一文详解Java线程中的安全策略的文章就介绍到这了,更多相关Java线程安全策略内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: