《Java并发编程实战》第三章:对象共享机制深度解析

《Java并发编程实战》第三章:对象共享机制深度解析

【免费下载链接】booknotes A collection of my book notes on various computer science books 【免费下载链接】booknotes 项目地址: https://gitcode.com/gh_mirrors/bo/booknotes

引言:多线程环境下的可见性挑战

在单线程环境中,当向变量写入值后,可以立即读取该值。但在多线程环境中,如果没有使用适当的同步机制,无法保证一个线程对变量的修改能够被其他线程看到。这种可见性问题(Visibility)是Java并发编程中最微妙且容易忽视的问题之一。

public class NoVisibility {
    private static boolean ready;
    private static int number;
    
    private static class ReaderThread extends Thread {
        public void run() {
            while (!ready)
                Thread.yield();
            System.out.println(number);
        }
    }
 
    public static void main(String[] args) {
        new ReaderThread().start();
        number = 42;
        ready = true;
    }
}

上述程序可能产生三种结果:程序永不终止、输出0,或正确输出42。JVM在后台进行的优化(如重排序、变量常量化等)是导致这种现象的根本原因。

内存可见性机制深度剖析

1. 陈旧数据(Stale Data)问题

陈旧数据是指线程读取到的值是过时的数据,这可能导致数据结构损坏、计算不准确甚至无限循环。

mermaid

2. 64位非原子操作的特殊性

对于64位变量(如long类型),如果没有同步或volatile关键字,不仅可能读取到陈旧数据,还可能读取到"撕裂值"(torn read)——高32位来自一个值,低32位来自另一个值。

volatile关键字的正确使用

volatile是Java中一种轻量级的同步机制,它只保证可见性,不保证原子性。

// 正确使用volatile的场景
public class StatusFlag {
    private volatile boolean shutdownRequested;
    
    public void shutdown() { shutdownRequested = true; }
    public boolean isShutdownRequested() { return shutdownRequested; }
}

使用volatile变量的条件:

  • 写入操作不依赖于变量的当前值
  • 变量不参与与其他状态变量的复合操作
  • 访问变量时不需要其他原因的锁保护

对象发布与逃逸(Publication & Escape)

对象发布的三种危险方式

// 1. 通过公共静态字段发布
public static Set<Secret> knownSecrets;

// 2. 从非私有方法返回私有字段引用
class UnsafeStates {
    private String[] states = {"AK", "AL"};
    public String[] getStates() { return states; } // 危险!
}

// 3. 发布内部类引用
public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(new EventListener() {
            public void onEvent(Event e) {
                doSomething(e); // 可以访问外部类的this引用
            }
        });
    }
}

安全构造实践

避免在构造过程中让this引用逃逸的正确做法:

public class SafeListener {
    private final EventListener listener;
    
    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        };
    }
 
    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }
}

线程限制(Thread Confinement)策略

栈限制(Stack Confinement)

public class AnimalPairs {
    // 线程安全的方法 - 使用栈限制
    public int countPairs(List<Animal> animals) {
        // animalsCopy被限制在当前线程的栈中
        List<Animal> animalsCopy = new ArrayList<>(animals);
        int pairs = 0;
        
        for (int i = 0; i < animalsCopy.size(); i++) {
            for (int j = i + 1; j < animalsCopy.size(); j++) {
                if (animalsCopy.get(i).isPotentialMate(animalsCopy.get(j)))
                    pairs++;
            }
        }
        return pairs;
    }
}

ThreadLocal的应用

// 使用ThreadLocal维护线程特定的数据库连接
public class ConnectionManager {
    private static ThreadLocal<Connection> connectionHolder = 
        new ThreadLocal<Connection>() {
            @Override
            protected Connection initialValue() {
                return DriverManager.getConnection(DB_URL);
            }
        };

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

不可变性(Immutability)设计模式

不可变对象的定义条件

一个对象是不可变的,当且仅当:

