并发编程实战读书笔记(二)对象的共享


并发编程实战读书笔记(二)对象的共享
本章内容:
1、内存可见性
2、线程封闭
3、不变性
4、安全发布

一、内存可见性
1、线程可见性引入
下面代码 中number值可能打印42,也有可能是0 重排序时!
public class Novisibility {

    private static boolean ready;
    private static int number;
    
    private static class ReadThread extends Thread{
        @Override
        public void run() {
            while(!ready){
                Thread.yield();
            }
            System.out.println(number);
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 20; i++) {
            new ReadThread().start();
        }
        ready = true;
        number = 42;
    }
}
还有一个例子 双重检测锁实现单例模式 时有时会出现意外(实际其他线程已经创建,但是个别线程读到的还是null 重复创建破坏了单例) 这也是为什么要加volitile修饰的原因
线程可见性可以解决此类问题:比如使用Volitile、比如get、set方法都使用synchronizedx修饰

2、加锁与可见性

Volitile是Java的一种稍弱的同步机制,用来确保变量的更新通知到其他线程。
然而不建议过度依赖Volitile提供的可见性,因为它比锁更脆弱,也更难以理解。
volitile一般使用方式包括确保自身状态的可见性,确保所引用对象的可见性,以及标识一些重要的生命周期事件的发生。
使用示例:其他线程通过改变flag可以控制这个线程的运行

public class RuntimeThread extends Thread{

    public static volatile AtomicBoolean flag = new AtomicBoolean(true);;
    @Override
    public synchronized void start() {
        
        while(flag.get()){
            
            System.out.println("do something");
        }
    }
    
}
局限:Volitile只能保证线程可见。做不到原子性

二、线程封闭
就是将对象封闭在线程内部来做到线程安全
1、Ad-hoc线程封闭
2、栈封闭:局部变量
3、ThreadLocal:创建变量副本来保证线程安全。理解:Map<Thread,T> 线程终止时值会回收。
 

三、不变性。

不可变对象是线程安全的。使用不可变对象Immutable Object。
满足以下几点对象才是不可变的:对象创建以后状态就不能修改、对象所有域为final、对象是正确创建的(创建过程中this没有逸出)
例:
public class ImmutableClass {

    private final Set<String> sets = Sets.newHashSet();
    
    public ImmutableClass(){
        sets.add("back");
        sets.add("back1");
        sets.add("back2");
    }
    public boolean contains(String name){
        return sets.contains(name);
    }
}
注:Final域代表不能修改的意思。Final域所引用的对象是可变的,那么这些被引用的对象是可以修改的。

Volite 修饰不可变对象可以做到线程安全

四、安全发布
对象发布的线程安全,例子:对象在A线程初始化,在B线程读取可能读不到正确的对象的状态
解决方式(安全发布对象):
1)即使发布不可变对象没有使用同步也可以安全的访问对象
2)静态初始化函数中初始化一个对象引用
3)对象引用保存在Volatile或AtomicReferance中
4)对象引用保存在锁保护的域中(双重检测锁)

在线程安全容器内部同步代表比如A线程初始化后将容器放入容器比如Vector,B线程读取容器中的元素。那么可以确保B读到对象的状态,即便代码中没有包含显示的同步。
Hashtable,synchronizedMap,ConcurrentMap、Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、BlockingQueue、ConcurrentLinkedQueue这些都可以。
还有一些其他数据传递机制Future、Exchanger也能实现安全发布

5)事实不可变对象
首先安全发布只能保证对象状态的一致,不能保证对象应用的线程安全。
当一个可变对象在发布后不被修改,那么这个对象就是事实上的不可变对象。安全发布这种对象后就直接使用就能保证线程安全。

6)可变对象
如果对象在构造后可以修改。安全发布只能确保"发布当时" 的状态可见。对于可变对象,不仅要保证安全发布,还要同步来保证后续修改操作的可见性。

此时对象发布取决于可变性:
不可变对象可以通过任意机制来发布
事实不可变对象必须通过安全的方式来发布
可变对象必须安全的方式发布,并且必须是线程安全的或者某个锁保护起来

7)安全的共享对象。
并发程序中使用和共享对象时,可以使用一些实用的策略:
线程封闭:线程封闭对象只有当前线程持有,且只有当前线程修改。
只读共享:在没有额外同步的情况下,共享只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象
线程安全共享:线程安全的对象在内部实现同步,因此多个线程可以通过对象的共有接口访问,而不需要进一步同步。
保护对象:被保护的对象只能通过持有特定的锁来访问。保护对象也包括封装在其他线程安全对象中的对象。以及已经发布并且由某个特定的锁保护的对象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章