概述
我们经常在使用synchronized关键字来修饰方法以保证实例变量的线程安全,今天来讨论一下synchronized关键字的相关特性
实现同步
synchronized使用起来很简单,比如在方法上使用,在多个线程访问同一个同步方法的时候,由于synchronized获得是对象锁,并不是把每一个方法当作锁,所以哪个线程先执行这个同步方法,哪个线程就持有该方法所属对象的锁,其他线程就需要等待当前持有对象锁的线程释放对象锁。
package com.leolee.multithreadProgramming.test.objectVariate;
/**
* @ClassName MyObject
* @Description: TODO
* @Author LeoLee
* @Date 2020/8/24
* @Version V1.0
**/
public class MyObject {
public synchronized void methodA() {
try {
System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.leolee.multithreadProgramming.test.objectVariate;
/**
* @ClassName ThreadA
* @Description: TODO
* @Author LeoLee
* @Date 2020/8/24
* @Version V1.0
**/
public class ThreadA extends Thread {
private MyObject object;
public ThreadA(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
object.methodA();
}
}
package com.leolee.multithreadProgramming.test.objectVariate;
/**
* @ClassName ThreadB
* @Description: TODO
* @Author LeoLee
* @Date 2020/8/24
* @Version V1.0
**/
public class ThreadB extends Thread {
private MyObject object;
public ThreadB(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodA();
}
}
package com.leolee.multithreadProgramming.test.objectVariate;
/**
* @ClassName Run
* @Description: TODO
* @Author LeoLee
* @Date 2020/8/24
* @Version V1.0
**/
public class Run {
public static void main(String[] args) {
MyObject object = new MyObject();
ThreadA a = new ThreadA(object);
a.setName("A");
ThreadB b = new ThreadB(object);
b.setName("B");
a.start();
b.start();
}
}
运行结果表明两个线程按照先后调用同步方法的顺序执行
begin methodA threadName = A
end
begin methodA threadName = B
end
验证“锁”的是对象
在MyObject类中添加b方法
package com.leolee.multithreadProgramming.test.objectVariate;
/**
* @ClassName MyObject
* @Description: TODO
* @Author LeoLee
* @Date 2020/8/24
* @Version V1.0
**/
public class MyObject {
public synchronized void methodA() {
try {
System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void methodB() {
try {
System.out.println("begin methodB threadName = " + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
修改ThreadB调用methodB方法
package com.leolee.multithreadProgramming.test.objectVariate;
/**
* @ClassName ThreadB
* @Description: TODO
* @Author LeoLee
* @Date 2020/8/24
* @Version V1.0
**/
public class ThreadB extends Thread {
private MyObject object;
public ThreadB(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodB();
}
}
运行结果:不被synchronized修饰的方法可以异步的被线程B调用
begin methodB threadName = B
begin methodA threadName = A
end
end
当给methodB也加上synchronized修饰的时候运行结果如下
begin methodA threadName = A
end
begin methodB threadName = B
end
虽然两个线程调用了MyObject类的不同方法,但是还是同步执行的。
得出以下结论:
- A线程首先持有某一个类的锁对象的时候,B线程可以异步调用该类的非同步方法
- A线程首先持有某一个类的锁对象的时候,B线程调用该类的另一个同步方法需要等待A线程释放
脏读
上面的例子通过synchronized对象锁实现了线程的同步,但是这时候并不意味着真正的实现了线程安全。因为在多线程实行过程中“写操作”会有执行时间的原因,会导致写与读之间的时间差,进而造成脏读现象的出现。脏读一定会出现操作实例变量的情况下,是多线程争夺实例变量的结果。
示例:
package com.leolee.multithreadProgramming.test.dirtyRead;
/**
* @ClassName PublicVar
* @Description: 脏读
* @Author LeoLee
* @Date 2020/8/27
* @Version V1.0
**/
public class PublicVar {
public String userName;
public String password;
public PublicVar(String userName, String password) {
this.userName = userName;
this.password = password;
}
public void getValue() {
System.out.println("getValue method thread Name:" + Thread.currentThread().getName());
System.out.println("userName:" + this.userName + ",password:" + this.password);
}
public synchronized void setValue(String userName, String password) {
try {
this.userName = userName;
Thread.sleep(5000);
this.password = password;
System.out.println("getValue method thread Name:" + Thread.currentThread().getName());
System.out.println("userName:" + this.userName + ",password:" + this.password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.leolee.multithreadProgramming.test.dirtyRead;
/**
* @ClassName ThreadA
* @Description:
* @Author LeoLee
* @Date 2020/8/27
* @Version V1.0
**/
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");
}
}
package com.leolee.multithreadProgramming.test.dirtyRead;
/**
* @ClassName Run
* @Description:
* @Author LeoLee
* @Date 2020/8/27
* @Version V1.0
**/
public class Run {
public static void main(String[] args) {
try {
PublicVar publicVarRef = new PublicVar("A", "AA");
ThreadA threadA = new ThreadA(publicVarRef);
threadA.start();
Thread.sleep(2000);
publicVarRef.getValue();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果如下:
getValue method thread Name:main
userName:B,password:AA
getValue method thread Name:Thread-0
userName:B,password:BB
很明显的在userName赋值之后,由于sleep了5秒模拟了业务操作,导致了只有userName赋值成功,主线程调用getValue方法读取了不在预期的脏数据。
解决办法显而易见,getValue方法也加上synchronized。