【并发编程-基础】(五)线程安全策略

文章目录

一、不可变对象final

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

  • 对象创建以后其状态就不能修改
  • 对象所有域都是final类型
  • 对象是正确创建的(在对象创建期间,this引用没有逸出)

1.2、创建一个不可变对象的方法

  • 可以将类声明为final就不能被继承了,将所有成员设置为私有的,就不能直接访问这些成员。
  • 对变量不提供set方法,将所有可变的成员声明为final,这样只能对他们赋值一次。
  • 通过构造器初始化所有成员进行深度拷贝。
  • 在get方法中不直接返回对象本身,而是克隆对象返回对象的拷贝。
  • 关于创建对象可以参考String类型和各种声明为final的类

1.3、Collectionsunmodifiable (JDK的不可变集合)

       使用Java的Collection类的unmodifiable相关方法,可以创建不可变对象。unmodifiable相关方法包含:Collection、List、Map、Set….
在这里插入图片描述

1.3.1、实例:
public class ImmutableExample1 {
    private static Map<Integer, Integer> map = Maps.newHashMap();
    static {
        map.put(1, 2);
        map.put(3, 4);
        map.put(5, 6);
        map = Collections.unmodifiableMap(map);
    }
    public static void main(String[] args) {
        map.put(1,3);
        log.info("{}", map.get(1));
    }
}
1.3.2、输出

       在main中的map.put(1,3)报错,说明map对象已经成为不可变对象了

1.3.2、解析

       Collections.unmodifiableMap在执行时,将参数中的map对象进行了转换,转换为Collection类中的内部类 UnmodifiableMap对象。而 UnmodifiableMapmap的更新方法(比如putremove等)进行了重写,均返回UnsupportedOperationException异常,这样就做到了map对象的不可变。

1.4、Google GuavaImmutable (不可变集合)

       使用Guava的Immutable相关类也可以创建不可变对象。同样包含很多类型:Collection、List、Map、Set….

<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>27.0.1-jre</version>
</dependency>

1577772477526

1.4.1、实例
@Slf4j
public class ImmutableExample3 {
    private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3);

    //ImmutableSet除了使用类似于ImmutableList的of的方法进行初始化,还可以使用copyof方法,将Collection类型、Iterator类型作为参数。
    private final static ImmutableSet set = ImmutableSet.copyOf(list);

    private final static ImmutableMap<Integer,Integer> map = ImmutableMap.of(1,2,3,4);
    
    //builder()是ImmutableMap的特殊写法
    private final static ImmutableMap<Integer,Integer> map2 = ImmutableMap.<Integer,Integer>builder().put(1,2).put(3,4).build();

    public static void main(String[] args) {
        map2.put(1,2);
        log.info("{}",map2.get(3));
    }
}

在这里插入图片描述

1.4.2、输出结果

       在main中put的那一行报错

1.4.3、解析

       Immutable相关类使用了跟Java的unmodifiable相关类相似的实现方法。

1.5、 unmodifiable VS Immutable

       Immutable的功能更强大,更具有人性化,提供的数据结构非常的多,比如Multimap(一对多的map),BiMap(可以通过value 查找key)等等等…

1.6、final关键字修饰

       1.6.1、修饰类
  • ​ 该类不能被继承,其中的所有成员方法都是final的
       1.6.2、修饰方法
  • ​ 锁定方法不能被继承类修改
  • ​ 效率
       1.6.3、修饰变量
  • ​ 基本数据类型变量
  • ​ 引用类型变量

二、线程封闭

       把对象封装到一个线程里,只有这一个线程能够访问,即使它不是线程安全的,也不会出现任何线程安全方面的问题。

2.1、线程封闭技术的常见应用(jdbc的Connection对象)

  • 数据库连接对应jdbc的Connection对象,Connection对象在实现的时候并没有对线程安全做太多的处理,jdbc的规范里也没有要求Connection对象必须是线程安全的。
  • 实际在服务器应用程序中,线程从连接池获取了一个Connection对象,使用完再把Connection对象返回给连接池,由于大多数请求都是由单线程采用同步的方式来处理的,并且在Connection对象返回之前,连接池不会将它分配给其他线程。
  • 因此这种连接管理模式处理请求时隐含的将Connection对象封闭在线程里面,这样我们使用的connection对象虽然本身不是线程安全的,但是它通过线程封闭也做到了线程安全。

