Single Threaded Execution Pattern

来源:互联网 发布:js获取自定义属性data 编辑:程序博客网 时间:2024/05/17 01:51

什么是Single Threaded Execution Pattern?

想象一个情景,一群人想要过一座桥,但是这个桥只能容忍一个人的质量,当一个人以上经过这座桥,这座桥就会断裂,我们先使用线程不安全的代码来实现这一过桥情景。

首先写桥类:

package singleThreadedExecution;public class Gate {    private int count = 0;//计数已经过桥的人    private String name;//正在桥上的人名    private String nameBackUp;//人名备份 如果和桥上人不一致说明桥上不止一个人    public void pass(String name){//过桥        this.name = name;        this.nameBackUp = name;//做一个备份        this.count++;    }    @Override    public String toString(){        return "name:"+this.name+" nameBackUp:"+this.nameBackUp;     }    public void check(){//检测桥断没断        if(this.name != this.nameBackUp){            System.out.println("Oops! The gate is broken! There are "+this.count+"men has passed this gate.");        }    }}
然后写实现了Runnable接口的类:
package singleThreadedExecution;public class UsingThread implements Runnable {    private Gate  gate;    private String name;    public UsingThread(Gate gate , String name){        this.gate = gate;        this.name = name;    }    @Override    public void run() {        while(true){            gate.pass(name);//name这个人过桥            gate.check();//检测        }    }}
最后做一个测试:

package singleThreadedExecution;public class Test {    public static void main(String[] args) {        Gate gate = new Gate();        UsingThread t1 = new UsingThread(gate, "Cherry");         UsingThread t2 = new UsingThread(gate, "Tommy");        UsingThread t3 = new UsingThread(gate, "Diana");        new Thread(t1).start();        new Thread(t2).start();        new Thread(t3).start();    }}
运行结果如下:


果然桥断了,为什么会这样呢?
因为在执行pass()方法和check()方法的时候,这两个方法内部的语句可能时交错执行的(这样说时把语句当成线程的基本操作单位,事实上线程可能以更小的单位在切换),而交错执行就导致了预料之外的情况。

我们看,第一次桥短的时候已经有3146个人通过了,这说明当运行次数少的时候我们可能根本无法发现这个问题。

怎么解决这个问题呢?

我们只需要在check()和pass()方法加上synchronized关键字进行修饰就行。因为这个关键字保证了只有一个线程(一个想过桥的人)能进行check()和pass(),而在这个阶段其他人想过桥都会被拦下。


这是一个很具体的例子,现在我们来抽象一下这个单线程模型:

首先介绍一个概念:sharedResource(共享资源)参与者,sharedResource参与者是可由多个线程访问的类,它的方法可以分为

1、safeMethod(安全的方法),不需要做特别处理

2、unsafeMethod(不安全的方法),被多个线程同时执行会产生预想不到的问题,必须加以防卫,使得多个线程不能同时访问这个方法。只能让单线程执行的程序范围叫做临界区(critical section)。


什么时候使用Single Threaded Execution?

1、多线程时,单线程程序显然不需要考虑这个问题。

2、数据可以被多个线程访问的时候。

3、状态可能变化的时候。

4、需要确保安全性的时候。


jdk提供的线程安全的包装方法

1、synchronizedCollection方法

2、synchronizedList方法

3、synchronizedMap方法

4、synchronizedSet方法

5、synchronizedSortedMap方法

6、synchronizedSortedSet方法


生命性与死锁

使用Single Threaded Execution pattern可能会发生死锁。

所谓死锁就是两个线程分别获得了锁,互相等待另一个锁释放锁,两个程序都无法继续,程序失去了生命性。


死锁的产生条件

1、具有多个sharedResource参与者。

2、线程锁定一个sharedResource时,还没接触钱就去锁定另一个sharedResource。

3、获取sharedResource参与者的顺序不固定(和sharedResource参与者对等,也就是没有固定的优先级)。


Single Threaded Execution使得程序执行性能减少的原因:

1、获取锁要花时间,可以减少sharedResource参与者的数量,以减少性能损失

2、线程冲突时没有锁的线程要进行等待,可以减少临界区范围,以减少出现线程冲突的机会。


原子性

所谓原子性就是不能再分的东西,Java语言规范中,一些基础类型的赋值和引用操作是原子的。对象等引用类型的赋值和引用操作也是原子的,因此即使不加上synchronized,也不会被分割。

比如现在有一个int型字段n,一个线程进行 n = 123; 另一个进行 n = 456;这样的操作导致n不是123就是456。

然而Java的long和double的指定、引用操作并不是不可分割的,对于一个线程进行l = 123L; 另一个进行l = 456L;这样的操作之后l会变成什么无法保证,也许是123L,也许是456L,也许是0L,甚至31415926L,当然现在大部分Java执行环境都将long和double当作原子操作来实现了。

对于想要保证这样的字段操作的原子性问题可以在synchronized方法内进行操作,还有一种方法就是声明变量的时候加上volatile关键字,使得对这个字段的操作变得不可分。

原创粉丝点击