synchronized同步方法《二》
1.synchronized方法和锁对象
(1)、验证线程锁的是对象
代码如下:
1.1创建一个MyObject类:
package edu.ymm.about_thread4; public class Myobject { public void methodA() { try { System.out.println("begin methodA threadName=" + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("end"); }catch (InterruptedException e) { e.printStackTrace(); } } }
1.2创建一个ThreadA类:
package edu.ymm.about_thread4; public class ThreadA extends Thread { private Myobject myobject; public ThreadA(Myobject myobject) { super(); this.myobject = myobject; } @Override public void run() { super.run(); myobject.methodA(); } }
1.3创建一个ThreadB类:
package edu.ymm.about_thread4; public class ThreadB extends Thread { private Myobject myobject; public ThreadB(Myobject myobject) { super(); this.myobject = myobject; } @Override public void run() { super.run(); myobject.methodA(); } }
1.4创建一个Test测试类:
package edu.ymm.about_thread4; public class Test { public static void main(String[] args) { Myobject myobject = new Myobject(); ThreadA threadA = new ThreadA(myobject); threadA.setName("a"); ThreadB threadB =new ThreadB(myobject); threadB.setName("b"); threadA.start(); threadB.start(); } }
1.5结果如下:
更改MyObject类后:
package edu.ymm.about_thread4; public class Myobject { synchronized public void methodA() { try { System.out.println("begin methodA threadName=" + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("end"); }catch (InterruptedException e) { e.printStackTrace(); } } }
其他三个类不变,结果如下:
通过上面的代码得到结论,调用关键字synchronized声明的方法一定是排队运行的。另外需要牢牢记住“共享”,因为只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本就没有同步的必要!
下面我们来看另一种情况:
(2)、其他方法被调用的效果,以及查看Lock锁对象的效果:
2.1继续创建一个MyObject类:
package edu.ymm.about_thread4; public class Myobject { synchronized public void methodA() { try { System.out.println("begin methodA threadName=" + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("end endTime" + System.currentTimeMillis()); }catch (InterruptedException e) { e.printStackTrace(); } } public void methodB() { try { System.out.println("begin methodB threadName=" + Thread.currentThread().getName() + " begin Time" + System.currentTimeMillis()); Thread.sleep(5000); System.out.println("end"); }catch (InterruptedException e) { e.printStackTrace(); } } }
2.2继续创建一个ThreadA类:
package edu.ymm.about_thread4; public class ThreadA extends Thread { private Myobject myobject; public ThreadA(Myobject myobject) { super(); this.myobject = myobject; } @Override public void run() { super.run(); myobject.methodA(); } }
2.3继续创建一个ThreadB类:
package edu.ymm.about_thread4; public class ThreadB extends Thread { private Myobject myobject; public ThreadB(Myobject myobject) { super(); this.myobject = myobject; } @Override public void run() { super.run(); myobject.methodB(); } }
2.4继续创建一个Test类:
package edu.ymm.about_thread4; public class Test { public static void main(String[] args) { Myobject myobject = new Myobject(); ThreadA threadA = new ThreadA(myobject); threadA.setName("a"); ThreadB threadB =new ThreadB(myobject); threadB.setName("b"); threadA.start(); threadB.start(); } }
结果如下:
通过这个例子我们可以得到的结论是:
(1)A线程先持有Object对象的Lock锁,B线程可以以异步的方式调用Object对象中的非synchronized类型的方法。
(2)A线程先持有Object对象的Lock锁,B线程如果在这时调用Object对象中的synchronized类型的方法则需要等待,也就是同步!
2.脏读
在上面1的例子中已经实现多个线程调用同一个方法时,为了避免数据出现交叉的情况,使用synchronized关键字来进行同步。虽然在赋值时进行了同步。但是在取值时有可能出现一些意料不到的意外,这种情况就是脏读。发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。
(1)创建一个publicVar类:
package edu.ymm.about_thread5; public class PublicVar { public String username = "A"; public String password = "AA"; synchronized public void setValue(String username,String password) { try { this.username = username; Thread.sleep(5000); this.password = password; System.out.println("setValue method thread name=" + Thread.currentThread().getName()+ " username " + username + " password " + password); }catch (InterruptedException e){ e.printStackTrace(); } } public void getValue() { System.out.println("getValue method thread name=" + Thread.currentThread().getName()+ " username " + username + " password " + password); } }
(2)创建一个ThreadA类:
package edu.ymm.about_thread5; public class ThreadA extends Thread { private PublicVar publicVar; public ThreadA(PublicVar publicVar) { super(); this.publicVar = publicVar; } @Override public void run() { super.run(); publicVar.setValue("B", "BB"); } }
(3)创建一个Test类:
package edu.ymm.about_thread5; public class Test { public static void main(String[] args) { try { PublicVar publicVar = new PublicVar(); ThreadA threadA = new ThreadA(publicVar); threadA.start(); Thread.sleep(2000); publicVar.getValue(); }catch (InterruptedException e) { e.printStackTrace(); } } }
执行结果如下:
上述的结果中已经出现了脏读,username为B的密码成了“AA”。原因就是public void getValue()方法不是同步的,所以在任何时候都可以调用。解决办法就是加synchronized:
package edu.ymm.about_thread5;
public class PublicVar {
public String username = "A";
public String password = "AA";
synchronized public void setValue(String username,String password) {
try {
this.username = username;
Thread.sleep(5000);
this.password = password;
System.out.println("setValue method thread name="
+ Thread.currentThread().getName()+ " username "
+ username + " password " + password);
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public void getValue() {
System.out.println("getValue method thread name="
+ Thread.currentThread().getName()+ " username "
+ username + " password " + password);
}
}
改后结果为:
可见,方法setValue()和方法getValue()被依次执行。通过这个例子不仅要知道脏读是通过synchronized解决的。还得明白以下几点:
当A线程调用anyObject对象加入synchronized 关键字的X方法时,A线程就获得了x方法锁,更准确地讲,是获得了对象的锁,所以其他线程必须等A线程执行完毕才可以调用x方法,但B线程可以随意调用其他的非synchronized同步方法。
当A线程调用anyobject对象加入synchronized关键字的X方法时,A线程就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程将X方法执行完,也就是释放对象锁后才可以调用。这时A线程已经执行了一个完整的任务,也就是说usemame和password这两个实例变量已经同时被赋值,不存在脏读的基本环境。
脏读一定会出现操作实例变量的情况下,这就是不同线程 “争抢”实例变量的结果。