2.1、线程封闭的种类

       2.1.1、Ad-hoc线程封闭
  • Ad-hoc线程封闭是指,维护线程封闭性的职责完全由程序实现来承担。Ad-hoc线程封闭是非常脆弱的,因为没有任何一种语言特性,例如可见性修饰符或局部变量,能将对象封闭到目标线程上。事实上,对线程封闭对象(例如,GUI应用程序中的可视化组件或数据模型等)的引用通常保存在公有变量中。
  • 需要通过程序控制实现,最糟糕,忽略。
       2.1.2、堆栈封闭
  • 堆栈封闭其实就是方法中定义局部变量。无并发问题。
  • 多个线程访问一个方法的时候,方法的局部变量都会拷贝一份在线程的栈中,所以局部变量是不会被多个线程共享的,因此不会出现并发问题。
       2.1.3、ThreadLocal线程封闭
  • 它是一个特别好的封闭方法。(建议研究源码)
  • ThreadLocal内部维护了一个map,map的key是每个线程的名称,而map的value就是我们要封闭的对象。
  • ThreadLocal提供了get、set、remove方法,每个操作都是基于当前线程的,所以它是线程安全的。

2.2、SpringBoot中使用ThreadLocal,LoginFilter实现登陆请求的拦截

       2.2.1、实现功能

              拦截请求并得到当前线程id并返回

       2.2.2、代码

           (1)创建一个包含ThreadLocal对象的类,并提供基础的添加、删除、获取操作。

public class RequestHolder {
    //Lang类型相当于线程id
    private final static ThreadLocal<Long> requestHolder = new ThreadLocal<>();

    //请求进入后端服务器,没有进行实际处理的时候调用这个方法。
    public static void add(Long id) {
        requestHolder.set(id);
    }

    public static Long getId() {
        return requestHolder.get();
    }

    //这个类一直存在,如果不能remove的话会出现内存泄漏
    public static void remove(){
        requestHolder.remove();
    }
}

           (2)创建Filter,在Filter中对ThreadLocal做添加操作。

@Slf4j
public class HttpFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        log.info("do filter , {} , {}",Thread.currentThread().getId(),request.getServletPath());
//        request.getSession().getAttribute("user");
        RequestHolder.add(Thread.currentThread().getId());
        //只做数据处理,不做拦截,把这个请求继续下去让这个请求继续处理
        filterChain.doFilter(servletRequest,servletResponse);
    }
    
    @Override
    public void destroy() {
    }
}

           (3)创建controller,在controller中获取到filter中存入的值

@Controller
@RequestMapping("/threadLocal")
public class ThreadLocalController {

    @RequestMapping("/test")
    @ResponseBody
    public Long test(){
        return RequestHolder.getId();
    }

}

           (4)创建拦截器Interceptor,在拦截器中删除刚才添加的值

@Slf4j
public class HttpInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        RequestHolder.remove();
        log.info("afterCompletion");
        return;
    }
}

           (5)在springboot的启动类Application中注册filter与Interceptor。要继承WebMvcConfigurerAdapter 类。(我这里的启动类名为:JavaBFApplication)

@Slf4j
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class JavaBFApplication extends WebMvcConfigurerAdapter {

    public static void main(String[] args) throws UnknownHostException {
        ConfigurableApplicationContext application = SpringApplication.run(JavaBFApplication.class, args);
        Environment env = application.getEnvironment();
        String ip = InetAddress.getLocalHost().getHostAddress();
        String port = env.getProperty("server.port");
        String path = env.getProperty("server.servlet.context-path");
        log.info("\n----------------------------------------------------------\n\t" +
                "Application Java并发 is running! Access URLs:\n\t" +
                "Local: \t\thttp://localhost:" + port + path + "/\n\t" +
                "External: \thttp://" + ip + ":" + port + path + "/\n\t" +
                "swagger-ui: \thttp://" + ip + ":" + port + path + "/swagger-ui.html\n\t" +
                "Doc: \t\thttp://" + ip + ":" + port + path + "/doc.html\n" +
                "----------------------------------------------------------");
    }

    @Bean
    public FilterRegistrationBean<HttpFilter> httpFilter(){
        FilterRegistrationBean<HttpFilter> registrationBean = new FilterRegistrationBean<HttpFilter>();
        registrationBean.setFilter(new HttpFilter());
        registrationBean.addUrlPatterns("/threadLocal/*");
        return registrationBean;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HttpInterceptor()).addPathPatterns("/**");
    }
}

三、线程不安全类与安全类

如果一个类的对象可以同时被多个线程访问,如果没有做同步或者并发处理就很容易表现出线程不安全的现象。

3.1、不安全类与对应的安全类

不安全类 安全类
StringBuilder StringBuffer
SimpleDateFormat JoadTime(更推荐DateTime)

3.2、安全类的使用

       3.2.1、StringBuffer
@Slf4j
public class StringExample2 {
    //请求总数
    public static int clientTotal = 5000;
    //同时并发执行的线程数
    public static int threadTotal = 200;
    //计数的值
    public static StringBuffer stringBuffer = new StringBuffer();

    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++) {
            //lambda表达式
            executorService.execute(() -> {
                try {
                    //引入信号量,不报错才执行add
                    semaphore.acquire();
                    update();
                    //释放进程
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                //执行一次就减一一次
                countDownLatch.countDown();
            });
        }
        //保证countDown减为0(执行完成)
        countDownLatch.await();
        //{}代表的是后面的那个值
        log.info("stringBuffer:{}", stringBuffer.length());
    }

    public static void update() {
        stringBuffer.append("1");
    }
}
       3.2.2、DateTime
