Java多线程学习(二)synchronized关键字(2)

阅读 116
收藏 1
2018-03-26
原文链接:mp.weixin.qq.com

系列文章传送门:

Java多线程学习(一)Java多线程入门

Java多线程学习(二)synchronized关键字(1)

系列文章将被优先更新于微信公众号“Java面试通关手册”,欢迎广大Java程序员和爱好技术的人员关注。

(2) synchronized同步语句块

本节思维导图:

思维导图源文件+思维导图软件关注微信公众号:“Java面试通关手册”回复关键字:“Java多线程”免费领取。

一 synchronized方法的缺点

使用synchronized关键字声明方法有些时候是有很大的弊端的,比如我们有两个线程一个线程A调用同步方法后获得锁,那么另一个线程B就需要等待A执行完,但是如果说A执行的是一个很费时间的任务的话这样就会很耗时。

先来看一个暴露synchronized方法的缺点实例,然后在看看如何通过synchronized同步语句块解决这个问题。

Task.java

  1. public class Task {

  2.    private String getData1;

  3.    private String getData2;

  4.    public synchronized void doLongTimeTask() {

  5.        try {

  6.            System.out.println("begin task");

  7.            Thread.sleep(3000);

  8.            getData1 = "长时间处理任务后从远程返回的值1 threadName="

  9.                    + Thread.currentThread().getName();

  10.            getData2 = "长时间处理任务后从远程返回的值2 threadName="

  11.                    + Thread.currentThread().getName();

  12.            System.out.println(getData1);

  13.            System.out.println(getData2);

  14.            System.out.println("end task");

  15.        } catch (InterruptedException e) {

  16.            // TODO Auto-generated catch block

  17.            e.printStackTrace();

  18.        }

  19.    }

  20. }

CommonUtils.java

  1. public class CommonUtils {

  2.    public static long beginTime1;

  3.    public static long endTime1;

  4.    public static long beginTime2;

  5.    public static long endTime2;

  6. }

MyThread1.java

  1. public class MyThread1 extends Thread {

  2.    private Task task;

  3.    public MyThread1(Task task) {

  4.        super();

  5.        this.task = task;

  6.    }

  7.    @Override

  8.    public void run() {

  9.        super.run();

  10.        CommonUtils.beginTime1 = System.currentTimeMillis();

  11.        task.doLongTimeTask();

  12.        CommonUtils.endTime1 = System.currentTimeMillis();

  13.    }

  14. }

MyThread2.java

  1. public class MyThread2 extends Thread {

  2.    private Task task;

  3.    public MyThread2(Task task) {

  4.        super();

  5.        this.task = task;

  6.    }

  7.    @Override

  8.    public void run() {

  9.        super.run();

  10.        CommonUtils.beginTime2 = System.currentTimeMillis();

  11.        task.doLongTimeTask();

  12.        CommonUtils.endTime2 = System.currentTimeMillis();

  13.    }

  14. }

Run.java

  1. public class Run {

  2.    public static void main(String[] args) {

  3.        Task task = new Task();

  4.        MyThread1 thread1 = new MyThread1(task);

  5.        thread1.start();

  6.        MyThread2 thread2 = new MyThread2(task);

  7.        thread2.start();

  8.        try {

  9.            Thread.sleep(10000);

  10.        } catch (InterruptedException e) {

  11.            e.printStackTrace();

  12.        }

  13.        long beginTime = CommonUtils.beginTime1;

  14.        if (CommonUtils.beginTime2 < CommonUtils.beginTime1) {

  15.            beginTime = CommonUtils.beginTime2;

  16.        }

  17.        long endTime = CommonUtils.endTime1;

  18.        if (CommonUtils.endTime2 > CommonUtils.endTime1) {

  19.            endTime = CommonUtils.endTime2;

  20.        }

  21.        System.out.println("耗时:" + ((endTime - beginTime) / 1000));

  22.    }

  23. }

运行结果:

从运行时间上来看,synchronized方法的问题很明显。可以使用synchronized同步块来解决这个问题。但是要注意synchronized同步块的使用方式,如果synchronized同步块使用不好的话并不会带来效率的提升。

