1.孙悟空去了高老庄?
悟空、八戒跟师父请假要回去探亲。师父要求每个人请假要填上姓名、目的地。
弟子们很老实遵照执行,然而,当过了一段时间出了问题。我们看看问题出在哪里。
1.1 定义师父类
师父提供了一个方法 请假(姓名,目的地)
,即public void notice(String name, String address)
。为避免出错,师父类定义了一个私有检查方法,看请假的情况有没有问题。
public class Master {
private int cnt = 0;
private String name;
private String address;
public synchronized void notice(String name, String address) {
this.name = name;
this.address = address;
cnt++;
check();
}
private void check() {
if (name.equals("孙悟空") && (!address.equals("花果山")))
System.out.println("第"+cnt+" 次出现问题:" + name + "," + address);
if (name.equals("猪八戒") && (!address.equals("高老庄")))
System.out.println("第"+cnt+" 次出现问题:" + name + "," + address);
}
}
1.2 定义徒弟类
徒弟很老实,在定义自己信息的时候都用上了final,担心数据被篡改。
徒弟线程类启动后将不断向师父请假。
public class Follower extends Thread {
private final Master master;
private final String name;
private final String address;
public Follower(Master master,String name,String address){
this.master = master;
this.name = name;
this.address = address;
}
@Override
public void run() {
while (true) {
master.notice(name,address);
}
}
}
1.3 师父发现不对劲了
在运行过程中,师父的check()方法,发现了不一致的情况。
public class Main {
public static void main(String[] args) {
Master master = new Master();
Follower sunwukong = new Follower(master,"孙悟空","花果山");
Follower zhubajie = new Follower(master,"猪八戒","高老庄");
sunwukong.start();
zhubajie.start();
}
}
运行结果:
第379950712 次出现问题:孙悟空,花果山
第379956315 次出现问题:孙悟空,高老庄
2.问题出现在哪?
经过一番检查。我们发现Master类是不安全的。
-
出现问题:孙悟空,花果山
。这个是正常的啊。为什么还是报错?
-
出现问题:孙悟空,高老庄
这个又是怎么产生的呢,明明悟空类的地址是花果山啊。
2.1 问题
观察这几个代码,由于没有同步机制,在多线程环境中是交替执行的,也就是说A B 线程会不断有可能交替覆盖name和address属性。
this.name = name;
this.address = address;
cnt++;
check();
2.2 完善
将Master的public void notice(String name, String address) 方法更改为同步方法。
经过synchronized方法同步后,出问题的四行代码被打包为一个原子方法。一次性只允许一个徒弟请假,请假的过程中不会出现交替运行的情况,
public synchronized void notice(String name, String address)
运行很久都没有报错。
2.3 问题二
为什么check方法不需要增加synchronized同步?
因为第一、check()只被notice()调用,而notice方法已经同步了。第二、check()是私有方法,外部无法调用,且内部就只被notice()调用。
3.注意事项
在Single Threaded Execution 模式中,需要仔细找出实例状态不稳定的范围,将这个范围设置为临界区进行保护,同一时间只允许一个线程执行。
总结
在使用多线程环境中,如果共享资源的状态有可能会发生变化,那么更改资源状态的方法很可能就是不安全方法。对于这些方法应该加锁同步,加以保护。
多线程系列在github上有一个开源项目,本系列博客的实验代码都在里面。
https://github.com/forestnlp/concurrentlab
如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。
您的支持是对我最大的鼓励。