Java 多线程设计模式之 Single Threades Execution

Single Threades Execution 模式

所谓 Single Threades Execution 模式,意即“以一个线程执行”。就像独木桥同一时间内只允许一个人通行一样,该模式用于设置限制,以确保同一时间内只能让一个线程执行处理。

Demo

不使用 Single Threades Execution 模式的程序

使用程序模拟三个人频繁地通过一个只允许一个人经过的门情形。当人们通过门的时候,统计人数便会递增。另外程序还会记录通行者的“姓名和出生地”

类一览表

名字说明

Main创建门,并让三个人不断地通过的类

Gate表示门的类。它会在人们通过门时记录其姓名与出生地

UserThread表示人的类。人们不断地通过门

// Main.javapublicclassMain{publicstaticvoidmain(String[] args){        Gate gate =newGate();newUserThread(gate,"Bob","Britain").start();newUserThread(gate,"Cao","China").start();newUserThread(gate,"Uber","USA").start();    }}复制代码

// Gate.javapublicclassGate{privateintcounter =0;privateString name ="Nobody";privateString address ="NoWhere";publicvoidpass(String name, String address){this.counter++;this.name = name;this.address = address;        check();    }privatevoidcheck(){if(this.name.charAt(0) !=this.address.charAt(0)) {            System.out.println("******** BROKEN ********** : "+ toString());        }    }@OverridepublicStringtoString(){return"No. "+this.counter +" : "+this.name +" , "+this.address;    }}复制代码

// UserThread.javapublicclassUserThreadextendsThread{privatefinalGate gate;privatefinalString name;privatefinalString address;publicUserThread(Gate gate, String name, String address){this.gate = gate;this.name = name;this.address = address;    }@Overridepublicvoidrun(){        System.out.println(this.name +" BEGIN");while(true) {            gate.pass(this.name,this.address);        }    }}复制代码

当这个程序执行时,时间点不同,生成的结果也会不一样,以下是打印出来的 log

Bob BEGINCao BEGIN******** BROKEN ********** : No. 59622 : Bob , BritainUber BEGIN******** BROKEN ********** : No. 77170 : Uber , USA******** BROKEN ********** : No. 89771 : Uber , USA******** BROKEN ********** : No. 93128 : Cao , China******** BROKEN ********** : No. 95654 : Uber , USA******** BROKEN ********** : No. 98440 : Cao , China******** BROKEN ********** : No. 102283 : Cao , China******** BROKEN ********** : No. 104491 : Cao , China******** BROKEN ********** : No. 106791 : Uber , USA******** BROKEN ********** : No. 110022 : Uber , USA******** BROKEN ********** : No. 112073 : Uber , USA******** BROKEN ********** : No. 113973 : Uber , USA******** BROKEN ********** : No. 77170 : Uber , USA******** BROKEN ********** : No. 116050 : Bob , China******** BROKEN ********** : No. 117334 : Bob , Britain******** BROKEN ********** : No. 119992 : Bob , USA******** BROKEN ********** : No. 124427 : Uber , USA******** BROKEN ********** : No. 117152 : Bob , Britain******** BROKEN ********** : No. 129298 : Bob , China******** BROKEN ********** : No. 130552 : Cao , Britain******** BROKEN ********** : No. 147176 : Cao , China******** BROKEN ********** : No. 148546 : Uber , USA复制代码

通过 log 可以知道运行结果与预期不一致,所以说 Gate 类是不安全的,是非线程安全类。

如果仔细看一下 counter 的值,最开始显示 BROKEN 的时候,counter 的值已经变为了 59622。也就是说,在检察处第一个错误的时候 Gate 的 pass 方法已经运行了 5 万多次了。在这里,因为 UserThread 类的 run 方法执行的是无限循环,所以才检查除了错误。但是如果只测试几次,是根本找不出错误的。

这就是多线程程序设计的难点之一。如果检察出错误,那么说明程序并不安全。但是就算没有检察出错误,也不能说程序就一定是安全的。

调试信息也不可靠

仔细看 log 会发现还有一个奇怪的现象,比如:

******** BROKEN ********** : No. 59622 : Bob , Britain复制代码

虽然此处输出了 BROKEN 信息,但是姓名和出生地首字母是一样的。尽管显示了 BROKEN,但是调试信息好像并没有错。

导致这种现象的原因是,在某个线程执行 check 方法时,其他线程不断执行 pass 方法,改谢了 name 字段和 address 字段的值。

这也是多线程程序设计的难点之一。如果显示调试信息的代码本身就是非线程安全的,那么显示的调试信息就很可能是错误的。

如果连操作测试和调试信息都无法确保安全性,那就进行代码评审吧。多个人一起仔细阅读代码,确认是否会发生问题,这是确保程序安全性的一个有效方法。

修改 Gate 类使其线程安全

// Gate.javapublicclassGate{    ...publicsynchronizedvoidpass(String name, String address){this.counter++;this.name = name;this.address = address;        check();    }        ...}复制代码

之后程序就可以正常的运行,也不在打印 BROKEN 的 log 信息了

Single Threaded Execution 模式归纳

SharedResource 共享资源

在刚才的示例中,Gate 类扮演 SharedResource 的角色

SharedResource 角色是可被多个线程访问的类,包含很多方法,但这些方法主要分为如下两类:

safeMethod: 多个线程同时调用也不会发生问题的方法

unsafeMethod:多个线程同时调用会发生问题,因此必须加以保护的方法

