SingleThread-Executton design pattern
SingleThread-Executto这个设计模式 是指在同一时刻只能有一个线程去访问共享资源 简单来说 SingleThread-Executton design pattern就是用排他的方式保证同一时刻只有一个线程访问共享资源
先看一个很简单的例子 就好像地铁过安检的时候 每次检查只能由一个人通过 下面用代码模仿这种情况
//定义一个门类 这里简单设定名字和地址 当名字和地址的第一个字母相同时就能通过这个门
public class Gate { private String name = ""; private String address = ""; private int Count = 0; public synchronized void pass(String name, String address) { this.name = name; this.address = address; this.Count++; check(); } public void check() { if (this.name.charAt(0) != this.address.charAt(0)) { System.out.println("**************BROKEN****" + toString()); } } public String toString() { return "NO:" + Count + ":" + name + ":" + address; } }
定义一个用户类
public class User extends Thread { private String name; private String address; private Gate gate; public User(String name, String address, Gate gate) { this.name = name; this.address = address; this.gate = gate; } @Override public void run() { System.out.println(this.name + "+" + this.address); while (true) { this.gate.pass(name, address); } } }
接下来测试一下
public static void main(String[] args) { Gate gate = new Gate(); User user1 = new User("baobao","beijing",gate); User user2 = new User("shangshang","shanghai",gate); User user3 = new User("guangguang","guangzhou",gate); user1.start(); user2.start(); user3.start(); }
在这个例子中共享资源就是门的pass方法 若不对pass方法加synchronized关键字 就会出现名字与地址不一样也会通过门的情况 这就是线程的上下文轮转造成的数据不搭配现象 所以在这个设计模式的最重要的环节应该是找出共享资源并对它进行保护 下面看另一个例子,这种模式设计的不好就会出现死锁的情况
假设有两个人要吃面 但这两个人只有一对餐具 那么餐具就是这两个人的共享资源 这样很容易就会发生死锁的情况 比如A手持刀 等待B放下叉 B手持叉等待A放下刀
代码如下
public class Tableware { //简单定义一个餐具类 private final String toolName; public Tableware(String toolName) { this.toolName = toolName; } @Override public String toString() { return "Tableware{" + "toolName='" + toolName + '\'' + '}'; } }
public class EatNoodleThread extends Thread { private final String name; //左手餐具 private final Tableware leftTool; //右手餐具 private final Tableware rightTool; public EatNoodleThread(String name, Tableware leftTool, Tableware rightTool) { this.name = name; this.leftTool = leftTool; this.rightTool = rightTool; } @Override public void run() { while (true) { eat(); } } private void eat() { synchronized (leftTool) { System.out.println(name + ":take up" + leftTool); synchronized (rightTool) { System.out.println(name + ":take up" + rightTool); System.out.println(name + ":is eatting now"); System.out.println(name + ":put down " + rightTool); } System.out.println(name + ":put down" + leftTool); } } }
public static void main(String[] args) { Tableware knife = new Tableware("knife"); Tableware fork = new Tableware("fork"); new EatNoodleThread("aaa", fork, knife).start(); new EatNoodleThread("bbb", knife, fork).start(); }
运行上面的程序 很快就会出现都阻塞住的情况 发生了死锁 而要解决这个死锁问题 可以将左餐具和右餐具结合在一起 将餐具看成一个共享变量而不是分成左餐具和右餐具 代码如下
public class EatNoodleThread extends Thread { private final String name; //将左餐具和右餐具都封装为一个餐具 private final Tableware tableware; public EatNoodleThread(String name, Tableware tableware) { this.name = name; this.tableware = tableware; } @Override public void run() { while (true) { eat(); } } private void eat() { synchronized (tableware) { System.out.println(name + ":take up " + tableware); System.out.println(name + ":is eatting now"); System.out.println(name + ":put down " + tableware); } } public static void main(String[] args) { Tableware tableware = new Tableware("tableware"); new EatNoodleThread("A", tableware).start(); new EatNoodleThread("B", tableware).start(); } }
这样运行多久也都不会出现死锁的情况了
线程的安全是非常重要的 线程安全的类是指多个线程对这个类进行操作时 不会引起数据不一致的问题 这就是一个线程安全的类 所以确保线程安全非常重要的 但是在这个例子中线程安全用了synchronized关键字,这样排他性的设计同时也会牺牲性能 所以在设计的时候也要尽量减少synchronized的作用域