java多线程设计模式——学习笔记(2)Single Threaded Execution Pattern

来源:互联网 发布:nba林书豪成名战数据 编辑:程序博客网 时间:2024/05/16 11:46

Single Threaded Execution Pattern是多线程中最为简单的一种模式,其用来限制同时只能让一个线程运行,用于多个线程共享资源(sharedResource)的情况。

文章中举了一个例子来说明该模式的使用。程序模拟3个人频繁经过只能同时通过一个人的门,当人通过门时,程序会在计数器中递增通过人数,并记录通过人员的姓名和地址。

程序中使用的类如下表所示:

名称解说Main创建一个门,并操作三个人不断的穿越门的类Gate表示门的类,当人经过时会记录人的姓名和地址UserThread表示人的类,只负责处理不断的在门间穿越

首先是不使用Single Threaded Execution Pattern的情况:

Main类:

public class Main {    public static void main(String[] args) {        System.out.println("Testing Gate, hit CTRL+C to exit.");        Gate gate = new Gate();        new UserThread(gate, "Alice", "Alaska").start();        new UserThread(gate, "Bobby", "Brazil").start();        new UserThread(gate, "Chris", "Canada").start();    }}

非线程安全(Thread-safe)的Gate类:

public class Gate {    private int counter = 0;    private String name = "Nobody";    private String address = "Nowhere";    public void pass(String name, String address) {        this.counter++;        this.name = name;        this.address = address;        check();    }    public String toString() {        return "No." + counter + ": " + name + ", " + address;    }    private void check() {        if (name.charAt(0) != address.charAt(0)) {            System.out.println("***** BROKEN ***** " + toString());        }    }}
其中check()方法是来检测门的状态的,如果姓名和地址的首字母不一致,则表示记录有问题,打印出BROKEN。


UserThread类:

package com.hik.test;public class UserThread extends Thread {    private final Gate gate;    private final String myname;    private final String myaddress;    public UserThread(Gate gate, String myname, String myaddress) {        this.gate = gate;        this.myname = myname;        this.myaddress = myaddress;    }    public void run() {        System.out.println(myname + " BEGIN");        while (true) {            gate.pass(myname, myaddress);        }    }}

运行结果:

**** BROKEN ***** No.654810203: Alice, Alaska
***** BROKEN ***** No.654810878: Chris, Canada
***** BROKEN ***** No.654811529: Chris, Alaska
***** BROKEN ***** No.654802550: Bobby, Canada
***** BROKEN ***** No.654812495: Alice, Brazil
***** BROKEN ***** No.654811529: Chris, Alaska
***** BROKEN ***** No.654813485: Chris, Canada
***** BROKEN ***** No.654814227: Alice, Alaska
***** BROKEN ***** No.654814972: Alice, Alaska
***** BROKEN ***** No.654815613: Alice, Alaska


可以看到,运算结果出现了错误,一种情况是即使姓名和地址的首字母一致,但还是打印出了BROKEN,另一种情况就是姓名和地址的首字母不一致的情况。

为什么会出现这种情况?

因为pass方法可以同时被多个线程调用,多个线程调用pass方法时,里面的语句可能是交错运行的,这就有可能出现姓名和地址的首字母不一致的情况。情况有可能如下图所示:


因为,所有线程都是调用同一个pass方法,它们之间是共享的关系,为了避免上述情况的发生,就必须保证同一时间只能有一个线程在访问pass方法。因此,可以将Gate类改为线程安全的类:

public class Gate {    private int counter = 0;    private String name = "Nobody";    private String address = "Nowhere";    public synchronized void pass(String name, String address) {        this.counter++;        this.name = name;        this.address = address;        check();    }    public synchronized String toString() {        return "No." + counter + ": " + name + ", " + address;    }    private void check() {        if (name.charAt(0) != address.charAt(0)) {            System.out.println("***** BROKEN ***** " + toString());        }    }}

先来看下运行结果:

Testing Gate, hit CTRL+C to exit.
Alice BEGIN
Bobby BEGIN
Chris BEGIN

(无论等多久,都不会打印出BROKEN)

运行结果正确。通过比较两个Gate类,可以发现,线程安全的Gate类只改了一点点,就是在pass()方法和toString()方法前面加上了synchronized关键字,因为synchronized方法可以保证同一时间只有一个线程可以执行它。

这样我们就完成了一个Single Threaded Execution Pattern。当然这过程中我们还需要注意几点:

  1. 死锁 
所谓死锁,就是指两个线程分别获取了锁定,互相等待另一个线程解除锁定的现象。举个例子,Alice和Bobby同吃一盘意大利面,桌上只有一只汤勺和一只叉子,吃意大利面时,必须同时使用汤勺和叉子,某一时刻,Alice拿着汤勺,Bobby拿着叉子,它们都在等待对方放下手中的餐具,这样,你等我,我等你,就陷入了死锁的情况。
当满足以下条件时,就可能出现死锁的情况:
(1)多个sharedResource参与者,相当于汤勺和叉子
(2)线程锁定一个sharedResource时,还没解除就去锁定另一个sharedResource。就相当于握着汤勺想着去拿对方的叉子
(3)sharedResource的角色是对等的,就像“拿汤勺—>拿叉子”和“拿叉子—>拿汤勺”这两个动作可能同时发生
(1)(2)(3)中只要破坏一种条件,就可避免死锁
2. 该以什么单位保护
对于Gate类,name和Address非得合在一起赋值不可,将pass方法定义成synchronized方法,就是为了防止多个线程穿插执行赋值语句,如果单独定义setName,setAddress这样的synchronized方法,线程对字段赋值的操作就被分散了,就不安全了
差不多就是这些了,这个模式算是比较简单了,后面还有更难的模式等着我呢,继续加油吧!



0 0
原创粉丝点击