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

摘自:https://www.cnblogs.com/qing-gee/p/11802921.html

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

 

01、前言

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

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

请随我来,我尽量写得有趣点。

02、线程安全类

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

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。

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; } }

03、非线程安全的对象

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

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

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 的源码,你会发现,它之所以是线程安全的,就是采用的这种监视器模式

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

假如现在有一个线程安全的类,比如之前提到的 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 的。因此,这种做法不具有普适性。

05、最后

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

上一篇:Java 并发编程(三):如何保证共享变量的可见性?

上上篇:Java 并发编程(二):线程安全性

上上上篇:Java 并发编程(一):入门篇

谢谢大家的阅读,原创不易,喜欢就点个赞,这将是我最强的写作动力。如果你觉得文章对你有所帮助,也蛮有趣的,就微信搜索「沉默王二」,关注后回复关键字《Java 并发编程实战》,即可获取该书的电子版(推荐购买纸质书)。

微信扫描左侧二维码,关注作者的微信公众号:「 沉默王二
后台回复“ 免费视频”即可获取一份 500G 的高清教学视频,并且已经分门别类,可以按需下载,速去!

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】。
 
分类: 有趣Java
标签: Java, 并发编程

01、前言

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

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

请随我来,我尽量写得有趣点。

02、线程安全类

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

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。

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; } }

03、非线程安全的对象

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

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

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 的源码,你会发现,它之所以是线程安全的,就是采用的这种监视器模式

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

假如现在有一个线程安全的类,比如之前提到的 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 的。因此,这种做法不具有普适性。

05、最后

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

上一篇:Java 并发编程(三):如何保证共享变量的可见性?

上上篇:Java 并发编程(二):线程安全性

上上上篇:Java 并发编程(一):入门篇

谢谢大家的阅读,原创不易,喜欢就点个赞,这将是我最强的写作动力。如果你觉得文章对你有所帮助,也蛮有趣的,就微信搜索「沉默王二」,关注后回复关键字《Java 并发编程实战》,即可获取该书的电子版(推荐购买纸质书)。

猜你喜欢

转载自www.cnblogs.com/xichji/p/11802997.html
今日推荐