  1. 创建后其状态不能被修改
  2. 所有字段都是final的
  3. 正确构造(this引用在构造期间没有逃逸)
@Immutable
public final class ThreeStooges {
    private final Set<String> stooges = new HashSet<>();
    
    public ThreeStooges() {
        stooges.add("Moe");
        stooges.add("Larry");
        stooges.add("Curly");
    }

    public boolean isStooge(String name) {
        return stooges.contains(name);
    }
}

final字段的特殊保证

final字段具有"初始化安全性"保证,这是Java内存模型为不可变对象提供的特殊保障。

mermaid

安全发布(Safe Publication)机制

安全发布惯用法

对象被正确发布的条件:

  • 从静态初始化器初始化
  • 引用存储到volatile变量或AtomicReference中
  • 引用存储到正确构造对象的final字段中
  • 引用存储到由锁正确保护的字段中

可变对象的安全发布模式

// 使用volatile发布不可变对象
@ThreadSafe
public class VolatileCachedFactorizer implements Servlet {
    private volatile OneValueCache cache = new OneValueCache(null, null);

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = cache.getFactors(i);
        
        if (factors == null) {
            factors = factor(i);
            cache = new OneValueCache(i, factors); // 安全发布
        }
        encodeIntoResponse(resp, factors);
    }
}

@Immutable
class OneValueCache {
    private final BigInteger lastNumber;
    private final BigInteger[] lastFactors;

    public OneValueCache(BigInteger i, BigInteger[] factors) {
        lastNumber = i;
        lastFactors = Arrays.copyOf(factors, factors.length);
    }

    public BigInteger[] getFactors(BigInteger i) {
        if (lastNumber == null || !lastNumber.equals(i))
            return null;
        return Arrays.copyOf(lastFactors, lastFactors.length);
    }
}

对象共享安全策略总结

对象类型发布要求访问要求典型应用场景
线程限制对象无特殊要求只能在拥有线程中修改局部变量、ThreadLocal
共享只读对象安全发布无需同步不可变对象、配置数据
共享线程安全对象安全发布无需额外同步ConcurrentHashMap、AtomicInteger
受保护对象安全发布需要持有特定锁自定义同步对象

最佳实践与性能考量

1. 优先使用不可变对象

// 使用不可变对象避免同步
public class ImmutablePoint {
    private final int x;
    private final int y;
    
    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public int getX() { return x; }
    public int getY() { return y; }
    
    // 返回新实例而不是修改现有实例
    public ImmutablePoint move(int deltaX, int deltaY) {
        return new ImmutablePoint(x + deltaX, y + deltaY);
    }
}

2. 有效不可变对象的使用

// 将可变对象转换为有效不可变对象
public class EffectiveImmutable {
    private final Map<String, String> config;
    
    public EffectiveImmutable(Map<String, String> config) {
        // 防御性复制
        this.config = Collections.unmodifiableMap(new HashMap<>(config));
    }
    
    public String getConfig(String key) {
        return config.get(key);
    }
}

3. 性能优化策略

mermaid

结论与实战建议

Java并发编程中的对象共享机制是一个需要精心设计的领域。通过理解可见性、安全发布、线程限制和不可变性等核心概念,可以构建出既正确又高效的并发程序。

关键要点总结:

  1. 可见性优先:总是假设多线程环境下的可见性问题会发生
  2. 安全发布:确保对象在发布时的状态一致性
  3. 不可变性:尽可能使用不可变对象简化并发控制
  4. 明确策略:为每个共享对象选择明确的线程安全策略

通过遵循这些原则和实践,可以显著降低并发程序的复杂度,提高代码的可维护性和性能。记住,在并发编程中,预防问题远比修复问题更重要。

【免费下载链接】booknotes A collection of my book notes on various computer science books 【免费下载链接】booknotes 项目地址: https://gitcode.com/gh_mirrors/bo/booknotes

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值