它是“一个线程”的意思,就像一个桥,每次只能通过一个人。
下面测试:Gate是表示门的类,经过门(pass)会检查姓名与address的首字母是否相同,如果不同,输出
Passer继承了Thread,它一直反复通过门
Main中有三个Passer,让这三个Passer通过同一个门(这里意味着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(); } private void check(){ if(name.charAt(0)!=address.charAt(0)) System.out.println("*****BROKEN*****"+toString()); } @Override public String toString(){ return "No."+counter+":"+name+","+address; } }
public class Passer extends Thread{ private final Gate gate; private final String myname; private final String myaddress; public Passer(Gate gate,String myname,String myaddress){ this.gate=gate; this.myname=myname; this.myaddress=myaddress; } @Override public void run(){ System.out.println(myname+" BEGIN"); while(true){ gate.pass(myname, myaddress); } } }
public class Main { public static void main(String[] args){ System.out.println("hit Ctrl+C to exit"); Gate gate=new Gate(); //三个Passer用了同一个gate new Passer(gate,"Alice","Alaska").start(); new Passer(gate,"Bobby","Brazil").start(); new Passer(gate,"Chris","Canada").start(); } }
运行结果:
hit Ctrl+C to exit
Alice BEGIN
Bobby BEGIN
*****BROKEN*****No.878:Bobby,Brazil
*****BROKEN*****No.1387:Bobby,Brazil
*****BROKEN*****No.23170:Alice,Alaska
*****BROKEN*****No.28157:Bobby,Brazil
*****BROKEN*****No.54236:Alice,Alaska
*****BROKEN*****No.59996:Bobby,Brazil
*****BROKEN*****No.69240:Bobby,Brazil
*****BROKEN*****No.77988:Alice,Alaska
*****BROKEN*****No.83399:Bobby,Brazil
*****BROKEN*****No.91945:Alice,Alaska
*****BROKEN*****No.97449:Bobby,Brazil
*****BROKEN*****No.106786:Alice,Alaska
*****BROKEN*****No.140881:Bobby,Brazil
*****BROKEN*****No.149914:Alice,Alaska
*****BROKEN*****No.183557:Alice,Alaska
*****BROKEN*****No.188912:Bobby,Brazil
*****BROKEN*****No.198266:Bobby,Brazil
*****BROKEN*****No.206391:Alice,Alaska
*****BROKEN*****No.237655:Bobby,Brazil
*****BROKEN*****No.245603:Bobby,Brazil
*****BROKEN*****No.561550:Alice,Brazil
*****BROKEN*****No.561550:Alice,Brazil
。。。。。(这时第三个线程还没有运行,其实这三个线程的运行顺序是不可知的)
原因分析:这个线程在check时,另外一个线程改变了gate的属性值。
应该做如下修改来保证每个时刻只有一个Passer进入gate的pass方法:
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(); } private void check(){ if(name.charAt(0)!=address.charAt(0)) System.out.println("*****BROKEN*****"+toString()); } @Override public synchronized String toString(){ return "No."+counter+":"+name+","+address; } }
这里check()是不需要用synchronized,因为check是private的,且只有pass才会调用它,而pass已经设置为synchronized的了。而String是public的,如果它不设置为synchronized,那么如果有线程调用toString的同时,另外一个Passer线程正修改完name而准备修改address,那么这样造成显示不一致了。
当多个线程共享同一个实例时候,多个线程独自改变实例的状态,使得实例丧失安全性。首先,需要找到不稳定的范围(临界区间),对临界区间进行保护,使同时执行的线程保持只有一条,这也就是Single Threaded Execution Pattern。当数据被多个线程访问、状态可能改变、需要确保安全时就可以使用这种模式。 java里可以用synchronized来实现临界区间。
synchronized void method(){....} synchronized (obj){......} void method(){ lock(); .... //这里如果有return 或者异常,那么锁就没有解开 unlock(); } void method(){ lock(); try{ .... } finally{ unlock(); } }这个模式会使程序的执行性能降低,因为在进入synchronized方法的时候要获取对象的锁定是需要时间的,当线程发生冲突的时候线程必须等待,这耗掉了很多时间。