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

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方法,线程对字段赋值的操作就被分散了,就不安全了
差不多就是这些了,这个模式算是比较简单了,后面还有更难的模式等着我呢,继续加油吧!



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值