Skip to content

返回首页

* 同步模式之Balking

Balking (犹豫)模式用在一个线程发现另一个线程或本线程已经做了某一件相同的事,那么本线程就无需再做了,直接结束返回

java
public class MonitorService {
    // 用来表示是否已经有线程已经在执行启动了
    private volatile boolean starting;

    public void start() {
        log.info("尝试启动监控线程...");
        synchronized (this) {
            if (starting) {
                return;
            }
            starting = true;
        }
        //其实synchronized外面还可以再套一层if,
        //或者改为if(!starting),if框后直接return

        // 真正启动监控线程...
    }
}

当前端页面多次点击按钮调用 start 时 。

输出

java
[http-nio-8080-exec-1] MonitorService - 该监控线程已启动?(false)
[http-nio-8080-exec-1] MonitorService - 监控线程已启动...
[http-nio-8080-exec-2] MonitorService - 该监控线程已启动?(true)
[http-nio-8080-exec-3] MonitorService - 该监控线程已启动?(true)
[http-nio-8080-exec-4] MonitorService - 该监控线程已启动?(true)

它还经常用来实现线程安全的单例

java
public final class Singleton {
    
    private Singleton() {
    }
    
    private static Singleton INSTANCE = null;
    
    public static synchronized Singleton getInstance() {
        if (INSTANCE != null) {
            return INSTANCE;
        }
      
        INSTANCE = new Singleton();
        return INSTANCE;
    }
}

对比一下保护性暂停模式:保护性暂停模式用在一个线程等待另一个线程的执行结果,当条件不满足时线程等待。


balking 模式习题

希望 doInit() 方法仅被调用一次,下面的实现是否有问题,为什么?

java
public class TestVolatile {
    volatile boolean initialized = false;
    void init() {
        if (initialized) { 
            return;
        } 
        doInit();
        initialized = true;
    }
    private void doInit() {
    }
}

线程安全单例习题

单例模式有很多实现方法,饿汉、懒汉、静态内部类、枚举类,试分析每种实现下获取单例对象(即调用 getInstance)时的线程安全,并思考注释中的问题

饿汉式:类加载就会导致该单实例对象被创建

懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

java
// 问题1:为什么加 final(防止被子类继承从而重写方法改写单例)
// 问题2:如果实现了序列化接口, 还要做什么来防止反序列化破坏单例(重写readResolve方法)
public final class Singleton implements Serializable {
    // 问题3:为什么设置为私有? 是否能防止反射创建新的实例?(防止外部调用构造方法创建多个实例;不能)
    private Singleton() {}
    // 问题4:这样初始化是否能保证单例对象创建时的线程安全?(能,线程安全性由类加载器保障)
    private static final Singleton INSTANCE = new Singleton();
    // 问题5:为什么提供静态方法而不是直接将 INSTANCE 设置为 public, 说出你知道的理由(可以保证instance的安全性,也能方便实现一些附加逻辑)
    public static Singleton getInstance() {
        return INSTANCE;
    }
    public Object readResolve() {
        return INSTANCE;
    }
}
java
// 问题1:枚举单例是如何限制实例个数的 (枚举类会按照声明的个数在类加载时实例化对象)
// 问题2:枚举单例在创建时是否有并发问题(没有,由类加载器保障安全性)
// 问题3:枚举单例能否被反射破坏单例(不能)
// 问题4:枚举单例能否被反序列化破坏单例(不能)
// 问题5:枚举单例属于懒汉式还是饿汉式(饿汉)
// 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做(写构造方法)
enum Singleton { 
    INSTANCE; 
}
java
public final class Singleton {
    private Singleton() { }
    private static Singleton INSTANCE = null;
    // 分析这里的线程安全, 并说明有什么缺点(没有线程安全问题,同步代码块粒度太大,性能差)
    public static synchronized Singleton getInstance() {
        if( INSTANCE != null ){
            return INSTANCE;
        } 
        INSTANCE = new Singleton();
        return INSTANCE;
    }
}
java
public final class Singleton {
    private Singleton() { }
    // 问题1:解释为什么要加 volatile ?(防止putstatic和invokespecial重排导致的异常)
    private static volatile Singleton INSTANCE = null;

    // 问题2:对比实现3, 说出这样做的意义 (缩小了锁的粒度,提高了性能)
    public static Singleton getInstance() {
        if (INSTANCE != null) { 
            return INSTANCE;
        }
        synchronized (Singleton.class) { 
            // 问题3:为什么还要在这里加为空判断, 之前不是判断过了吗
            if (INSTANCE != null) { // t2 
                return INSTANCE;
            }
            INSTANCE = new Singleton(); 
            return INSTANCE;
        } 
    }
}
java
public final class Singleton {
    private Singleton() { }
    // 问题1:属于懒汉式还是饿汉式?实际上问的是类的加载机制
    private static class LazyHolder {
        static final Singleton INSTANCE = new Singleton();
    }
    // 问题2:在创建时是否有并发问题
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
}