Java 并发编程(四):如何保证对象的线程安全性

本篇来谈谈 Java 并发编程:如何保证对象的线程安全性。html

0一、前言

先让我吐一句肺腑之言吧,不说出来会憋出内伤的。《Java 并发编程实战》这本书太特么枯燥了,尽管它被奉为并发编程当中的经典之做,但我仍是忍不住。由于第四章“对象的组合”我整整啃了两周的时间,才啃出来点肉丝。java

读者朋友们见谅啊。要怪只能怪我本身的学习能力有限,真读不了这种生硬无趣的技术书。可是为了学习,为了进步,为了未来(口号喊得有点大了),只能硬着头皮上。编程

请随我来,我尽可能写得有趣点。浏览器

0二、线程安全类

做者说了啊,设计一个线程安全类须要三个步骤:安全

1)找出表示对象状态的全部变量
2)对变量进行有效性约束
3)增长类的并发访问策略并发

我在做者说的基础上作了微调,读起来更加容易理解。怎么和代码对应起来了,先来看一个普通的计数器类 Counter。学习

public class Counter {
    private int value = 0;

    public int getValue() {
        return value;
    }

    public int increment() {
        return ++value;
    }
}

1)Counter 的状态变量只有一个,就是 value。线程

2)value 的有效性是什么呢,它最大不能超过 Integer.MAX_VALUE,最小只能为 0(计数嘛,总不能记成负数)。换句话说就是,value 的有效范围是 0 ~ Integer.MAX_VALUE设计

public int increment() {
    if (value == Integer.MAX_VALUE) {
        throw new IllegalStateException("counter overflow");
    }
    return ++value;
}

3)增长类的并发访问策略,直接上 synchronized。code

public class Counter {
    private int value = 0;

    public synchronized int getValue() {
        return value;
    }

    public synchronized int increment() {
        if (value == Integer.MAX_VALUE) {
            throw new IllegalStateException("counter overflow");
        }
        return ++value;
    }
}

0三、非线程安全的对象

以前咱们谈了如何设计一个线程安全的类。若是类是安全的,那么它做为对象使用的时候就是线程安全的。但若是一个类不是线程安全的,它做为对象使用的时候怎么保证是线程安全的呢?

做者提到了一个名词叫作“封闭机制”:

1)把对象做为类的私有成员变量;
2)把对象做为方法内部的局部变量;
3)线程 A 把对象传递到 B 线程,而不是与线程 B 共享这个对象;

你们来看下面这段代码。

class StringList {
    private List<String> myList = new ArrayList<>();
    
    public synchronized void addString(String s) {
        myList.add(s);
    }
    
    public synchronized void removeString(String s) {
        myList.remove(s);
    }
}

自己 ArrayList 不是线程安全的,但 myList 是私有的,访问它的两个方法 addString()removeString() 都加了关键字 synchronized,所以 myList 在使用的时候就变成了线程安全的对象,StringList 类就变成了一个线程安全的类——这种方式被称做 Java 监视器模式:可变的状态被封装在一个类中,访问它们只能经过加上锁的方法。

查看 Vector 的源码,你会发现,它之因此是线程安全的,就是采用的这种监视器模式

0四、在已有的线程安全类上追加功能

假如如今有一个线程安全的类,好比以前提到的 StringList,它包含了大多数咱们须要的功能,但还不够,那么怎么确保咱们追加的功能不破坏原有的线程安全性呢?

最直接的方法固然是修改源码,假如源码掌握在咱们本身手里的话。

class StringList {
    private List<String> myList = new ArrayList<>();
    
    public synchronized void addString(String s) {
        myList.add(s);
    }
    
    public synchronized void addIfNotExist(String s) {
        boolean isExist = myList.contains(s);
        if (!isExist) {
            myList.add(s);
        }
    }
}

咱们新增了一个 addIfNotExist() 方法:若是字符串 s 尚未添加到 List 当中,就添加一个。

新增的方法没有破坏 StringList 的线程安全性,由于当两个线程同时执行 addIfNotExist() 方法时,须要通过 synchronized 把守的这道大门。

但不少时候,咱们没法直接修改源码,这时候就只好在原来的基础上进行改造。你们听过以前的“红芯”浏览器吗?在谷歌浏览器的内核上裹了一层层皇帝的新衣。

class StringList {
    protected List<String> myList = new ArrayList<>();
    
    public synchronized void addString(String s) {
        myList.add(s);
    }
}

public class NewStringList extends StringList {
    public synchronized void addIfNotExist(String s) {
        boolean isExist = myList.contains(s);
        if (!isExist) {
            myList.add(s);
        }
    }
}

新建一个类 NewStringList,继承自 StringList,而后在 NewStringList 中新增一个方法 addIfNotExist()。固然了,这样作的前提是父类中的 myList 是 protected 而不是 private 的。所以,这种作法不具备普适性。

0五、最后

站在个人角度来看,《Java 并发编程实战》的第四章“对象的组合”写得烂透了。致使我在写这篇文章的时候感受到万分的痛苦。但愿下一章不要写的这么烂。

上一篇:如何保证共享变量的可见性?

上上篇:如何保证共享变量的原子性?

相关文章
相关标签/搜索