二 synchronized(this)同步代码块的使用

修改上例中的Task.java如下:

  1. public class Task {

  2.    private String getData1;

  3.    private String getData2;

  4.    public void doLongTimeTask() {

  5.        try {

  6.            System.out.println("begin task");

  7.            Thread.sleep(3000);

  8.            String privateGetData1 = "长时间处理任务后从远程返回的值1 threadName="

  9.                    + Thread.currentThread().getName();

  10.            String privateGetData2 = "长时间处理任务后从远程返回的值2 threadName="

  11.                    + Thread.currentThread().getName();

  12.            synchronized (this) {

  13.                getData1 = privateGetData1;

  14.                getData2 = privateGetData2;

  15.            }

  16.            System.out.println(getData1);

  17.            System.out.println(getData2);

  18.            System.out.println("end task");

  19.        } catch (InterruptedException e) {

  20.            // TODO Auto-generated catch block

  21.            e.printStackTrace();

  22.        }

  23.    }

  24. }

运行结果:从上面代码可以看出当一个线程访问一个对象的synchronized同步代码块时,另一个线程任然可以访问该对象非synchronized同步代码块。

时间虽然缩短了,但是大家考虑一下synchronized代码块真的是同步的吗?它真的持有当前调用对象的锁吗?

是的。不在synchronized代码块中就异步执行,在synchronized代码块中就是同步执行。

验证代码:https://github.com/Snailclimb/threadDemo/tree/master/src/synchronizedDemo1(synchronizedDemo1包下)

三 synchronized(object)代码块间使用

MyObject.java

  1. public class MyObject {

  2. }

Service.java

  1. public class Service {

  2.    public void testMethod1(MyObject object) {

  3.        synchronized (object) {

  4.            try {

  5.                System.out.println("testMethod1 ____getLock time="

  6.                        + System.currentTimeMillis() + " run ThreadName="

  7.                        + Thread.currentThread().getName());

  8.                Thread.sleep(2000);

  9.                System.out.println("testMethod1 releaseLock time="

  10.                        + System.currentTimeMillis() + " run ThreadName="

  11.                        + Thread.currentThread().getName());

  12.            } catch (InterruptedException e) {

  13.                e.printStackTrace();

  14.            }

  15.        }

  16.    }

  17. }

ThreadA.java

  1. public class ThreadA extends Thread {

  2.    private Service service;

  3.    private MyObject object;

  4.    public ThreadA(Service service, MyObject object) {

  5.        super();

  6.        this.service = service;

  7.        this.object = object;

  8.    }

  9.    @Override

  10.    public void run() {

  11.        super.run();

  12.        service.testMethod1(object);

  13.    }

  14. }

ThreadB.java

  1. public class ThreadB extends Thread {

  2.    private Service service;

  3.    private MyObject object;

  4.    public ThreadB(Service service, MyObject object) {

  5.        super();

  6.        this.service = service;

  7.        this.object = object;

  8.    }

  9.    @Override

  10.    public void run() {

  11.        super.run();

  12.        service.testMethod1(object);

  13.    }

  14. }

Run1_1.java

  1. public class Run1_1 {

  2.    public static void main(String[] args) {

  3.        Service service = new Service();

  4.        MyObject object = new MyObject();

  5.        ThreadA a = new ThreadA(service, object);

  6.        a.setName("a");

  7.        a.start();

  8.        ThreadB b = new ThreadB(service, object);

  9.        b.setName("b");

  10.        b.start();

  11.    }

  12. }

运行结果:可以看出如下图所示,两个线程使用了同一个“对象监视器”,所以运行结果是同步的。 那么,如果使用不同的对象监视器会出现什么效果呢?

修改Run1_1.java如下:

  1. public class Run1_2 {

  2.    public static void main(String[] args) {

  3.        Service service = new Service();

  4.        MyObject object1 = new MyObject();

  5.        MyObject object2 = new MyObject();

  6.        ThreadA a = new ThreadA(service, object1);

  7.        a.setName("a");

  8.        a.start();

  9.        ThreadB b = new ThreadB(service, object2);

  10.        b.setName("b");

  11.        b.start();

  12.    }

  13. }

