温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Effective Java —— 并发篇 精华总结

发布时间:2020-08-10 10:40:00 来源:网络 阅读:310 作者:星恒Android 栏目:移动开发
同步访问共享的可变数据

将可变数据限制在单个线程中
当多个线程共享可变数据,每个读或者写的线程都必须执行同步
如下例,去除同步锁则会造成死循环

private static boolean stopRequested;
private static synchronized void requestStop() {
    stopRequested = true;
}

private static synchronized boolean stopRequested() {
    return stopRequested;
}

public static void main(String[] args) throws InterruptedException {
    Thread backgroundThread = new Thread(new Runnable() {
        @Override
        public void run() {
            int i = 0;
            while (!stopRequested()) {
                i++;
            }
        }
    });
    backgroundThread.start();
    TimeUnit.SECONDS.sleep(1);
    requestStop();
}
避免过度同步

多线程环境下,在观察者模式中使用CopyOnWriteArrayList或CopyOnWriteArraySet(并发场景常用),可以避免死锁,提高并发性。反之在遍历中去修改原有的集合,则可能会导致异常。
在同步区域内,做尽可能少的事情。

executor和task优先于线程

大负载服务器:Executors.newFixedThreadPool(),并发过高需要限制线程总数
轻量级程序:Executors.newCachedThreadPool(),自动处理多数情况。
替代Timer:Executors.newScheduledThreadPool(),更为准确,支持多线程,并且能异常恢复。

取代notify和wait

使用并发集合和同步器,例如ConcurrentMap,无需使用notify和wait。
间歇性计时优先使用System.nanoTime,它将更精确并免受系统时钟调整影响。
如果维护代码,优先使用notifyAll(),避免不相关线程恶意等待。

线程安全文档化

文档注释如下安全类型,而非简单用synchronized判断线程安全,需要注明调用时需要获得哪一把锁

1.不可变

不变的对象绝对是线程安全的,不需要线程同步,如String、Long、BigInteger

2.无条件的线程安全

对象自身做了 足够的内部同步,也不需要外部同步,如 Random 、ConcurrentHashMap、Concurrent集合、atomic

3.有条件的线程安全

对象的部分方法可以无条件安全使用,但是有些方法需要外部同步,需要Collections.synchronized;有条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器

4.非线程安全(线程兼容)

对象本身不提供线程安全机制,但是通过外部同步,可以在并发环境使用, 如ArrayList HashMap

5.线程对立

即使外部进行了同步调用,也不能保证线程安全,这种情况非常少,如如System.setOut()、System.runFinalizersOnExit()

慎用延迟初始化

大多数情况下使用正常初始化。
根据性能差距决定是否延迟初始化(懒加载)。当实例化开销很高并只在部分域访问的时候,可以考虑延迟初始化,同时在多线程下,需要考虑同步。
延迟初始化建议使用双重检查锁(效率比单重大概高25%),并且变量声明volatile。

private volatile FieldType field;
public FieldType getField() {
    FieldType result = field;
    if (result == null) {
        synchronized (this) {
            result = field;
            if (result == null) {
                field = result = computeFieldValue();
            }
        }
    }
    return result;
}

另一种方式是静态域延迟初始化,原理是静态内部类使用的时候才会进行初始化,并且无需使用同步。

private static class FieldHolder {
    static final FieldType field = computeFieldValue();
}

public static FieldType getField() {
    return FieldHolder.field;
}
不要依赖线程调度器

依赖线程调度器将使程序缺乏可移植性和健壮性,不依赖Thread.yield或者线程优先级。
程序原则是确保可运行线程平均数量不明显多于处理器数量。

避免使用线程组

线程组(ThreadGroup)没有提供它所提及的任何安全功能,不仅如此它提供的功能不完全而且有缺陷,请当它不存在吧。它的替代品是线程池。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI