现在有三个人要循环随机通过一个门,三个人的名字和国家首字母都一样,每当一个人通过门的时候,就检查这个人和国家是否一致,不一致则报错BROKEN.程序如下:
门:
import java.awt.Checkbox;
public class Gate {
private int count = 0;
private String name = "Nobody";
private String country = "Nowhere";
public void name() {
} void pass(String name,String address) {
// TODO Auto-generated method stub
this.count++;
this.name = name;
this.country = address;
Check();
}
public String toString() {
return "No."+count+":"+name+","+country;
}
private void Check() {
// TODO Auto-generated method stub
if(name.charAt(0) != country.charAt(0)){
System.out.println("BROKEN*************"+toString());
}
}
}
人(每个人穿越是独立的,故为一个线程)
public class UserThread extends Thread{
private Gate gate;
private String myname;
private String mycountry;
public UserThread(Gate gate, String name, String country) {
this.gate = gate;
this.myname = name;
this.mycountry = country;
}
public void run() {
// TODO Auto-generated method stub
System.out.println(myname+"BEGIN");
while (true) {
gate.pass(myname, mycountry);
}
}
}
客户端程序:
public class Main {
public static void main(String[] args){
System.out.println("Testing Gate");
Gate gate = new Gate();
new UserThread(gate, "Bobe", "Brazil").start();
new UserThread(gate, "Chen", "China").start();
new UserThread(gate, "Alice", "America").start();
}
}
每个人的名字首字母都和国家首字母一样,感觉应该不会出现BROKEN,但是看下结果:
Testing Gate
BobeBEGIN
AliceBEGIN
ChenBEGIN
BROKEN*************No.432:Alice,America
BROKEN*************No.543:Alice,Brazil
BROKEN*************No.811:Chen,America
BROKEN*************No.543:Alice,Brazil
BROKEN*************No.811:Chen,America
BROKEN*************No.1521:Alice,America
BROKEN*************No.1765:Alice,America
BROKEN*************No.2142:Alice,China
BROKEN*************No.1078:Alice,America
BROKEN*************No.2554:Bobe,Brazil
BROKEN*************No.2834:Bobe,Brazil
BROKEN*************No.3111:Alice,Brazil
BROKEN*************No.2142:Alice,China
BROKEN*************No.3495:Alice,America
(以下省略n条记录。。)
My god!出好多问题了!
为什么呢?因为所有线程共享一份数据——Gate对象,它是线程不安全的。在这个过程中,
this.count++;
this.name = name;
this.country = address;
Check();
是被三个线程交错调用的,要通过竞争来确定谁先执行。
可能出现一个线程在调用完pass的时候,恰好另一个线程
调用pass中给name赋值却还没及时给country赋值,结果前者check的时候发现name和country不一致,于是BROKEN,所以打印出来的名字和国家不一致。
注意到,在BROKEN中也有很多名字和国家一致的,这又是怎么回事?
同样因为Gate的线程不安全,前者在调用check的时候,后者调用pass将name和country同时修改了,于是又一致了。
如何让Gate线程安全呢?很简单,在被各个线程写数据的pass加上synchronized:
public synchronized void pass(String name,String address) {
// TODO Auto-generated method stub
this.count++;
this.name = name;
this.country = address;
Check();
}
结果
很给力~~
Testing Gate
BobeBEGIN
AliceBEGIN
ChenBEGIN
synchronized简单来说就是确保一个时刻只有一个线程访问对应的代码,其他线程在等待知道里面的线程执行完毕。而一般对于
多线程程序来说,对于多个线程同时执行的时候会使得对象的状态出现矛盾的方法,就要用synchronized来保护。synchronized实际上
是让线程得到一把锁(一般锁存在于对象中),只有持有锁的线程才可以执行,执行完synchronized代码块才释放锁。当一个线程执行的时候其他想执行
synchronized代码块的线程就需要等待这个锁。我们把这种当只有一个线程可以访问的代码称为临界区,这种多线程模式称为Single
Thread Execution。
一般作为于方法和代码块:
public synchronized void method() {
// TODO Auto-generated method stub
}
synchronized(obj) {
}
这时候锁住的是对象(方法所在的对象),于是当一个对象的 synchronized 代码有一个线程运行,其他线程也不能进入需要该对象其他代码块或方法。
还有一种是静态方法:
public static synchronized void method() {
}
此时需持有对应类的class对象的锁。
java中另外一种锁是明锁:ReentrantLock。
比如上面的例子,pass方法改为:
private ReentrantLock lock = new ReentrantLock();
public void pass(String name,String address) {
// TODO Auto-generated method stub
lock.lock();
this.count++;
this.name = name;
this.country = address;
Check();
lock.unlock();
}
同样也是线程安全的。
相对来说,ReentrantLock更加安全,但是存在一些隐患。、
public void method() {
lock.lock();
if(条件){
return;//或者抛异常
}
lock.unlock();
}
如果是进入if语句中,那么锁将永远打不开。那这一块代码其他线程就进不来了。
安全的方式是在finally中解锁。
public void method() {
lock.lock();
try {
if(条件){
return;//或者抛异常
}
} finally{
lock.unlock();
}
}
使用Single Thread Execution要注意的是避免死锁。死锁就是两个线程分别获得了锁定,互相等待另一个线程来解锁,最典型代码:
public void method1() {
synchronized (A) {
synchronized (B) {
}
}
}
public void method2() {
synchronized (B) {
synchronized (A) {
}
}
}
当一个线程调用了method1,另一个线程同时调用了method2,两个线程都出不来,都在等待对方持有的那把锁。
Single Thread Execution达到以下条件就会出现死锁:
1.具有多个锁参与其中。
2.线程锁定一个锁没解除就去获得其他锁。
3.线程获取这些锁的顺序不同。
以上条件只要打破一个就可以避免死锁发生。