@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;
            //lambda表达式
            executorService.execute(() -> {
                try {
                    //引入信号量,不报错才执行add
                    semaphore.acquire();
                    update(count);
                    //释放进程
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                //执行一次就减一一次
                countDownLatch.countDown();
            });
        }
        //保证countDown减为0(执行完成)
        countDownLatch.await();
    }

    public static void update(int i) {
        log.info("{} , {}",i,DateTime.parse("20190726",dateTimeFormatter).toDate());
    }
}

四、同步容器

4.1、同步容器使用

       4.1.1、ArrayList –> Vector,Stack

​ Vector是同步的类,不能直接说是线程安全

       (1)Vector的线程安全测试

@Slf4j
public class VectorExample {
    public static int clientTotal = 5000;
    public static int threadTotal = 200;
    public static Vector<Integer> list = new Vector<>();

    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;
            //lambda表达式
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        log.info("size {}",list.size());
    }

    public static void update(Integer i) {
        list.add(i);
    }
}

       (2)Vector的for循环操作测试

public class VectorExample2 {
    private static Vector<Integer> vector = new Vector<>();

    public static void main(String[] args) {
        while (true) {
            for (int i = 0; i < 10; i++) {
                vector.add(i);
            }
            Thread thread1 = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < vector.size(); i++) {
                        vector.remove(i);
                        System.out.println("1 " + vector.size());
                    }
                }
            };
            Thread thread2 = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < vector.size(); i++) {
                        vector.get(i);
                        System.out.println("2 " + vector.size());
                    }
                }
            };
            thread1.start();
            thread2.start();
        }
    }
}

       (2)Vector的iterator循环操作测试(会报错报错原因在 “4.2、同步容器操作需注意” 中)

@Slf4j
public class VectorExample3 {
    //这个方法会报错java.util.ConcurrentModificationException
    private static void test1(Vector<Integer> v1){
        for (Integer i:v1){
            if (i.equals(3)){
                v1.remove(i);
            }
        }
    }
    //这个方法会报错java.util.ConcurrentModificationException
    private static void test2(Vector<Integer> v1){
        //iterator
        Iterator<Integer> iterator = v1.iterator();
        while (iterator.hasNext()){
            Integer i = iterator.next();
            if (i.equals(3)){
                v1.remove(i);
            }
        }
    }

    //success
    private static void test3(Vector<Integer> v1){
        //for
        for (int i=0;i<v1.size();i++){
            if (v1.get(i).equals(3)){
                v1.remove(i);
            }
        }
    }

    public static void main(String[] args) {
        Vector<Integer> vector = new Vector<>();
        vector.add(1);
        vector.add(2);
        vector.add(3);
        //下面的这个调用会报错
        test1(vector);
        //下面的这个调用会报错
//        test2(vector);
        test3(vector);
        log.info(vector.toString());
    }
}
       4.1.2、HashMap -> HashTable(key、value不能为null)

HashTable进行了同步处理synchronized

@Slf4j
public class HashTableExample {
    public static int clientTotal = 5000;
    public static int threadTotal = 200;
    public static Map<Integer,Integer> map = new Hashtable<>();

    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;
            //lambda表达式
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        log.info("size {}",map.size());
    }

    public static void update(Integer i) {
        map.put(i,i);
    }
}
       4.1.3、Collections.synchroizedXXX(List、Set、Map)

操作与普通的集合类型几乎一样,只是初始化有差异

  • public static List list = Collections.synchronizedList(Lists.newArrayList());
  • public static Set list = Collections.synchronizedSet(Sets.newHashSet());
  • public static Map<Integer,Integer> map = Collections.synchronizedMap(Maps.newHashMap());

4.2、同步容器使用需注意

  • iterator和foreach遍历Vector的时候,尽量不要做remove相关的更新操作,如果一定要做,建议在遍历的时候发现需要删除的值,做好标记,在遍历完之后在做相关的remove操作,让这两步分开执行。
  • for循环中处理remove相关操作是可以的的,也推荐使用for循环对Vector进行相关的操作。如果Vector这个容器不行,其它的容器肯定也不行。
  • 多个线程同时对一个容器进行操作的时候,特别容易出现异常,多线程状态下的解决方案是在我们使用迭代器迭代的时候使用 sychroized或者lock做同步措施,同时也可以使用并发容器来代替这些容器。并发容器下一节讲。

4.3、并发容器基本使用

       4.3.1、ArrayList -> CopyOnWriteArrayList

