Java并发编程实战笔记(2)-对象的共享

本文介绍了并发编程中的关键概念,包括同步、可见性、重排序、Volatile关键字的作用、对象发布与逸出的问题、线程封闭技术、ThreadLocal的使用场景及不变性的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载请注明出处 http://www.paraller.com 原文排版地址 点击获取更好阅读体验

核心知识点

  • 同步不仅能够保证原子性; 还能实现内存可见性: 当一个线程修改了对象状态后,其他线程能够看到发生的状态变化

  • 如何让多个线程安全的访问可变状态 ?

可见性

重排序

``` class Test{ private static boolean visible; private static int number;

static class Reader extends Thread{
    void run(){
        while(!visible){
            Thread.yield();
        }
        system.out.println(number);
    }
}

static void main(String[]  args){
    new Reader().start();
    number = 12;
    visible = true;
}

} ```

Reader线程可能看到了visible的值,却没有看到number的,这种现象称为重排序 Jvm为了充分利用现代多核处理器,可能对操作的执行顺序进行调整

如何避免 : 只要有数据在线程之间共享,就使用正确的同步

Volatile
  • 变量不会被缓存在寄存器或者其他处理器不可见的地方,在读取valatile变量时总会返回最新写入的值
  • 变量声明为 volatile,编译器和运行时都会注意到这个变量是共享的,不会将该变量的操作与其他内存操作一起重排序

使用场景:

检查某个状态标记以判断是否退出循环:(因为所有线程修改对其他线程都是可见) while(!asleep){ // TODO }

发布与逸出

核心知识点
  • 发布的概念: 指对象能够在当前作用域之外的代码中使用.
  • 发布内部的状态,会破坏封装性,会影响线程安全
  • 逸出: 某个不应该被发布的对象被发布,就成为逸出(Escape)
  • 当对象在其构造函数中创建一个线程时,this引用都会被新创建的线程共享;解决方案:在构造函数中创建但不是马上启动它,而是通过一个方法来启动。
  • 避免逸出: 要返回一个对象A的时候,拷贝对应的值创建一个对象B,然后返回对象B。(可能在拷贝的时候A对象发生了改变,造成数据不一致,取决于需求是否这样使用)

逸出常见场景1: 将对象保存在共有的静态变量.

``` public static Set hello;

public void init(){ hello = new HashSet() } ```

逸出常见场景2: 非私有方法中返回一个引用.

``` class A{ private Object obj = new Object()''

public get Obj(){
    return this.obj;
}

} ```

线程封闭

核心知识点
  • 避免使用同步的方式就是不共享数据: 如果仅在单线程内访问数据,就不需要同步。
  • 线程封闭: 在数据封闭在一个线程中不共享
栈封闭

定义:栈封闭?其实就是把同步变量写在局部方法中,当然就线程安全了,根本不会共享变量。线程私有。

注意事项:确保栈内对象不会逸出

ThreadLocal

概念:ThreadLocal 提供了get /set等访问接口或方法,这些方法为每个使用该变量的线程都保存一份独立的副本, 因此Get方法总是返回由当前执行线程在调用set时设置的最新值。

使用ThreadLocal,能使线程中的某个值所有改变只在该当前线程中变动,不会被其他线程操作,保证了当前线程的数据不共享,避免竞态条件的发生。

场景: Spring中的事务上下文(Transaction Context),需要对当前线程进行 回滚、提交 操作,为了当前事务不会和其他线程串了,就通过将事务上下文与某个执行线程关联起来。

缺点: 耦合性会更强,并且降低代码的可重用性

不变性

核心知识点
  • 如果某个对象被创建之后就不能被改变(不变性条件是由构造函数创建的),就一定是线程安全的。

  • 即使对象中所有的域都是final类型的,这个对象仍然可能是可变的, 因为final类型的域可以保存对 可变对象的引用

  • 不可变对象需满足的条件,代码示例符合以下三个条件: 1、对象创建以后其所有状态都不能修改。(没有修改状态的方法) 2、对象的所有域都是final类型的。 3、对象是正确创建的(创建期间,this引用没有逸出)。

``` class Demo{ private final Object demoObj;

public Demo(Object obj){
    demoObj = obj;
}

public Object getObj(){
    return demoObj;
}

} ```

  • 保存在不可变对象中的程序状态仍然可以更新,即通过将一个保存新状态的实例来"替换"原有的不可变对象

``` class ImmutableObject{ private final BigDecimal bd;

ImmutableObject(String val){
    bd = new BigDecimal(val);
}

}

...

ImmutableObject io = new ImmutableObject("2"); io = new ImmutableObject("3");

```

io是不可变对象,但它的程序状态 bd仍然可以更新, 因为我创建了一个保存新状态的实例来替换原有的不可变对象io;

Final域
  • final类型的域是不能被修改的(但是所引用的对象可能是可变的)。
  • 除非需要更高的可见性,否则应该将所有的变量声明为私有的。
  • 除非需要某个域是可变的,否则变量应该声明为final类型的
  • 对于在访问和更新 多个相关变量时出现的竞态条件问题,可以通过将这些变量全部保存在一个不可变对象中来消除。

final类型的域初始化之后就不能更改赋值。但如果引用的对象是可变的,比如: public static final Set

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值