运行结果:可以看出如下图所示,两个线程使用了不同的“对象监视器”,所以运行结果不是同步的了。

四 synchronized代码块间的同步性

当一个对象访问synchronized(this)代码块时,其他线程对同一个对象中所有其他synchronized(this)代码块代码块的访问将被阻塞,这说明synchronized(this)代码块使用的“对象监视器”是一个。也就是说和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。

另外通过上面的学习我们可以得出两个结论。

  1. 其他线程执行对象中synchronized同步方法(上一节我们介绍过,需要回顾的可以看上一节的文章)和synchronized(this)代码块时呈现同步效果;

  2. 如果两个线程使用了同一个“对象监视器”,运行结果同步,否则不同步.

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

synchronized关键字加到static静态方法和synchronized(class)代码块上都是是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。

Service.java

  1. package ceshi;

  2. public class Service {

  3.    public static void printA() {

  4.        synchronized (Service.class) {

  5.            try {

  6.                System.out.println(

  7.                        "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");

  8.                Thread.sleep(3000);

  9.                System.out.println(

  10.                        "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");

  11.            } catch (InterruptedException e) {

  12.                e.printStackTrace();

  13.            }

  14.        }

  15.    }

  16.    synchronized public static void printB() {

  17.        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");

  18.        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");

  19.    }

  20.    synchronized public void printC() {

  21.        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printC");

  22.        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printC");

  23.    }

  24. }

ThreadA.java

  1. public class ThreadA extends Thread {

  2.    private Service service;

  3.    public ThreadA(Service service) {

  4.        super();

  5.        this.service = service;

  6.    }

  7.    @Override

  8.    public void run() {

  9.        service.printA();

  10.    }

  11. }

ThreadB.java

  1. public class ThreadB extends Thread {

  2.    private Service service;

  3.    public ThreadB(Service service) {

  4.        super();

  5.        this.service = service;

  6.    }

  7.    @Override

  8.    public void run() {

  9.        service.printB();

  10.    }

  11. }

ThreadC.java

  1. public class ThreadC extends Thread {

  2.    private Service service;

  3.    public ThreadC(Service service) {

  4.        super();

  5.        this.service = service;

  6.    }

  7.    @Override

  8.    public void run() {

  9.        service.printC();

  10.    }

  11. }

Run.java

  1. public class Run {

  2.    public static void main(String[] args) {

  3.        Service service = new Service();

  4.        ThreadA a = new ThreadA(service);

  5.        a.setName("A");

  6.        a.start();

  7.        ThreadB b = new ThreadB(service);

  8.        b.setName("B");

  9.        b.start();

  10.        ThreadC c = new ThreadC(service);

  11.        c.setName("C");

  12.        c.start();

  13.    }

  14. }

运行结果:

从运行结果可以看出:静态同步synchronized方法与synchronized(class)代码块持有的锁一样,都是Class锁,Class锁对对象的所有实例起作用。synchronized关键字加到非static静态方法上持有的是对象锁。

线程A,B和线程C持有的锁不一样,所以A和B运行同步,但是和C运行不同步。

六 数据类型String的常量池属性

在Jvm中具有String常量池缓存的功能

  1.    String s1 = "a";

  2.    String s2="a";

  3.    System.out.println(s1==s2);//true

上面代码输出为true.这是为什么呢?

字符串常量池中的字符串只存在一份! 即执行完第一行代码后,常量池中已存在 “a”,那么s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2。

因为数据类型String的常量池属性,所以synchronized(string)在使用时某些情况下会出现一些问题,比如两个线程运行 synchronized("abc"){}和synchronized("abc"){}修饰的方法时,这两个线程就会持有相同的锁,导致某一时刻只有一个线程能运行。所以尽量不要使用synchronized(string)而使用synchronized(object)

参考:

《Java多线程编程核心技术》 《Java并发编程的艺术》

评论