iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >一文详解Java线程中的安全策略
  • 754
分享到

一文详解Java线程中的安全策略

2024-04-02 19:04:59 754人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

目录一、不可变对象二、线程封闭三、线程不安全类与写法四、线程安全-同步容器1. ArrayList -> Vector, Stack2. HashMap -> HashT

一、不可变对象

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

(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中添加如下依赖包:

<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线程不安全的代码示例如下:

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);
        }
    }
}

修改成如下代码即可。

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

<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;

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

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线程安全策略内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 一文详解Java线程中的安全策略

本文链接: https://www.lsjlt.com/news/149013.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 一文详解Java线程中的安全策略
    目录一、不可变对象二、线程封闭三、线程不安全类与写法四、线程安全-同步容器1. ArrayList -> Vector, Stack2. HashMap -> HashT...
    99+
    2022-11-13
  • Java线程中的安全策略实例分析
    这篇文章主要介绍“Java线程中的安全策略实例分析”,在日常操作中,相信很多人在Java线程中的安全策略实例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java线程中的安全策略实例分析”的疑惑有所帮助!...
    99+
    2023-06-30
  • Java线程池的四种拒绝策略详解
    目录预先配置配置线程池。创建线程任务拒绝策略一:AbortPolicy拒绝策略二:CallerRunsPolicy拒绝策略三:DiscardPolicy拒绝策略四:DiscardOl...
    99+
    2022-11-13
  • 详解什么是Java线程池的拒绝策略?
    目录一、拒绝策略1.1 AbortPolicy(默认拒绝策略)1.2 CallerRunsPolicy(使用调用线程池的线程来执行任务 )1.3 DiscardPolicy (忽略新...
    99+
    2022-11-12
  • 程序猿必须要掌握的多线程安全问题之锁策略详解
    目录一、常见的锁策略1.1 乐观锁1.2 悲观锁1.3 读写锁1.4 公平锁与非公平锁1.5 自旋锁(Spin Lock)1.6 可重入锁1.7 相关题目二、CAS问题2.1 什么是...
    99+
    2022-11-12
  • 一文搞懂Java中的线程安全与线程同步
    目录1.为什么需要线程同步线程安全问题2.怎么实现线程同步2.1.使用volatile关键字2.2.使用synchronized关键字1.为什么需要线程同步 什么是线程安全:指在被多...
    99+
    2022-11-13
  • java线程池ThreadPoolExecutor的八种拒绝策略示例详解
    目录池化设计思想线程池触发拒绝策略的时机JDK内置4种线程池拒绝策略拒绝策略接口定义CallerRunsPolicy(调用者运行策略)AbortPolicy(中止策略)Discard...
    99+
    2022-11-12
  • Java多线程之线程安全问题详解
    目录1. 什么是线程安全和线程不安全?2. 自增运算为什么不是线程安全的?3. 临界区资源和竞态条件总结:面试题: 什么是线程安全和线程不安全?自增运算是不是线程安全的?如何保证多线...
    99+
    2022-11-13
  • java多线程创建及线程安全详解
    什么是线程 线程被称为轻量级进程,是程序执行的最小单位,它是指在程序执行过程中,能够执行代码的一个执行单位。每个程序程序都至少有一个线程,也即是程序本身。 线程...
    99+
    2022-11-12
  • 关于java中线程安全问题详解
    目录一、什么时候数据在多线程并发的环境下会存在安全问题?二、怎么解决线程安全问题?三、银行 取钱/存钱 案例为什么会出现线程安全问题四、总结 一、什么时候数据在多线程并发的环境下会存...
    99+
    2022-11-12
  • Java中线程状态+线程安全问题+synchronized的用法详解
    目录java中的线程状态线程安全问题案例分析多线程对同一变量进行写操作内存可见性问题指令重排序问题synchronized的用法synchronized起作用的本质修饰普通方法修饰静...
    99+
    2022-11-13
  • 【Java系列】详解多线程(三)—— 线程安全(下篇)
    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【Java系列专栏】【JaveEE学习专栏】 本专栏旨在分享学习Java的一点学习心得,欢迎大家在评...
    99+
    2023-12-22
    java 安全 多线程 java-ee
  • java中线程池的拒绝策略有哪些
    本篇文章为大家展示了java中线程池的拒绝策略有哪些,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Java的特点有哪些Java的特点有哪些1.Java语言作为静态面向对象编程语言的代表,实现了面向对...
    99+
    2023-06-14
  • Java使用线程同步解决线程安全问题详解
    第一种方法:同步代码块: 作用:把出现线程安全的核心代码上锁 原理:每次只能一个线程进入,执行完毕后自行解锁,其他线程才能进来执行 锁对象要求:理论上,锁对象只要对于当前同时执行的线...
    99+
    2022-11-13
  • Java线程的全方位详解
    目录什么是线程?线程的几种创建方式?线程的几种状态?线程相关的核心方法及作用?❤️‍大家好,我是贾斯汀,今天主要聊一聊关于线程的瓜!d...
    99+
    2022-11-12
  • 一文详解NSSecureCoding真的安全吗
    目录NSSecureCodingNSCoding的用法NSSecureCoding的用法NSSecureCoding的致命缺点结语NSSecureCoding NSSecureCod...
    99+
    2023-05-14
    NSSecureCoding安全解析 NSSecureCoding安全
  • 解析Java多线程之常见锁策略与CAS中的ABA问题
    目录1.常见的锁策略1.1乐观锁与悲观锁1.2读写锁与普通互斥锁1.3重量级锁与轻量级锁1.4挂起等待锁与自旋锁1.5公平锁与非公平锁1.6可重入锁与不可重入锁1.7死锁问题1.7....
    99+
    2022-11-13
  • 一文精通Java 多线程之全方位解读
    目录并行和并发线程基础概念线程和进程多线程的好处线程的状态实现多线程的两种方式继承Thread类实现Runnable接口线程的安全性和原子性锁的概念和使用生产消费者模型生产消费者模型...
    99+
    2022-11-12
  • 通过本地安全策略中的应用程序控制策略限制软件运行
      1、打开控制面板,选择管理工具。如下图所示:   2、选择本地安全策略。如下图所示:   3、打开本地安全策略后,打开“应用程序控制策略”--点击“Applock...
    99+
    2022-06-04
    安全策略 策略 程序控制
  • 一文详解Qt中线程的实际应用
    目录1、多线程操作UI界面的示例2、避免多次connect3、优雅地结束线程的两种方法为了让程序尽快响应用户操作,在开发应用程序时经常会使用到线程。对于耗时操作如果不使用线程,UI界...
    99+
    2023-03-10
    Qt线程应用 Qt线程
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作