实现:

  • CopyOnWriteArrayList线程安全,有内容添加进来的时候,它先从原有的数组拷贝数据出来,然后在新的数组上执行写操作,写完之后,再将原来的数组指向到新的数组,整个操作都是在锁的保护下执行的。
  • 主要是怕在操作的时候复制出多个副本出来把数据搞乱,导致最终的数组数据不是我们期望的。

缺点:

  1. 由于写操作的时候需要拷贝数组,就会消耗内存,如果数组元素比较多的情况下,可能会导致GC发生。
  2. 不能用于实时读的场景,拷贝数组,新增值等都需要时间,所以调用一个get的时候读取到的数据可能时旧的,虽然它能实现最终的实时性,但是还是没办法满足我们对实时性的要求,因此CopyOnWriteArrayList更适合读多写少的场景。注意如果数据过多的话代价会很高,慎用。通常多个线程共享的list不会很大,修改操作很少,绝大多数请求下可以代理。
  3. 读写分离,最终一致性,使用时另外开辟空间,避免并发冲突。它读数组都是在原数组上读的,不需要加锁,写操作时需要加锁,为了避免多个线程并发修改,复制出多个副本出来,会把数据搞乱。写操作是通过lock.lock()加的锁
@Slf4j
public class CopyOnWriteArrayListExample {
    public static int clientTotal = 5000;
    public static int threadTotal = 200;
    public static List<Integer> list = new CopyOnWriteArrayList<>();

    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;
            //lambda表达式
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        log.info("size {}",list.size());
    }

    public static void update(Integer i) {
        list.add(i);
    }
}
4.3.2、HashSet、TreeSet -> CopyOnWriteArraySet ConcurrentSkipListSet

       (1)CopyOnWriteArraySet

  • 线程安全,底层实现是跟上面的CopyOnWriteArrayList一样,所以也适合大小很小的set集合,迭代器不支持可变的remove操作。
@Slf4j
public class CopyOnWriteArraySetExample {
    public static int clientTotal = 5000;
    public static int threadTotal = 200;
    public static Set<Integer> set = new CopyOnWriteArraySet<>();

    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;
            //lambda表达式
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        log.info("size {}",set.size());
    }

    public static void update(Integer i) {
        set.add(i);
    }
}

       (2)ConcurrentSkipListSet

  • JDK6的类,支持自然排序,基于map集合,addremove都是线程安全的,对于addAllall操作并不保证原子性操作,只能保证每一次add都是原子性的,但是不一定不会被其它线程打断,做addAll等all操作还是要额外加一个锁才能保证并发安全性。
@Slf4j
@ThreadSafe
public class ConcurrentSkipListSetExample {
    public static int clientTotal = 5000;
    public static int threadTotal = 200;
    public static Set<Integer> set = new ConcurrentSkipListSet<>();

    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;
            //lambda表达式
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        log.info("size {}",set.size());
    }

    public static void update(Integer i) {
        set.add(i);
    }
}
       4.3.3、HashMap、TreeMap -> ConcurrentHashMap ConcurrentSkipListMap

       (1)ConcurrentHashMap

  • 不允许空值,除少数的插入操作和删除操作外绝大多数的都是读操作,而且读操作大多是情况下都是成功的,基于这个前提,针对读操作做了大量的优化,这个类在高并发下的表现非常好,也因为这个,所以这个经常会被面试问到,后面会对其进行很多介绍。
@Slf4j
public class ConcurrentHashMapExample {
    public static int clientTotal = 5000;
    public static int threadTotal = 200;
    public static Map<Integer,Integer> map = new ConcurrentHashMap<>();

    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;
            //lambda表达式
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        log.info("size {}",map.size());
    }

    public static void update(Integer i) {
        map.put(i,i);
    }
}

       (2)ConcurrentSkipListMap

  • 存储性能没有上面的ConcurrentHashMap好(四倍左右),但是它有几个上面的无法比拟的优点①key为有序的②支持更高的并发,存储时间和线程数几乎没有关系,线程数越多,优势越高。
  • connections的sync里面也包装了treeMap,效率也挺高
@Slf4j
public class ConcurrentSkipListMapExample {
    public static int clientTotal = 5000;
    public static int threadTotal = 200;
    public static Map<Integer,Integer> map = new ConcurrentSkipListMap<>();

    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;
            //lambda表达式
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        log.info("size {}",map.size());
    }

    public static void update(Integer i) {
        map.put(i,i);
}

五、安全共享对象策略

5.1、线程限制

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

5.2、共享只读

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

5.3、线程安全对象

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

5.4、被守护的对象

被守护的对象只能通过获取特定的锁来访问。

发布了20 篇原创文章 · 获赞 1 · 访问量 562

猜你喜欢

转载自blog.csdn.net/weixin_42295814/article/details/103789883