线程安全问题的例子
下面示例没有线程同步,出现了脏读现象。线程A调用setValue取得了publicVarRef
对象锁,但是线程A仍然可以调用publicVarRef
对象的非synchronized方法getValue()。
public class Runner {
public static void main(String[] args) {
try {
PublicVar publicVarRef = new PublicVar();
ThreadA threadA = new ThreadA(publicVarRef);
threadA.start();
Thread.sleep(100);
publicVarRef.getValue();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
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 thread name=" + Thread.currentThread().getName() +
" username=" + username +
" password=" + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void getValue() {
System.out.println("getValue thread name=" + Thread.currentThread().getName() +
" username=" + username +
" password=" + password);
}
}
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");
}
}
运行结果:
getValue thread name=main username=B password=AA
setValue thread name=Thread-0 username=B password=BB
如果public void getValue()方法加上synchronized修饰,则没有脏读了,因为必须等待执行完setValue才能调用getValue。结果如下:
setValue thread name=Thread-0 username=B password=BB
getValue thread name=main username=B password=BB
synchronized的锁是什么
关键字 synchronized 取得的锁是对象锁,而不是把一段代码或方法当做锁。哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能等待,前提是多个线程访问的是同一个对象。如果多个线程访问多个对象,则JVM会创建多个锁。、
synchronized 几种情况的对象锁:
- synchronized非静态同步方法,对象锁是
this
- synchronized静态同步方法,对象锁是
类名.class
- synchronized (obj) {} 同步代码块,对象锁是
obj
下面两个方法是类似的,synchronized修饰静态方法和synchronized (Service.class){}代码块的对象锁都是Service.class
class Service {
public synchronized static void foo(){
}
public static void foo2(){
synchronized (Service.class){
}
}
}
synchronized的一些特性
- 出现异常,锁自动释放
当线程执行的代码出现异常时,其所持有的锁会自动释放 - synchronized修饰方法不具有继承性
子类重写父类synchronized修饰的方法,需要在子类方法添加synchronized关键字 - synchronized锁重入
自己可以再次获取自己的内部锁。比如有一个线程获取了某个对象锁,此时这个对象锁还没有释放,当其再次想要获得这个对象的锁的时候还是可以获取的,如果不可锁重入的话就会造成死锁。// synchronized锁重入 public class Runner { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); } } class Service { // 可以再次获取自己的内部锁 synchronized public void service1() { System.out.println("service1"); service2(); } synchronized public void service2() { System.out.println("service1"); service3(); } synchronized public void service3() { System.out.println("service3"); } } class MyThread extends Thread { @Override public void run() { super.run(); Service service = new Service(); service.service1(); } }
- synchronized有volatile同步的功能
关键字synchronized具有可视性,可以使多个线程访问同一资源具有同步性,而且它还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能,这可以保证进入同步方法或代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。