《java并发编程实战》之 对象共享

解决问题:
如何共享和发布对象,从而使它们能够安全地由多个线程同时访问

写多线程注意两点

  1. 防止某个线程正在使用对象状态时,而另一个线程同时在修改状态。
  2. 确保当一个线程修改了对象状态后,其他线程能够看到状态变化。(同步的内存可见性)

1.可见性

错误写法

public class NoVisibity {
    private static boolean ready=false;
    private static int number=0;

    private static class ReadyThread extends Thread{
        public void run(){
            while(!ready){
                Thread.yield(); //去掉情况也是一样。
                System.out.println(number);
            }
        }
    }

    public static void main(String[] args) {
        new ReadyThread().start();
        number = 42;
        ready = true;
    }
}

大部分情况不会输出任何值。

原因:从代码逻辑上看到是设置了number=42, 应该输出42,然后ready=true,停止输出,但事实上我们不能保证线程中的操作按照指定的顺序执行,当主线程在没有同步的情况下,写入了number,然后又写入ready,那么读现车给你看到的顺序可能与写入的相反,所以没有数据输出。


多个线程之间数据共享时,同步很重要。
Java线程中的Thread.yield()方法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。
    
yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

举个例子:一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后所有人就一块冲向公交车,有可能是其他人先上车了,也有可能是Yield先上车了。但是线程是有优先级的,优先级越高的人,就一定能第一个上车吗?这是不一定的,优先级高的人仅仅只是第一个上车的概率大了一点而已,最终第一个上车的,也有可能是优先级最低的人。并且所谓的优先级执行,是在大量执行次数中才能体现出来的。

使用volatile原则:

  • 对变量的写入不依赖变量当前值,如:count++ 这种骚操作不可以。除非保证单线程更新变量值
  • 访问变量不需要加锁,因为volatile只是保证可减刑,不保证原子性
  • 该变量不会与其他状态变量一起纳入不变性条件中

2.发布和逸出

2.1发布

发布一个对象,也就是让对象以外作用域的代码可以使用。
弊端:发布对象内部状态可能会破坏线程的安全性 如:未完成构造对象之前就进行发布

public static Set<Secret> knowSecrets;    //静态公共的容器,每个线程都可以访问
public void initialize(){
    knowSecrets = new HashSet<Secret>();
}

2.2 逸出

发布了不该发布的对象就是逸出。
在构造方法中,内部类机制的发布,存在把本类this逸出

正确的防止逸出

1.私有的构造方法 和 公共的工厂方法,避免了不正确的构造过程
例如

public class SafeListener{
    private final EventListener listener;
    public SafeListener(){
        listener = new EventListener(Event e){
            doSomtthing(e);
        };
    }
    
    public static SafeListener newInstance(EventSource source){
        SafeListener safe = new SafeListener();
        source.registerlistener(safe.listener);
        return safe;
    }
}




this隐式逸出,在构造方法中注册方法,构造方法会获得this,共享给其他线程是很危险的。
public class ThisEscape{
    public ThisEscape(EventSource source){
        source.registerListener(){
            new EventListener(){
                public void onEvent(Event e){
                    doSomething();
                }    
            }
        }
    }
}

3.线程封闭

仅在单线程内访问可变数据,就是线程封闭

ThreadLocal类 为每个使用某个变量的线程保存了一份独立的副本,get() set()方法可以获得这个设置副本的值。

private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>{
    public Connection initialValue(){
        return DriverManager.getConnection()DB_URL;
    }
}

public static Connection getConnection(){
    return connectionHolder.get();
}


线程通过调用getConnection(), 每个线程获得属于自己的一个connection副本,这些值是保存在线程中的,线程终止后,就会垃圾回收。

猜你喜欢

转载自blog.csdn.net/sinat_24230393/article/details/84332313