同步问题的引出
有这么一段简单的代码:
定义一个Runnable接口实现类,覆写run方法,其中该类中有一个属性num,初值为0,run方法功能是让num自加10000次,主方法中创建两个线程都执行此代码,执行完之后,在通过主线程输出num的值,为了确保打印语句在两个线程都执行完毕执行再执行,因此让两个线程都调用join方法。
public class TestDaemon implements Runnable {
private int num = 0;
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
num++;
}
}
public static void main(String[] args) {
TestDaemon td = new TestDaemon();
Thread thread1 = new Thread(td);
Thread thread2 = new Thread(td);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(td.num);
}
}
但程序运行结果num的值很少能达到20000.
这是因为num++不是原子操作,
非线程安全的。
执行num++需要三步走:
- 从主内存中读到工作内存
- 在工作内存中加1
- 写回主内存
分析原因:
内存图如上图所示,每个线程都有自己的工作内存,而对于变量num在主内存中存着,每次一个线程对它修改(+1),都需要将该变量的值从主内存中读进自己的工作内存中,然后修改完之后,再写回主内存。这便是一个完整的线程操作过程。
但是不能保证每次都是一个线程修改完之后另一个线程才开始修改这个值。假如此时的num是10,A线程首先将num读自己的工作内存中,改成11,然而,在写回主内存之前,B线程就来读取num,因此它读到的数是10,也进行+1,值也改成11,再写回主内存,(这便发生了脏读)此时可以发现两个线程都对num执行了一次+1操作,但最终结果num的值才加一次。.
这便是导致最终num的值不到20000的原因。
使用synchronized加锁
synchronized的作用就是实现线程间的同步,当多个线程抢占式访问共享资源时,使用
synchronized可以使得每次都只有一个线程访问该共享资源。
synchronized修饰代码块
被synchronized修饰的代码块称为同步代码块,使用同步代码块需要设置一个要被锁定的对象,任何对象都可以作为这个被锁对象,但为了使得每次只有一个线程执行该代码块,这个锁定的对象必须是所有执行代码块的线程所共享的,。
通过同步代码块来使得原来的程序变得线程安全。
public void run() {
for (int i = 0; i < 10000; i++) {
synchronized (this) { // 锁当前对象,
num++;
}
}
}
synchronized修饰普通方法
被synchronized修饰的方法称为同步方法,给方法加速等同于给当前对象加锁,进入同步方法首先需要获得该对象实例的锁。
代码演示:
public synchronized void run() {
for (int i = 0; i < 10000; i++) {
num++;
}
}
synchronized修饰静态方法
因为静态方法不属于某一个对象,而是类的方法,在静态方法中是不能使用this的,这个由类属性的初始化顺序而定的。因此给静态方法加锁,锁的不能是当前对象实例,而锁的是类的Class对象。Class对象,全局唯一。因此者是一个全局锁,
因此下列两种写法是一样的:
public static synchronized void fun(){
}
public static void fun(){
synchronized (Test.class)
}
synchronized锁重入
synchronized锁重入是一个线程获取到锁对象之后,进入同步方法或者同步块,当再次遇到同样的锁对象之后,可以重复获得该锁对象,这便是锁重入。试想一下,若不能锁重入,则一个线程重复获取同一个锁,必将造成死锁。
验证锁的可重入性:
public class TestReentry {
public static void main(String[] args) {
new Thread(new Monitor()).start();
}
}
class Monitor implements Runnable{
@Override
public void run() {
testA();
}
public synchronized void testA(){
System.out.println("helloA...");
// 锁的重入
testB();
}
public synchronized void testB(){
System.out.println("helloB....");
}
}
运行结果: