这座桥,一次只能过一个人。
使用程序模拟三个人频繁通过一个只允许通过一个人的门。
每次有人通过,人数统计便会增加。
每次通过,都会校验通过者的信息。
定义接口。
/**
* 接口
* @author bbhou
*/
public interface Gate {
/**
* 对过门的人通过校验
* @param name 姓名
* @param address 地址
*/
void pass(String name, String address);
}
用户执行线程
/**
* 用户线程
* @author bbhou
* @since 1.0.0
*/
public class UserThread extends Thread {
private final Gate gate;
/**
* 名称
*/
private final String name;
/**
* 地址
*/
private final String address;
public UserThread(Gate gate, String name, String address) {
this.gate = gate;
this.name = name;
this.address = address;
}
@Override
public void run() {
System.out.println(this.name + " BEGIN!");
while (true) {
this.gate.pass(name, address);
}
}
}
线程不安全的实现
/**
* UnsafeGate 线程不安全
* @since 1.0.0
* @author bbhou
*/
public class UnsafeGate implements Gate {
/**
* 计数器
*/
private int counter = 0;
/**
* 姓名
*/
private String name;
/**
* 地址
*/
private String address;
/**
* 通过
* @param name 姓名
* @param address 地址
*/
@Override
public void pass(String name, String address) {
this.counter++;
this.name = name;
this.address = address;
check();
}
/**
* 信息校验
*/
private void check() {
if(name.charAt(0) != address.charAt(0)) {
System.out.println("-----------------------BROKEN-----------------------"
+toString());
}
}
@Override
public String toString() {
return "UnsafeGate{" +
"counter=" + counter +
", name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
public static void main(String[] args) {
Gate gate = new UnsafeGate();
new UserThread(gate, "Apple", "Apple").start();
new UserThread(gate, "Big", "Big").start();
new UserThread(gate, "Cat", "Cat").start();
}
Apple BEGIN!
Cat BEGIN!
-----------------------BROKEN-----------------------UnsafeGate{counter=3393, name='Apple', address='Cat'}
Big BEGIN!
-----------------------BROKEN-----------------------UnsafeGate{counter=3903, name='Apple', address='Apple'}
-----------------------BROKEN-----------------------UnsafeGate{counter=4098, name='Apple', address='Apple'}
-----------------------BROKEN-----------------------UnsafeGate{counter=4301, name='Apple', address='Apple'}
-----------------------BROKEN-----------------------UnsafeGate{counter=3393, name='Apple', address='Cat'}
(以下省略)
很明显,这不太符合我们的预期。
当多线程执行时,这个类是线程不安全的。
出现这种现象的原因是,当一个线程执行 check()
方法时,其他线程在执行 pass()
方法。导致 name、address 的属性被修改。
如何解决这个问题呢?
请往下看。
我们将 pass()
和 toString()
进行同步保护。如下:
/**
* 线程安全
* @since 1.0.0
* @author bbhou
*/
public class SafeGate implements Gate {
/**
* 计数器
*/
private int counter = 0;
/**
* 姓名
*/
private String name;
/**
* 地址
*/
private String address;
/**
* 通过
* @param name 姓名
* @param address 地址
*/
@Override
public synchronized void pass(String name, String address) {
this.counter++;
this.name = name;
this.address = address;
check();
}
/**
* 信息校验
*/
private void check() {
if(name.charAt(0) != address.charAt(0)) {
System.out.println("-----------------------BROKEN-----------------------"
+toString());
}
}
@Override
public synchronized String toString() {
return "SafeGate{" +
"counter=" + counter +
", name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
public static void main(String[] args) {
Gate gate = new SafeGate();
new UserThread(gate, "Apple", "Apple").start();
new UserThread(gate, "Big", "Big").start();
new UserThread(gate, "Cat", "Cat").start();
}
Big BEGIN!
Apple BEGIN!
Cat BEGIN!
这次结果不会再出现错误的信息。
原因是什么呢?
synchronized 的作用
第一个案例中提到,之所以出现问题,是因为 pass()
被个线程交错执行导致的。
synchronized 可以保证此方法同时只能被一个线程执行。
synchronized 保护着什么?
本案例中,pass()
被声明为 synchronized 之后,保护着 Gate 类中的 counter,name,address 三个字段。
确保这些字段不会被多个线程同时访问。
其他地方保护好了吗?
上面的方法中,你应该发现 check()
方法并没有被声明为 synchronized。
会存在问题吗?
其实不会,原因如下:
(1)此类为 private 方法,外部无法直接访问。
(2)调用此类的方法 pass()
是被声明为 synchronized 的。
所需该类无需进行声明。
当然就算加上也不算错,但是这样可能会降低性能。
代码地址
多线程系列导航