而 unsafeMethod 在被多个线程同时执行时,实例状态有可能发生分歧。这时就需要保护该方法,使其不被多个线程同时访问。Java 则是通过将 unsafeMethod 声明为 synchronized 方法来进行保护

死锁

在该模式下,满足下列条件时,死锁就会发生

存在多个 SharedResource 角色

线程在持有着某个 SharedResource 角色锁的同时,还想获取其他 SharedResource 角色的锁

获取 SharedResource 角色的锁的顺序并不固定

原子操作

不可分割的操作通常称为原子操作。

上述示例中 Gate类是线程安全的 我们将 pass 声明为了 synchronized 方法,这样 pass 方法也就成为了原子操作

Java 编程规范中定义了一些原子操作。例如 char、int 等基本类型的赋值和引用操作都是原子的。另外,对象等引用类型的赋值和引用操作也是原子的。由于本身就是原子的,所以就算不加上 synchronized,这些操作也不会被分割。但是 long、double 的赋值和引用操作并不是原子的

总结如下:

基本类型、引用类型的赋值和引用是原子操作

但 long 和 double 的赋值和引用是非原子操作

long 或 double 在线程间共享时,需要将其放入 synchronized 中操作,或者声明为 volatile

计数信号量和 Semaphore 类

上面介绍 Single Threaded Execution 模式用于确保某个区域“只能由一个线程”执行。下面我们将这种模式进一步扩展,以确保某个区域“最多只能由 N 个线程”执行。这时就要用计数信号量来控制线程数量。

java.util.concurrent 包提供了表示计数信号量的 Semaphore 类

资源的许可个数将通过 Semaphore 的构造函数来指定

Semaphore 的 acquire 方法用于确保存在可用资源。当存在可用资源时,线程会立即从 acquire 方法返回,同时信号量内部的资源个数会减 1 。 如无可用资源,线程阻塞在 acquire 方法内,直至出现可用资源。

Semaphore 的 release 方法用于释放资源。释放资源后,信号量内部的资源个数会增加 1。另外如果 acquire 中存在等待的线程,那么其中一个线程会被唤醒,并从 acquire 方法返回。

示例

// BoundedResource.javapublicclassBoundedResource{privatefinalintpermits;privatefinalSemaphore semaphore;privatefinalRandom random =newRandom(314159);publicBoundedResource(intpermits){this.semaphore =newSemaphore(permits);this.permits = permits;    }publicvoiduse()throwsInterruptedException{try{this.semaphore.acquire();            doUse();        }finally{this.semaphore.release();        }    }privatevoiddoUse()throwsInterruptedException{        System.out.println(Thread.currentThread().getName() +" : BEGIN used = "+ (this.permits -this.semaphore.availablePermits()));        Thread.sleep(this.random.nextInt(500));        System.out.println(Thread.currentThread().getName() +" : END used = "+ (this.permits -this.semaphore.availablePermits()));    }}复制代码

// SemaphoreThread.javapublicclassSemaphoreThreadextendsThread{privatefinalRandom random =newRandom(26535);privatefinalBoundedResource resource;publicSemaphoreThread(BoundedResource resource){this.resource = resource;    }@Overridepublicvoidrun(){try{while(true) {this.resource.use();                Thread.sleep(this.random.nextInt(2000));            }        }catch(InterruptedException e) {            e.printStackTrace();        }    }}复制代码

// Main.javapublicclassMain{publicstaticvoidmain(String[] args){        BoundedResource boundedResource =newBoundedResource(3);newSemaphoreThread(boundedResource).start();newSemaphoreThread(boundedResource).start();newSemaphoreThread(boundedResource).start();    }}复制代码

打印结果:

Thread-0 : BEGIN used = 2

Thread-2 : BEGIN used = 3

Thread-1 : BEGIN used = 2

Thread-2 : END used = 3

Thread-1 : END used = 2

Thread-0 : END used = 1

Thread-2 : BEGIN used = 1

Thread-2 : END used = 1

Thread-1 : BEGIN used = 1

Thread-0 : BEGIN used = 2

Thread-1 : END used = 2

Thread-0 : END used = 1

Thread-2 : BEGIN used = 1

Thread-2 : END used = 1

Thread-1 : BEGIN used = 1

Thread-0 : BEGIN used = 2

Thread-2 : BEGIN used = 3

Thread-0 : END used = 3


进群:可以领取免费的架构师学习资料。

进群:了解最新的学习动态

进群:了解最新的阿里,京东招聘资讯

进群:获取更多的面试资料

1、具有1-5工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加群。

2、在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加群。

3、如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的,可以加群。

4、觉得自己很牛B,一般需求都能搞定。但是所学的知识点没有系统化,很难在技术领域继续突破的可以加群。

5. 群号:835638062 点击链接加入群:https://jq.qq.com/?_wv=1027&k=5S3kL3v

6.阿里Java高级大牛直播讲解知识点,分享知识,上面五大专题都是各位老师多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 从3岁入园到如今,整整23年。我的学生生涯貌似是暂时要告一段落了!回首过去,好像没有太多的画面留下来,心里...
    墨aimosion阅读 194评论 0 1
  • 做事能有人一起、生活能有人一起,很多时候都有同路人陪伴,整个人都会受到激励而动力十足。 因为同伴在做和你一类的事情...
    文建伟CZYH阅读 169评论 0 0
  • 有人看唐门吗? 小哥这几天给你们发福利啦!
    西北望天狼阅读 176评论 0 0