您好!欢迎来到北极熊网

北极熊网

热门搜索: 任正非传    神雕侠侣    红楼梦   

Java线程中的安全策略实例分析

  • 技术文档
  • 来源:北极熊
  • 编辑:佚名
  • 时间:2022-11-29 20:17
  • 阅读:58

一、不可变对象

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

(1)对象创建以后其状态就不能修改

(2)对象所有域都是final类型

(3)对象是正确创建的(在对象创建期间,this引用没有溢出)

对于不可变对象,可以参见JDK中的String类

final关键字:类、方法、变量

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

(2)修饰方法:锁定方法不被继承类修改;效率。注意:一个类的private方法会被隐式的指定为final方法

(3)修饰变量:基本数据类型变量(数值被初始化后不能再修改)、引用类型变量(初始化之后则不能再指向其他的对象)

在JDK中提供了一个Collections类,这个类中提供了很多以unmodifiable开头的方法,如下:

Collections.unmodifiableXXX: Collection、List、Set、Map…

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

此时,将我们自己创建的Collection、List、Set、Map,传递到Collections.unmodifiableXXX方法中,就变为不可变的了。此时,如果修改Collection、List、Set、Map中的元素就会抛出java.lang.UnsupportedOperationException异常。

在Google的Guava中,包含了很多以Immutable开头的类,如下:

ImmutableXXX,XXX可以是Collection、List、Set、Map…

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

 Markup

<dependency> 
    <groupId>com.google.guava</groupId> 
    <artifactId>guava</artifactId> 
    <version>23.0</version> </dependency>

二、线程封闭

(1)Ad-hoc线程封闭:程序控制实现,最糟糕,忽略

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

(3)ThreadLocal线程封闭:特别好的封闭方法

三、线程不安全类与写法

1. StringBuilder -> StringBuffer

StringBuilder:线程不安全;

StringBuffer:线程不安全;

字符串拼接涉及到多线程操作时,使用StringBuffer实现

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

2. SimpleDateFormat -> JodaTime

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

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

 Java

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; @Slf4j 
public 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); 
        } 
    } }

修改成如下代码即可。

 Java

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; @Slf4j 
public 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中添加如下依赖包:

 Markup

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

示例代码如下:

 Java

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; 
 @Slf4j 
public 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:同步操作,但是可能会出现线程不安全的情况,线程不安全的代码示例如下:

 Java

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)

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

 Java

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()操作。如下代码所示:

 Java

//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。


全部评论(0)
资讯详情页最新发布上方横幅
推荐阅读
  • javascript中如何匹配具体数量
  • javascript中如何匹配具体数量
  • 说明1、可花括号的数量说明符可用于指定匹配模式的上下限。但是有时候只需要特定数量的匹配。2、指定一定数量的匹配模式,只需在大括号之间放置一个数字。实例要求修改正则表达式timRegex,以匹配仅有四个字母 m 的单词 Timber。let timStr = "Timmmmber";let timRegex = /change
  • 技术服务
  • 来源:北极熊
  • 编辑:oal
  • 时间:2023-01-01 14:16
  • 阅读:25
  • javascript中先行断言指的是什么
  • javascript中先行断言指的是什么
  • 说明1、先断言是告诉JavaScript在字符串中向前搜索的匹配模式。如果你想在同一个字符串上搜索多个匹配模式,可能会有用。2、先行断言有两种:正向先行断言和负向先行断言。实例let quit = "qu";let noquit = "qt";let quRegex= /q(?=u)/;let qRegex&
  • 技术文档
  • 来源:北极熊
  • 编辑:oal
  • 时间:2023-01-01 14:15
  • 阅读:34
  • 怎么用JavaScript实现截屏功能
  • 怎么用JavaScript实现截屏功能
  • 1.Blob的媒体类型必须是"image/svg+xml"2.需要一个svg元素3.在svg元素里面插入一个 foreignObject 元素4.在foreignObject元素里面放入符合规范的html把dom转成canvas就这么简单,就上面几个步骤。下面是文档给出的一上简单的demo:
  • 技术文档
  • 来源:北极熊
  • 编辑:oal
  • 时间:2023-01-01 14:14
  • 阅读:38
  • javascript是不是解释型语言
  • javascript是不是解释型语言
  • javascript是具有函数优先的轻量级、解释型的编程语言。javascript被广泛用于web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。本文操作环境:windows10系统、javascript1.8.5、thinkpadt480电脑。JavaScript是一种具有函数优先的轻量级,解释型的编程语言。虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中,JavaScr
  • 技术文档
  • 来源:北极熊
  • 编辑:oal
  • 时间:2023-01-01 14:13
  • 阅读:31
  • javascript的连字符如何使用
  • javascript的连字符如何使用
  • 说明1、使用连字符(-)匹配字符的范围不仅限于字母。也可以匹配一系列数字。2、可以在单个字符集中组合一系列字母和数字。在=字符集中,连字符(-)能够定义要匹配的字符范围。实例要求匹配字符串 quoteSample 中的所有字母。 注意:一定要同时匹配大小写字母。let quoteSample = "The quick brown&nbs
  • 技术文档
  • 来源:北极熊
  • 编辑:oal
  • 时间:2023-01-01 12:11
  • 阅读:41
联系我们
电话:18936411277
邮箱:1044412291@qq.com
时间:09:00 - 19:00
公众号:北格软件
底部广告