一文详解synchronized与volatile

内容简介

1)synchronized对象监视器为Object时的使用;
2)synchronized对象监视器为Class时的使用;
3)非线程安全是如何出现的;
4)volatile的主要作用;
5)volatilesynchronized的区别及使用情况。

2.1 synchronized同步方法

非线程安全:在多个线程对同一对象中的实例变量进行并发访问产生,产生的后果就是脏读
线程安全:就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。

2.1.1 方法内的变量为线程安全

非线程安全问题存在于实例变量中,如果是方法内部的私有变量,则不存在非线程安全问题

public class HasSelfPrivateNum {
    public void addI(String userName) {
        try {
            int num;
            if (userName.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(userName + " num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class HasSelfPrivateNumThread1 extends Thread {
    private HasSelfPrivateNum numRef;
    public HasSelfPrivateNumThread1(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }
    @Override
    public void run() {
        numRef.addI("a");
    }
}
public class HasSelfPrivateNumThread2 extends Thread {
    private HasSelfPrivateNum numRef;
    public HasSelfPrivateNumThread2(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }
    @Override
    public void run() {
        numRef.addI("b");
    }
}
public class HasSelfPrivateNumRun {
    public static void main(String[] args) {
        HasSelfPrivateNum privateNum = new HasSelfPrivateNum();
        HasSelfPrivateNumThread1 numThread1 = new HasSelfPrivateNumThread1(privateNum);
        numThread1.start();
        HasSelfPrivateNumThread2 numThread2 = new HasSelfPrivateNumThread2(privateNum);
        numThread2.start();
    }
}
a set over!
b set over!
b num=200
a num=100

从上面的示例可见,方法中的变量不存在非线程安全问题。这是方法内部的变量是私有的特性造成的。

2.1.2 实例变量非线程安全

如果多个线程共同访问一个对象中的实例变量,则有可能出现非线程安全问题。
如果线程访问的对象中有多个实例变量,则运行的结果有可能出现交叉的情况。
如果该对象中仅有一个实例变量,则可能出现覆盖的情况。

public class HasSelfPrivateNum {
    private int num = 0;
    public void addI(String userName) {
        try {
            if (userName.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(userName + " num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class HasSelfPrivateNumThread1 extends Thread {
    private HasSelfPrivateNum numRef;
    public HasSelfPrivateNumThread1(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }
    @Override
    public void run() {
        numRef.addI("a");
    }
}
public class HasSelfPrivateNumThread2 extends Thread {
    private HasSelfPrivateNum numRef;
    public HasSelfPrivateNumThread2(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }
    @Override
    public void run() {
        numRef.addI("b");
    }
}
public class HasSelfPrivateNumRun {
    public static void main(String[] args) {
        HasSelfPrivateNum privateNum = new HasSelfPrivateNum();
        HasSelfPrivateNumThread1 numThread1 = new HasSelfPrivateNumThread1(privateNum);
        numThread1.start();
        HasSelfPrivateNumThread2 numThread2 = new HasSelfPrivateNumThread2(privateNum);
        numThread2.start();
    }
}
a set over!
b set over!
b num=200
a num=200

上面示例演示的是两个线程方通一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则有可能会出现非线程安全问题。上一章介绍了synchronized,所以只需在public void addI(String userName)方法前加synchronized

public class HasSelfPrivateNum {
    private int num = 0;
    synchronized public void addI(String userName) {
        try {
            if (userName.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(userName + " num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
a set over!
a num=100
b set over!
b num=200

根据以上实验结果:在两个线程访问统一对象中的同步方法时一定是线程安全的。

2.1.3 多个对象多个锁

public class HasSelfPrivateNum {
    private int num = 0;
    synchronized public void addI(String userName) {
        try {
            if (userName.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(userName + " num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class HasSelfPrivateNumThread1 extends Thread {
    private HasSelfPrivateNum numRef;
    public HasSelfPrivateNumThread1(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }
    @Override
    public void run() {
        numRef.addI("a");
    }
}
public class HasSelfPrivateNumThread2 extends Thread {
    private HasSelfPrivateNum numRef;
    public HasSelfPrivateNumThread2(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }
    @Override
    public void run() {
        numRef.addI("b");
    }
}
public class HasSelfPrivateNumRun {
    public static void main(String[] args) {
        HasSelfPrivateNum privateNum1 = new HasSelfPrivateNum();
        HasSelfPrivateNum privateNum2 = new HasSelfPrivateNum();
        HasSelfPrivateNumThread1 numThread1 = new HasSelfPrivateNumThread1(privateNum1);
        numThread1.start();
        HasSelfPrivateNumThread2 numThread2 = new HasSelfPrivateNumThread2(privateNum2);
        numThread2.start();
    }
}

上面示例是两个线程分别访问同一个类的两个不同势力的相同名称的同步方法,效果却是异步的方式运行的。

synchronized取得的锁都是对象锁,而不是把一段代码或方法当作锁,哪个线程先执行带有synchronized的方法,哪个线程就持有该方法所属对象的锁,那么其他线程也只能处于等待状态。

2.1.4 synchronized方法与锁对象

public class MyObject {
    public void methodA() {
        try {
            System.out.println("begin methodA threadName=" + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread {
    private MyObject myObject;
    public ThreadA(MyObject myObject) {
        this.myObject = myObject;
    }
    @Override
    public void run() {
        myObject.methodA();
    }
}
public class ThreadB extends Thread {
    private MyObject myObject;
    public ThreadB(MyObject myObject) {
        this.myObject = myObject;
    }
    @Override
    public void run() {
        myObject.methodA();
    }
}
public class RunTest {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        ThreadA threadA = new ThreadA(myObject);
        threadA.setName("threadA");
        ThreadB threadB = new ThreadB(myObject);
        threadB.setName("threadB");
        threadA.start();
        threadB.start();
    }
}
begin methodA threadName=threadA
begin methodA threadName=threadB
end
end

运行结果如上所示,下面在methodA方法前加入synchronized进行同步处理:

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 (Exception e) {
            e.printStackTrace();
        }
    }
}
begin methodA threadName=threadA
end
begin methodA threadName=threadB
end

调用用synchronized声明的方法一定是排队运行的,另外需要记住共享,只有共享资源的读写访问才需要同步化。

那么其他的方法在被调用的时候会是什么效果呢?如果查看到Lock锁对象的效果呢?

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 (Exception e) {
            e.printStackTrace();
        }
    }
    public void methodB() {
        try {
            System.out.println("begin methodB threadName=" + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread {
    private MyObject myObject;
    public ThreadA(MyObject myObject) {
        this.myObject = myObject;
    }
    @Override
    public void run() {
        myObject.methodA();
    }
}
public class ThreadB extends Thread {
    private MyObject myObject;
    public ThreadB(MyObject myObject) {
        this.myObject = myObject;
    }
    @Override
    public void run() {
        myObject.methodB();
    }
}
public class RunTest {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        ThreadA threadA = new ThreadA(myObject);
        threadA.setName("threadA");
        ThreadB threadB = new ThreadB(myObject);
        threadB.setName("threadB");
        threadA.start();
        threadB.start();
    }
}
begin methodB threadName=threadB
begin methodA threadName=threadA
end
end endTime=1587626865508

修改MyObject.java代码如下:

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 (Exception e) {
            e.printStackTrace();
        }
    }
    synchronized public void methodB() {
        try {
            System.out.println("begin methodB threadName=" + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

重新执行结果:

begin methodB threadName=threadB
end
begin methodA threadName=threadA
end endTime=1587626985729

综合实验结论:
A线程现持有MyObject对象的Lock锁,B线程可以以一部的方式调用MyObject对象中的非synchronized类型的方法。
B线程先持有MyObject对象的Lock锁,B线程如果在这时调用MyObject对象中的synchronized声明的方法时,进入等待。

2.1.5 脏读

多个线程调用同一个方法时,为了避免数据出现交叉情况,使用了synchronized来进行同步,虽然赋值的时候进行了同步,但是取值的时候有可能出现一些意想不到的意外,这种情况就是脏读。脏读:在读取实例变量时,值已经被其他线程更改过。

public class PublicVar {
    public String userName = "111";
    public String password = "222";
    synchronized 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 (Exception e) {
            e.printStackTrace();
        }
    }
    void getValue() {
        System.out.println("setValue method thread name" + Thread.currentThread().getName()
                + "   userName=" + userName + "   password=" + password);
    }
}
public class PublicVarThreadA extends Thread {
    private PublicVar publicVar;
    public PublicVarThreadA(PublicVar publicVar){
        this.publicVar = publicVar;
    }
    @Override
    public void run() {
        publicVar.setValue("AAA", "AAAAAAAA");
    }
}
public class PublicVarThreadRun {
    public static void main(String[] args) throws InterruptedException {
       PublicVar publicVar = new PublicVar();
       PublicVarThreadA thread = new PublicVarThreadA(publicVar);
       thread.start();
       Thread.sleep(200);
       publicVar.getValue();
    }
}
setValue method thread namemain   userName=AAA   password=222
setValue method thread nameThread-0   userName=AAA   password=AAAAAAAA

脏读的原因是getValue方法并不是同步的,在getValue方法加上synchronized,重新验证:

public class PublicVar {
    public String userName = "111";
    public String password = "222";
    synchronized 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 (Exception e) {
            e.printStackTrace();
        }
    }
    synchronized void getValue() {
        System.out.println("setValue method thread name" + Thread.currentThread().getName()
                + "   userName=" + userName + "   password=" + password);
    }
}
setValue method thread nameThread-0   userName=AAA   password=AAAAAAAA
setValue method thread namemain   userName=AAA   password=AAAAAAAA

当A线程调用X对象中加入synchronized修饰的Y方法时,A线程就获得了X对象的锁,其他线程必须等A线程执行完毕才可以调用Y方法。其他线程可以随意调用X对象的其他非同步方法;其他线程若其他synchronized修改的其他的方法时,必须等A线程将Y方法执行完,只有A线程释放了对象锁,其他线程才可以调用。

2.1.6 synchronized锁重入

synchronized用友锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时,是可以再次得到该对象的锁的。这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是可以得到锁的。

public class Service {
    synchronized public void service1(){
        System.out.println("service1");
        service2();
    }
    synchronized public void service2(){
        System.out.println("service2");
        service3();
    }
    synchronized public void service3(){
        System.out.println("service3");
    }
}
public class Mythread extends Thread {
    @Override
    public void run() {
        Service service = new Service();
        service.service1();
    }
}
public class ServiceRun {
    public static void main(String[] args) {
        Mythread mythread = new Mythread();
        mythread.start();
    }
}
service1
service2
service3

可重入锁:自己可以再次获取自己的内部锁。比如有一线程获取了某个对象锁,在锁没有释放之前,可以再次获取这个对象的锁。如果不可重入,就会造成死锁

可重入锁也支持在父子类继承的环境中。

public class Main {
    public int i = 10;
    synchronized public void operateMainMethod() {
        try {
            i--;
            System.out.println("main print i=" + i);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Sub extends Main {
    public void operateSubMethod() {
        try {
            while (i > 0) {
                i--;
                System.out.println("sub print i=" + i);
                Thread.sleep(100);
                this.operateMainMethod();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class SubMainThread extends Thread {
    @Override
    public void run() {
        Sub sub = new Sub();
        sub.operateSubMethod();
    }
}
public class SubMainThreadRun {
    public static void main(String[] args) {
        SubMainThread subMainThread = new SubMainThread();
        subMainThread.start();
    }
}
sub print i=9
main print i=8
sub print i=7
main print i=6
sub print i=5
main print i=4
sub print i=3
main print i=2
sub print i=1
main print i=0

根据以上示例说明,存在父子类继承关系时,子可以通过可重入锁调用父类的同步方法。

2.1.7 出现异常,锁自动释放

当一个线程执行的代码出现异常时,持有的锁会自动释放。

public class TestService {
    synchronized public void testMethod() {
        if (Thread.currentThread().getName().equals("a")) {
            System.out.println("ThreadName=" + Thread.currentThread().getName() +
                    " run beginTime=" + System.currentTimeMillis());
            while (true) {
                if (("" + Math.random()).substring(0, 8).equals("0.123456")) {
                    System.out.println("ThreadName="
                            + Thread.currentThread().getName() +
                            " run exceptionTime=" + System.currentTimeMillis());
                    Integer.parseInt("a");
                }
            }
        } else {
            System.out.println("Thread B run Time=" + System.currentTimeMillis());
        }
    }
}
public class TestServicetThreadA extends Thread {
    private TestService testService;
    public TestServicetThreadA(TestService testService) {
        this.testService = testService;
    }
    @Override
    public void run() {
        testService.testMethod();
    }
}
public class TestServicetThreadB extends Thread {
    private TestService testService;
    public TestServicetThreadB(TestService testService) {
        this.testService = testService;
    }
    @Override
    public void run() {
        testService.testMethod();
    }
}
public class TestRun {
    public static void main(String[] args) {
        try {
            TestService testService = new TestService();
            TestServicetThreadA threadA = new TestServicetThreadA(testService);
            threadA.setName("a");
            threadA.start();
            Thread.sleep(500);
            TestServicetThreadB threadB = new TestServicetThreadB(testService);
            threadB.setName("b");
            threadB.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
ThreadName=a run beginTime=1587694811074
ThreadName=a run exceptionTime=1587694811180
Exception in thread "a" java.lang.NumberFormatException: For input string: "a"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:580)
	at java.lang.Integer.parseInt(Integer.java:615)
	at com.ykc.part2.TestService.testMethod(TestService.java:14)
	at com.ykc.part2.TestServicetThreadA.run(TestServicetThreadA.java:10)
Thread B run Time=1587694811574

线程TestServicetThreadA出现异常并释放锁,线程TestServicetThreadB方法正常打印,实验的结论就是出现异常时,持有锁的线程自动释放。

2.1.8 同步不具有继承性

public class Main {
    synchronized public void mainMethod() {
        try {
            System.out.println("main 下一步 sleep begin threadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("main 下一步 sleep end threadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Sub extends Main {
    @Override
    public void mainMethod() {
        try {
            System.out.println("sub 下一步 sleep begin threadName=" +
                    Thread.currentThread().getName() + " time=" +
                    System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("sub 下一步 sleep end threadName=" +
                    Thread.currentThread().getName() + " time=" +
                    System.currentTimeMillis());
            super.mainMethod();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class MyThreadA extends Thread {
    private Sub sub;
    public MyThreadA(Sub sub) {
        this.sub = sub;
    }
    @Override
    public void run() {
        sub.mainMethod();
    }
}
public class MyThreadB extends Thread {
    private Sub sub;
    public MyThreadB(Sub sub) {
        this.sub = sub;
    }
    @Override
    public void run() {
        sub.mainMethod();
    }
}
public class SubMainRun {
    public static void main(String[] args) {
        Sub subRef = new Sub();
        MyThreadA myThreadA = new MyThreadA(subRef);
        myThreadA.setName("a");
        myThreadA.start();
        MyThreadB myThreadB = new MyThreadB(subRef);
        myThreadB.setName("b");
        myThreadB.start();
    }
}
sub 下一步 sleep begin threadName=b time=1587697314468
sub 下一步 sleep begin threadName=a time=1587697314469
sub 下一步 sleep end threadName=a time=1587697319469
sub 下一步 sleep end threadName=b time=1587697319469
main 下一步 sleep begin threadName=a time=1587697319469
main 下一步 sleep end threadName=a time=1587697324469
main 下一步 sleep begin threadName=b time=1587697324469
main 下一步 sleep end threadName=b time=1587697329469

从此示例可以看到同步不能继承,所以还得在子类添加synchronized,运行后的结果如下所示:

sub 下一步 sleep begin threadName=b time=1587697429248
sub 下一步 sleep end threadName=b time=1587697434248
main 下一步 sleep begin threadName=b time=1587697434248
main 下一步 sleep end threadName=b time=1587697439248
sub 下一步 sleep begin threadName=a time=1587697439248
sub 下一步 sleep end threadName=a time=1587697444248
main 下一步 sleep begin threadName=a time=1587697444248
main 下一步 sleep end threadName=a time=1587697449248

2.2 synchronized同步语句块

synchronized声明方法在某些情况下是有弊端的,比如某一线程调用同步方法执行一个长时间的任务,那么其他线程必然要等待比较长的时间,在这种情况下,我们可以使用synchronized来解决。

2.2.1 synchronized方法的弊端

public class Task {
    private String getData1;
    private String getData2;
    public synchronized void doLongTimeTask() {
        try {
            System.out.println("begin task");
            Thread.sleep(3000);
            String privateData1 = "长时间处理任务1 threadName=" + Thread.currentThread().getName();
            String privateData2 = "长时间处理任务2 threadName=" + Thread.currentThread().getName();
            getData1 = privateData1;
            getData2 = privateData2;
            System.out.println(getData1);
            System.out.println(getData2);
            System.out.println("end task");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class CommonUtils {
    public static long beginTime1;
    public static long endTime1;
    public static long beginTime2;
    public static long endTime2;
}
public class Mythread1 extends Thread {
    private Task task;
    public Mythread1(Task task) {
        this.task = task;
    }
    @Override
    public void run() {
        CommonUtils.beginTime1 = System.currentTimeMillis();
        task.doLongTimeTask();
        CommonUtils.endTime1 = System.currentTimeMillis();
    }
}
public class Mythread2 extends Thread {
    private Task task;
    public Mythread2(Task task) {
        this.task = task;
    }
    @Override
    public void run() {
        CommonUtils.beginTime2 = System.currentTimeMillis();
        task.doLongTimeTask();
        CommonUtils.endTime2 = System.currentTimeMillis();
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();
        Mythread1 mythread1 = new Mythread1(task);
        mythread1.start();
        Mythread2 mythread2 = new Mythread2(task);
        mythread2.start();
        Thread.sleep(10000);
        long beginTime = CommonUtils.beginTime1;
        if (CommonUtils.beginTime2 < CommonUtils.beginTime1) {
            beginTime = CommonUtils.beginTime2;
        }
        long endTime = CommonUtils.endTime1;
        if (CommonUtils.endTime2 > CommonUtils.endTime1) {
            endTime = CommonUtils.endTime2;
        }
        System.out.println("耗时:" + (endTime - beginTime) / 1000);
    }
}
begin task
长时间处理任务1 threadName=Thread-0
长时间处理任务2 threadName=Thread-0
end task
begin task
长时间处理任务1 threadName=Thread-1
长时间处理任务2 threadName=Thread-1
end task
耗时:6

执行结果如上所示,先执行的线程持有对象锁的时候,其他线程处于等待获取该对象锁的状态。解决这样的问题可以使用synchronized同步块。

2.2.2 synchronized同步代码块的使用

当两个并发线程访问同一个对象中的synchronized同步代码块时,一点时间只能由一个线程被执行,另一个线程必须等待当前线程执行完这个代码块后才能执行该代码块。

public class ObjectService {
    public void serviceMethod() {
        try {
            synchronized (this) {
                System.out.println("begin time=" + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("end   time=" + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ObjectServiceThread1 extends Thread {
    private ObjectService objectService;
    public ObjectServiceThread1(ObjectService objectService) {
        this.objectService = objectService;
    }
    @Override
    public void run() {
        objectService.serviceMethod();
    }
}
public class ObjectServiceThread2 extends Thread {
    private ObjectService objectService;
    public ObjectServiceThread2(ObjectService objectService) {
        this.objectService = objectService;
    }
    @Override
    public void run() {
        objectService.serviceMethod();
    }
}
public class ObjectServiceRun {
    public static void main(String[] args) {
        ObjectService service = new ObjectService();
        ObjectServiceThread1 thread1 = new ObjectServiceThread1(service);
        thread1.setName("thread1");
        thread1.start();
        ObjectServiceThread2 thread2 = new ObjectServiceThread2(service);
        thread2.setName("thread2");
        thread2.start();
    }
}
begin time=1587708096658
end   time=1587708098658
begin time=1587708098658
end   time=1587708100658

执行效果还是同步运行的。那么如何使用synchronized同步代码块解决程序执行效率低的问题呢?

###2.2.3 用同步代码块解决同步方法的弊端
修改2.2.1小节代码如下:

public class Task {
    private String getData1;
    private String getData2;
    public  void doLongTimeTask() {
        try {
            System.out.println("begin task");
            Thread.sleep(3000);
            String privateData1 = "长时间处理任务1 threadName=" + Thread.currentThread().getName();
            String privateData2 = "长时间处理任务2 threadName=" + Thread.currentThread().getName();
            synchronized(this){
                getData1 = privateData1;
                getData2 = privateData2;
            }
            System.out.println(getData1);
            System.out.println(getData2);
            System.out.println("end task");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
begin task
begin task
长时间处理任务1 threadName=Thread-1
长时间处理任务2 threadName=Thread-0
end task
长时间处理任务1 threadName=Thread-0
长时间处理任务2 threadName=Thread-0
end task
耗时:3

通过上面的实验可以得知,当一个线程访问Object的一个synchronized同步代码块时,另一个线程仍然可以访问该Object对象中非synchronized(this)同步代码块。

synchronized代码块真的是同步的吗?是否持有当前调用对象的锁?

###2.2.4 一半异步,一半同步

public class Task {
    public void doLongTimeTask() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("asynchronized threadName=" + Thread.currentThread().getName() + " i=" + (i + 1));
        }
        synchronized (this){
            for (int i = 0; i < 5; i++) {
                System.out.println("synchronized threadName=" + Thread.currentThread().getName() + " i=" + (i + 1));
            }
        }
    }
}
public class MyThread1 extends Thread {
    private Task task;
    public MyThread1(Task task) {
        this.task = task;
    }
    @Override
    public void run() {
        task.doLongTimeTask();
    }
}
public class MyThread2 extends Thread {
    private Task task;
    public MyThread2(Task task) {
        this.task = task;
    }
    @Override
    public void run() {
        task.doLongTimeTask();
    }
}
public class Run {
    public static void main(String[] args) {
        Task task = new Task();
        MyThread1 myThread1 = new MyThread1(task);
        myThread1.start();
        MyThread2 myThread2 = new MyThread2(task);
        myThread2.start();
    }
}
asynchronized threadName=Thread-0 i=1
asynchronized threadName=Thread-0 i=2
asynchronized threadName=Thread-1 i=1
asynchronized threadName=Thread-0 i=3
asynchronized threadName=Thread-1 i=2
asynchronized threadName=Thread-0 i=4
asynchronized threadName=Thread-1 i=3
asynchronized threadName=Thread-1 i=4
asynchronized threadName=Thread-0 i=5
synchronized threadName=Thread-0 i=1
synchronized threadName=Thread-0 i=2
synchronized threadName=Thread-0 i=3
synchronized threadName=Thread-0 i=4
synchronized threadName=Thread-0 i=5
asynchronized threadName=Thread-1 i=5
synchronized threadName=Thread-1 i=1
synchronized threadName=Thread-1 i=2
synchronized threadName=Thread-1 i=3
synchronized threadName=Thread-1 i=4
synchronized threadName=Thread-1 i=5

根据以上示例说明:不在synchronized块中的就是异步执行,在synchronized块中的就是同步执行。

2.2.5 synchronized 代码块间的同步性

当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中其他所有synchronized(this)的访问将被阻塞,这说明synchronized使用的对象监视器是一个。

public class ObjectService {
    public void serviceMethodA() {
        try {
            synchronized (this) {
                System.out.println("A begin time=" + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("A end   time=" + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void serviceMethodB(){
        synchronized (this){
            System.out.println("B begin time=" + System.currentTimeMillis());
            System.out.println("B end   time=" + System.currentTimeMillis());
        }
    }
}
public class ObjectServiceThread1 extends Thread {
    private ObjectService objectService;
    public ObjectServiceThread1(ObjectService objectService) {
        this.objectService = objectService;
    }
    @Override
    public void run() {
        objectService.serviceMethodA();
    }
}
public class ObjectServiceThread2 extends Thread {
    private ObjectService objectService;
    public ObjectServiceThread2(ObjectService objectService) {
        this.objectService = objectService;
    }
    @Override
    public void run() {
        objectService.serviceMethodB();
    }
}
public class ObjectServiceRun {
    public static void main(String[] args) {
        ObjectService service = new ObjectService();
        ObjectServiceThread1 thread1 = new ObjectServiceThread1(service);
        thread1.setName("thread1");
        thread1.start();
        ObjectServiceThread2 thread2 = new ObjectServiceThread2(service);
        thread2.setName("thread2");
        thread2.start();
    }
}
B begin time=1587710947329
B end   time=1587710947329
A begin time=1587710947330
A end   time=1587710949330

###2.2.6 验证synchronized(this)代码块是锁定当前对象的

synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。

public class Task {
    public void otherMethod(){
        System.out.println("====================run otherMethod====================");
    }
    public void doLongTimeTask() {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("sync threadName=" + Thread.currentThread().getName() + " i=" + (i + 1));
            }
        }
    }
}
public class MyThread1 extends Thread {
    private Task task;
    public MyThread1(Task task) {
        this.task = task;
    }
    @Override
    public void run() {
        task.doLongTimeTask();
    }
}
public class MyThread2 extends Thread {
    private Task task;
    public MyThread2(Task task) {
        this.task = task;
    }
    @Override
    public void run() {
        task.otherMethod();
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();
        MyThread1 myThread1 = new MyThread1(task);
        myThread1.start();
        Thread.sleep(100);
        MyThread2 myThread2 = new MyThread2(task);
        myThread2.start();
    }
}
sync threadName=Thread-0 i=1
====================run otherMethod====================
sync threadName=Thread-0 i=2
sync threadName=Thread-0 i=3
sync threadName=Thread-0 i=4
sync threadName=Thread-0 i=5
sync threadName=Thread-0 i=6
sync threadName=Thread-0 i=7
sync threadName=Thread-0 i=8
sync threadName=Thread-0 i=9
sync threadName=Thread-0 i=10

更改Task代码:

public class Task {
    synchronized public void otherMethod(){
        System.out.println("====================run otherMethod====================");
    }
    public void doLongTimeTask() {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("sync threadName=" + Thread.currentThread().getName() + " i=" + (i + 1));
            }
        }
    }
}
sync threadName=Thread-0 i=1
sync threadName=Thread-0 i=2
sync threadName=Thread-0 i=3
sync threadName=Thread-0 i=4
sync threadName=Thread-0 i=5
sync threadName=Thread-0 i=6
sync threadName=Thread-0 i=7
sync threadName=Thread-0 i=8
sync threadName=Thread-0 i=9
sync threadName=Thread-0 i=10
====================run otherMethod====================

2.2.7 将任意对象作为对象监视器

1)多个线程调用同一个对象中的不同名称的synchronized同步方法或synchronized(this)同步代码块时,代码调用按顺序执行;
2)synchronized同步方法对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态;
3)synchronized同步方法同一时间只有一个线程可以执行synchronized同步方法中的代码。

在多个线程持有对象监视器为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。示例如下:

public class Service {
    private String userNameParam;
    private String passwordParam;
    String anyString = new String();
    public void setUserNameAndPassword(String userName, String password) {
        try {
            synchronized (anyString) {
                System.out.println("线程名称为:" + Thread.currentThread().getName() +
                        " 在" + System.currentTimeMillis() + "进入同步代码块");
                this.userNameParam = userName;
                Thread.sleep(3000);
                this.passwordParam = password;
                System.out.println("线程名称为:" + Thread.currentThread().getName() +
                        " 在" + System.currentTimeMillis() + "离开同步代码块");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Thread1 extends Thread {
    private Service service;
    public Thread1(Service service){
        this.service = service;
    }
    @Override
    public void run() {
        service.setUserNameAndPassword("a", "aa");
    }
}
public class Thread2 extends Thread {
    private Service service;
    public Thread2(Service service){
        this.service = service;
    }
    @Override
    public void run() {
        service.setUserNameAndPassword("b", "bb");
    }
}
public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        Thread1 thread1 = new Thread1(service);
        thread1.setName("A");
        thread1.start();
        Thread2 thread2 = new Thread2(service);
        thread2.setName("B");
        thread2.start();
    }
}
线程名称为:A 在1587717017127进入同步代码块
线程名称为:A 在1587717020127离开同步代码块
线程名称为:B 在1587717020127进入同步代码块
线程名称为:B 在1587717023127离开同步代码块

锁非this对象具有一定的有点:如果在一个类中有多个synchronized方法,虽然能实现同步,但是会阻塞,所以影响运行效率;但如果锁定同步代码块,锁非this对象,则代码块中的程序与同步方法时异步的,不会与其他this同步方法争抢this锁,以此提高运行效率。修改代码如下:

public class Service {
    private String userNameParam;
    private String passwordParam;
    public void setUserNameAndPassword(String userName, String password) {
        try {
            String anyString = new String();
            synchronized (anyString) {
                System.out.println("线程名称为:" + Thread.currentThread().getName() +
                        " 在" + System.currentTimeMillis() + "进入同步代码块");
                this.userNameParam = userName;
                Thread.sleep(3000);
                this.passwordParam = password;
                System.out.println("线程名称为:" + Thread.currentThread().getName() +
                        " 在" + System.currentTimeMillis() + "离开同步代码块");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
线程名称为:A 在1587718183143进入同步代码块
线程名称为:B 在1587718183143进入同步代码块
线程名称为:A 在1587718186143离开同步代码块
线程名称为:B 在1587718186143离开同步代码块

synchronized(非this对象x)与同步synchronized方法是异步调用的。示例如下:

public class Service {
    private String anyString = new String();
    public void a() {
        try {
            synchronized (anyString) {
                System.out.println("a begin");
                Thread.sleep(3000);
                System.out.println("a   end");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    synchronized public void b(){
        System.out.println("b begin");
        System.out.println("b   end");
    }
}
public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.a();
    }
}
public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.b();
    }
}
public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
    }
}
a begin
b begin
b   end
a   end

由于对象监视器不同所以运行结果就是异步的。同步代码块放在非同步synchronized方法中进行声明,并不能保证调用方法的线程执行同步/顺序性,也就是线程调用方法的顺序是无序的,虽然在同步块中执行的顺序是同步的,这样极易出现脏读问题。

使用synchronized(非this对象x)同步代码块格式也可以解决脏读问题。

验证多个线程调用同一个方法是随机的。

public class MyList {
    private List<String> list = new ArrayList();
    synchronized public void add(String userName) {
        System.out.println("ThreadName=" + Thread.currentThread().getName() + " do add");
        list.add(userName);
        System.out.println("ThreadName=" + Thread.currentThread().getName() + " exit add");
    }
}
public class MyThreadA extends Thread {
    private MyList myList;
    public MyThreadA(MyList myList) {
        this.myList = myList;
    }
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            myList.add("threadA" + (i + 1));
        }
    }
}
public class MyThreadB extends Thread {
    private MyList myList;
    public MyThreadB(MyList myList) {
        this.myList = myList;
    }
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            myList.add("threadB" + (i + 1));
        }
    }
}
public class  Test {
    public static void main(String[] args) {
        MyList myList = new MyList();
        MyThreadA a = new MyThreadA(myList);
        a.setName("A");
        a.start();
        MyThreadB b = new MyThreadB(myList);
        b.setName("B");
        b.start();
    }
}

部分输出:

ThreadName=B do add
ThreadName=B exit add
ThreadName=A do add
ThreadName=A exit add
ThreadName=A do add
ThreadName=A exit add

从运行结果来看,同步块中的代码是同步打印的,但线程A和线程B的执行却是异步的,这就有可能出现脏读的环境。由于线程的执行方法顺序不确定,所以当A和B两个线程执行带有分支判断的方法时,就会出现逻辑上的错误,有可能出现脏读

public class MyList {
    private List<String> list = new ArrayList();
    synchronized public void add(String userName) {
        list.add(userName);
    }
    synchronized public int getSize() {
        return list.size();
    }
}
public class MyService {
    public MyList addServiceMethod(MyList myList, String data) {
        try {
            if (myList.getSize() < 1) {
                Thread.sleep(2000);
                myList.add(data);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myList;
    }
}
public class MyThreadA extends Thread {
    private MyList myList;
    public MyThreadA(MyList myList) {
        this.myList = myList;
    }
    @Override
    public void run() {
        MyService myService = new MyService();
        myService.addServiceMethod(myList, "A");
    }
}
public class MyThreadB extends Thread {
    private MyList myList;

    public MyThreadB(MyList myList) {
        this.myList = myList;
    }

    @Override
    public void run() {
        MyService myService = new MyService();
        myService.addServiceMethod(myList, "B");
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyList myList = new MyList();
        MyThreadA a = new MyThreadA(myList);
        a.setName("A");
        a.start();
        MyThreadB b = new MyThreadB(myList);
        b.setName("B");
        b.start();
        Thread.sleep(6000);
        System.out.println("listSize :" + myList.getSize());
    }
}
listSize :2

脏读出现的原因是,两个线程以异步的方式返回list的大小,解决办法就是同步化。修改MyService代码如下:

public class MyService {
    public MyList addServiceMethod(MyList myList, String data) {
        try {
            synchronized (myList){
                if (myList.getSize() < 1) {
                    Thread.sleep(2000);
                    myList.add(data);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myList;
    }
}

###2.2.8 细化验证3个结论

synchronized(非this对象x)是将x对象本身作为对象监视器
1)当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。
2)当其他线程执行x对象中synchronized同步方法呈同步效果。
3)当其他线程执行x对象方法里面的synchronized(this)代码块时,也是呈现同步效果。(如果其他线程调用x对象非synchronized呈异步)。

首先验证第一个结论:当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。

public class MyObject {}
public class Service {
    public void testMethod1(MyObject myObject) {
        synchronized (myObject) {
            try {
                System.out.println("testMethod1 getLock time=" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName());
                Thread.sleep(2000);
                System.out.println("testMethod1 releaseLock time=" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadA extends Thread {
    private Service service;
    private MyObject object;
    public ThreadA(Service service, MyObject object) {
        this.service = service;
        this.object = object;
    }
    @Override
    public void run() {
        service.testMethod1(object);
    }
}
public class ThreadB extends Thread {
    private Service service;
    private MyObject object;
    public ThreadB(Service service, MyObject object) {
        this.service = service;
        this.object = object;
    }
    @Override
    public void run() {
        service.testMethod1(object);
    }
}
public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        MyObject object = new MyObject();
        ThreadA a = new ThreadA(service, object);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(service, object);
        b.setName("b");
        b.start();
    }
}
testMethod1 getLock time=1587869506596 run ThreadName=a
testMethod1 releaseLock time=1587869508596 run ThreadName=a
testMethod1 getLock time=1587869508596 run ThreadName=b
testMethod1 releaseLock time=1587869510596 run ThreadName=b

因为使用了同一个对象监视器所以呈同步效果,如果使用不同的对象监视器会出现什么效果呢?修改代码如下:

public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        MyObject object = new MyObject();
        MyObject object2 = new MyObject();
        ThreadA a = new ThreadA(service, object);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(service, object2);
        b.setName("b");
        b.start();
    }
}
testMethod1 getLock time=1587869667589 run ThreadName=b
testMethod1 getLock time=1587869667589 run ThreadName=a
testMethod1 releaseLock time=1587869669589 run ThreadName=b
testMethod1 releaseLock time=1587869669590 run ThreadName=a

验证第二个结论,当其他线程执行x对象中synchronized同步方法时呈同步效果。

public class MyObject {
    synchronized public void printString() {
        System.out.println("printString getLock time" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName());
        System.out.println("======================");
        System.out.println("printString releaseLock time" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName());
    }
public class Service {
    public void testMethod1(MyObject myObject) {
        synchronized (myObject) {
            try {
                System.out.println("testMethod1 getLock time=" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName());
                Thread.sleep(5000);
                System.out.println("testMethod1 releaseLock time=" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadA extends Thread {
    private Service service;
    private MyObject object;
    public ThreadA(Service service, MyObject object) {
        this.service = service;
        this.object = object;
    }
    @Override
    public void run() {
        service.testMethod1(object);
    }
}
public class ThreadB extends Thread {
    private MyObject object;
    public ThreadB(MyObject object) {
        this.object = object;
    }
    @Override
    public void run() {
        object.printString();
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        MyObject object = new MyObject();
        ThreadA a = new ThreadA(service, object);
        a.setName("a");
        a.start();
        Thread.sleep(100);
        ThreadB b = new ThreadB(object);
        b.setName("b");
        b.start();
    }
}
testMethod1 getLock time=1587870132429 run ThreadName=a
testMethod1 releaseLock time=1587870137429 run ThreadName=a
printString getLock time1587870137429 run ThreadName=b
======================
printString releaseLock time1587870137429 run ThreadName=b

验证第三个结论,当其他线程执行x对象方法里面的synchronized(this)代码块时,也是呈现同步效果。(如果其他线程调用x对象非synchronized呈异步)。修改上一个示例中MyObject.java代码如下:

public class MyObject {
    public void printString() {
        synchronized (this) {
            System.out.println("printString getLock time" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName());
            System.out.println("======================");
            System.out.println("printString releaseLock time" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName());
        }
    }
}
testMethod1 getLock time=1587870388622 run ThreadName=a
testMethod1 releaseLock time=1587870393622 run ThreadName=a
printString getLock time1587870393622 run ThreadName=b
======================
printString releaseLock time1587870393622 run ThreadName=b

2.2.9 静态同步synchronized方法与synchronized(class)代码块

synchronized还可以应用在static方法上,这样写是对当前*.java文件对应的Class进行持锁。

public class Service {
    synchronized public static void printA() {
        try {
            System.out.println("printA begin, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
            Thread.sleep(3000);
            System.out.println("printA   end, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    synchronized public static void printB() {
            System.out.println("printB begin, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
            System.out.println("printB   end, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
    }
}
public class ThreadA extends Thread {
    @Override
    public void run() {
        Service.printA();
    }
}
public class ThreadB extends Thread {
    @Override
    public void run() {
        Service.printB();
    }
}
public class Run {
    public static void main(String[] args) {
        ThreadA a = new ThreadA();
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB();
        b.setName("B");
        b.start();
    }
}
printA begin, threadName=A startTime=1587871093446
printA   end, threadName=A startTime=1587871096446
printB begin, threadName=B startTime=1587871096446
printB   end, threadName=B startTime=1587871096446

从运行结果来看,都是同步效果,看起来与synchronized加在非static方法上的使用效果一样。其实synchronized加在static方法上是给Class类上锁,反之是给对象上锁。

public class Service {
    synchronized public static void printA() {
        try {
            System.out.println("printA begin, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
            Thread.sleep(3000);
            System.out.println("printA   end, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    synchronized public static void printB() {
            System.out.println("printB begin, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
            System.out.println("printB   end, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
    }
    synchronized public  void printC() {
        System.out.println("printC begin, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
        System.out.println("printC   end, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
    }
}
public class ThreadA extends Thread {
    @Override
    public void run() {
        Service.printA();
    }
}
public class ThreadB extends Thread {
    @Override
    public void run() {
        Service.printB();
    }
}
public class ThreadC extends Thread {
    @Override
    public void run() {
        Service service = new Service();
        service.printC();
    }
}
public class Run {
    public static void main(String[] args) {
        ThreadA a = new ThreadA();
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB();
        b.setName("B");
        b.start();
        ThreadC c = new ThreadC();
        c.setName("C");
        c.start();
    }
}
printA begin, threadName=A startTime=1587872022862
printC begin, threadName=C startTime=1587872022863
printC   end, threadName=C startTime=1587872022863
printA   end, threadName=A startTime=1587872025862
printB begin, threadName=B startTime=1587872025862
printB   end, threadName=B startTime=1587872025862

异步的原因是因为持有不同的锁,一个是对象锁,一个是Class锁。而Class锁可以对类的所有对象实例起作用。

public class Service {
    synchronized public static void printA() {
        try {
            System.out.println("printA begin, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
            Thread.sleep(3000);
            System.out.println("printA   end, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    synchronized public static void printB() {
            System.out.println("printB begin, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
            System.out.println("printB   end, threadName=" + Thread.currentThread().getName() + " startTime=" + System.currentTimeMillis());
    }
}
public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.printA();
    }
}
public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.printB();
    }
}
public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        Service service1 = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(service1);
        b.setName("B");
        b.start();
    }
}
printA begin, threadName=A startTime=1587879226062
printA   end, threadName=A startTime=1587879229063
printB begin, threadName=B startTime=1587879229063
printB   end, threadName=B startTime=1587879229063

同步synchronized(class)代码块的作用和synchronized static方法的作用一样。

2.2.10 数据类型String常量池的特性

在JVM中具有String常量池缓存的功能。将synchronized(String)同步快与String联合使用,要注意常量池带来的一些例外。

public class Service {
    public static void print(String stringParam) {
        try {
            synchronized (stringParam) {
                while (true) {
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.print("AAA");
    }
}
public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.print("AAA");
    }
}
public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
    }
}
A
A
A
A
A
A

控制台会一直打印A,出现这种情况,是因为String的两个值都是AAA,两个线程持有相同的锁。

2.2.11 同步synchronized方法无线等待与解决

public class Service {
    synchronized public void methodA(){
        System.out.println("methodA begin");
        boolean isContinueRun = true;
        while (isContinueRun){}
        System.out.println("methodA   end");
    }
    synchronized public void methodB(){
        System.out.println("methodB begin");
        System.out.println("methodB end");
    }
}
public class ThreadA extends Thread {
    private  Service service;
    public ThreadA(Service service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.methodA();
    }
}
public class ThreadB extends Thread {
    private  Service service;
    public ThreadB(Service service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.methodB();
    }
}
public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA thread = new ThreadA(service);
        thread.start();
        ThreadB thread1 = new ThreadB(service);
        thread1.start();
    }
}
methodA begin

更改Service.java代码如下:

public class Service {
    Object object1 = new Object();
    public void methodA() {
        synchronized (object1) {
            System.out.println("methodA begin");
            boolean isContinueRun = true;
            while (isContinueRun) {
            }
            System.out.println("methodA   end");
        }
    }
    Object object2 = new Object();
    public void methodB() {
        synchronized (object2) {
            System.out.println("methodB begin");
            System.out.println("methodB end");
        }
    }
}
methodA begin
methodB begin
methodB end

2.2.12 多线程的死锁

Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能释放的锁,从而导致所有任务都无法继续完成。

public class DealThread implements Runnable {
    public String userName;
    public Object lock1 = new Object();
    public Object lock2 = new Object();
    public void setFlag(String userName) {
        this.userName = userName;
    }
    @Override
    public void run() {
        if ("a".equals(userName)) {
            synchronized (lock1) {
                System.out.println("userName=" + userName);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("lock1->lock2顺序执行···");
                }
            }
        }
        if ("b".equals(userName)) {
            synchronized (lock2) {
                System.out.println("userName=" + userName);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("lock2->lock1顺序执行···");
                }
            }
        }
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        DealThread dealThread = new DealThread();
        dealThread.setFlag("a");
        Thread thread1 = new Thread(dealThread);
        thread1.setName("a");
        thread1.start();
        Thread.sleep(100);
        dealThread.setFlag("b");
        Thread thread2 = new Thread(dealThread);
        thread2.setName("b");
        thread2.start();
    }
}
userName=a
userName=b

可以使用JDK自带的工具来检测是否有死锁现象,首先进入CMD工具,再进入JDK的安装文件夹中的bin目录,执行jps命令。

jps
查看到我们线程Run的pid为9466,然后在执行jstack -l pid查看结果。

jstack -l pid

2.2.13锁对象的改变

在将任何数据类型作为同步锁时,需要注意的是,是否有多线程同时持有锁对象,如果同时持有相同的锁对象,则这些线程之间就是同步的;如果分别获取锁对象,这些线程之间就是异步的。

2.3 volatile

通过volatile强制从公共内存中读取变量的值。

多个线程访问主内存中的共享变量时,先从主内存中拷贝一份共享变量值到自己的工作内存(共享变量副本)中,在线程每次访问该变量时,都是访问的线程工作内存中的共享变量,而不是每次都去主内存中读取共享变量的值。在线程结束、IO操作导致线程切换、抛出异常等情况发生时会将自己工作内存中的值刷写到主存中。

从以上描述来看,如果每个线程都从自己的工作内存中读写数据,那么在线程结束、IO操作导致线程切换、抛出异常等情况发生时,同步到主内存中会出现线程同步问题。

不使用volatile关键字,什么时候重新去读共享变量的值、把工作内存中的共享变量写回主存中?

public class TestThread extends Thread {
    private boolean printFlag = false;
    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        printFlag = true;
        System.out.println("printFlag=" + printFlag);
    }
    public boolean isPrintFlag() {
        return printFlag;
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        testThread.start();
        while (true){
            // System.out.println();
            if(testThread.isPrintFlag()){
                System.out.println(System.currentTimeMillis());
                break;
            }
        }
    }
}

可以看到由于testThread.isPrintFlag()获取到的值一直是false,TestThread工作内存中的共享变量printFlag没有同步到主存中,导致死循环的出现,取消注释System.out.println();
分析1:
1)获得同步锁
2)清空工作内存
3)从主内存中拷贝对象副本到本地内存
4)执行代码(打印语句或加加操作)
5)刷新主内存数据
6)释放同步锁
分析2:
粗化锁。假如一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即 使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。假如虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(膨胀)到整个操作序列的外部(由屡次加锁编程只加锁一次)。
使用Thread.sleep();有同样的效果,当执行该线程的CPU有空闲时尽力的去主存取最新数据。

volatilesynchronized的比较:
1)volatile是线程同步的轻量级实现,所以volatile的性能比synchronized性能要好。volatile只能修饰变量,synchronize可以修饰方法,及代码块。
2)多线程访问volatile不会发生阻塞,synchronized会出现阻塞。
3)volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。
4)volatile解决的是变量在多个线程之间的可见性。而synchronized解决的是多个线程之间访问资源的同步性。

2.3.4 volatile非原子性的特性

volatile能保证数据的可见性,但不能保证原子性。

public class MyThread extends Thread {
    volatile public static int count;
    private static void addCount() {
        for (int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println("count=" + count);
    }
    @Override
    public void run() {
        addCount();
    }
}
public class Run {
    public static void main(String[] args) {
        MyThread[] myThreads = new MyThread[100];
        for (int i = 0; i < 100; i++) {
            myThreads[i] = new MyThread();
        }
        for (int i = 0; i < 100; i++) {
            myThreads[i].start();
        }
    }
}
...
count=9400
count=9200
count=9900
count=9800
count=9800
count=10000
count=9700
count=9500

修改MyThread代码如下:

public class MyThread extends Thread {
    public static int count;
    synchronized private static void addCount() {
        for (int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println("count=" + count);
    }
    @Override
    public void run() {
        addCount();
    }
}
...
count=8800
count=8900
count=9000
count=9100
count=9200
count=9300
count=9400
count=9500
count=9600
count=9700
count=9800
count=9900
count=10000

volatile主要是用的场合是在多个线程中可以感知实例变量被更改,并且可以获得最新的值使用,也就是用多线程读取共享变量时可以获得最新的值使用。

volatile提示线程每次从主内存中读取变量,而不是从私有内存中读取,这样就保证了同步数据的可见性。如果修改实例变量中的数据,比如i++,分解表达式的操作步骤如下:
1)从内存中读取i的值。
2)计算i的值。
3)将I的值写到内存中。
假如在第二步计算值的时候,另一个线程也修改了i的值,这时候就会出现脏数据,解决办法可以使用synchronized。所以说volatile本身并不处理数据的原子性,而是强制对数据的读写及时影响到主内存的。

下面演示一下volatile出现非线程安全的原因。
1)read和load阶段:从主存复制变量到当前线程工作内存;
2)use和assign结算:执行代码,改变共享变量值;
3)store和write阶段:用工作内存数据刷新主存对应变量的值。

变量在内存中的工作过程

在多线程环境中,use和assign是多次出现的,但这一操作并不是原子性,也就是在read和load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应变化,也就是私有内存和公共内存中的变量不同步,所以计算出来的结果和预期不一样,也就出现了非线程安全问题。

对于用volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的。例如线程1和线程2进行read和load操作的时候,发现主内存的count的值都是5,那么都会加载这个最新的值。也就是说,volatile解决的是变量读取时的可见性问题,但无法保证原子性,对于多线程访问同一个实例变量还是需要加锁同步。

2.3.5 使用原子类进行i++操作

除了i++操作时使用synchronized实现同步外,还可以使用AtomicInteger原子类实现。
原子操作是不能分割的整体,没有其他线程能够中断或检查正在原子操作的变量。一个原子(atomic)类型就是一个原子操作的可用类型,他可以在没有锁的情况下做到线程安全(thread-safe)。

参考文献

《Java多线程编程核心技术》高红岩

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

人生逆旅我亦行人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值