线程学习(27)-不可变类

什么是不可变类?

不可变类为什么是线程安全的?

如果一个对象在不能修改其内部状态时,就代表了当前类不会存在对内部属性进行写的情况,也就是说它是线程安全的。

package com.bo.threadstudy.seven;

import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;

@Slf4j
public class DateFormatTest {



    //测试时间类的不可见性
    public static void main(String[] args) {
        //使用SimpleDateFormat报错
        //extracted();

        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                TemporalAccessor parse = dateTimeFormatter.parse("1951-02-04");
                log.debug("{}",parse);
            }).start();
        }

//线程安全,是多个线程改变某个字段时,造成的数据异常,不可变类是创建的新对象,保护性拷贝,也就没有线程安全的问题,其实就是不可能出现修改操作
        //线程安全,是对同一个对象使用多线程修改时的问题,所以不可变类可以控制线程安全。
        //虽然指令交错的现象还会发生,但是因为不会修改原有数据,所以是线程安全的,如果想控制执行顺序或者结果,采用CAS或者synchroonized,reetrantlock的方式
//        BigDecimal bigDecimal = new BigDecimal("10000");
//
//        for (int i = 0; i < 10; i++) {
//            new Thread(() -> {
//                BigDecimal subtract = bigDecimal.subtract(new BigDecimal("1000"));
//                log.debug(subtract.toString());
//            }).start();
//        }

    }

    private static void extracted() {
        //这种方式报错
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                Date format = null;
                try {
                    format = simpleDateFormat.parse("1951-04-21");
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                log.debug("{}",format);
            }).start();
        }
    }

}

因为我们平时接触的线程不安全,都是在多个线程进行操作时,并发对内部元素进行修改,发生了指令交错,最后这个内部元素被修改的出现了问题。

而不可变类,之所以不可变,是因为内部元素或者类,或者方法都设置为final类型,就拿属性举例,final修饰了,基础类型就不可能发生并发修改的情况。而引用类型,虽然可能会改变final所修饰对象的成员变量。但是在不可变类中,会有一个保护性拷贝的方式,让最终的结果是一个新拷贝的结果,而无法被多个线程并发修改。

拿String来举例吧,看看保护性拷贝,就像SubString方法,以及构造方法。

String的构造方法可以传入一个char数组。

这里采用了拷贝的方式,为什么要拷贝一下?这种就是保护性拷贝。因为String既然设计成不可变类,那么这个数组中的元素是不可以被改变的。如果只是赋值的话,这种赋值方式是一种浅拷贝的过程。假如我在外部对这个数组造成改变,那么String内部的数组也会发生改变,不可变就被破坏掉了。所以这里在构造函数中采用了保护性拷贝的方式。

 

 substring方法则是new了个的字符串。也好理解,假如多个线程来进行操作时,相当于创建了两个新的字符串。虽然发生了指令交错,但并没有出现并发写的情况。也是保护性拷贝的一种方式,是线程安全的。

我原先还写了段代码,不可变类虽然是线程安全的,但要明白线程安全的概念,多个线程对共享数据发生并发写的情况造成数据异常,这种才是线程不安全。线程安全,不可变类确实满足线程安全的条件,没有对共享数据发生并发写操作。但是,不代表了结果就是对的。

BigDecimal虽然是线程安全的,不可变类。但是如果多个线程对该对象进行操作时,所有的线程取的都是10000这个数,最终都是9000。假如想让并发操作有序进行,还是得配合AtomicReference来进行使用。

注意线程安全的概念,不是说某个类线程安全了,那么多个线程操作同一对象的某个方法时,就什么控制都不需要做了。就像synfhronized,Reetrantlock以及CAS修饰的这种线程安全不控制也不会出现问题。不可变类是个例外,得控制执行顺序。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值