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


    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79670775

    系列文章传送门:

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

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

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

    Java多线程学习(三)volatile关键字

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

    (2) synchronized同步语句块

    本节思维导图:
    思维导图

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

    一 synchronized方法的缺点

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

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

    Task.java

    public class Task {
    
        private String getData1;
        private String getData2;
    
        public synchronized void doLongTimeTask() {
            try {
                System.out.println("begin task");
                Thread.sleep(3000);
                getData1 = "长时间处理任务后从远程返回的值1 threadName="
                        + Thread.currentThread().getName();
                getData2 = "长时间处理任务后从远程返回的值2 threadName="
                        + Thread.currentThread().getName();
                System.out.println(getData1);
                System.out.println(getData2);
                System.out.println("end task");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    CommonUtils.java

    public class CommonUtils {
    
        public static long beginTime1;
        public static long endTime1;
    
        public static long beginTime2;
        public static long endTime2;
    }
    

    MyThread1.java

    public class MyThread1 extends Thread {
        private Task task;
        public MyThread1(Task task) {
            super();
            this.task = task;
        }
        @Override
        public void run() {
            super.run();
            CommonUtils.beginTime1 = System.currentTimeMillis();
            task.doLongTimeTask();
            CommonUtils.endTime1 = System.currentTimeMillis();
        }
    }
    

    MyThread2.java

    public class MyThread2 extends Thread {
        private Task task;
        public MyThread2(Task task) {
            super();
            this.task = task;
        }
        @Override
        public void run() {
            super.run();
            CommonUtils.beginTime2 = System.currentTimeMillis();
            task.doLongTimeTask();
            CommonUtils.endTime2 = System.currentTimeMillis();
        }
    }

    Run.java

    public class Run {
    
        public static void main(String[] args) {
            Task task = new Task();
    
            MyThread1 thread1 = new MyThread1(task);
            thread1.start();
    
            MyThread2 thread2 = new MyThread2(task);
            thread2.start();
    
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            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));
        }
    }

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

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

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

    public class Task {
    
        private String getData1;
        private String getData2;
    
        public void doLongTimeTask() {
            try {
                System.out.println("begin task");
                Thread.sleep(3000);
    
                String privateGetData1 = "长时间处理任务后从远程返回的值1 threadName="
                        + Thread.currentThread().getName();
                String privateGetData2 = "长时间处理任务后从远程返回的值2 threadName="
                        + Thread.currentThread().getName();
    
                synchronized (this) {
                    getData1 = privateGetData1;
                    getData2 = privateGetData2;
                }
    
                System.out.println(getData1);
                System.out.println(getData2);
                System.out.println("end task");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

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

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

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

    验证代码:synchronizedDemo1包下

    三 synchronized(object)代码块间使用

    MyObject.java

    public class MyObject {
    }

    Service.java

    public class Service {
    
        public void testMethod1(MyObject object) {
            synchronized (object) {
                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();
                }
            }
        }
    }

    ThreadA.java

    public class ThreadA extends Thread {
    
        private Service service;
        private MyObject object;
    
        public ThreadA(Service service, MyObject object) {
            super();
            this.service = service;
            this.object = object;
        }
    
        @Override
        public void run() {
            super.run();
            service.testMethod1(object);
        }
    }

    ThreadB.java

    public class ThreadB extends Thread {
        private Service service;
        private MyObject object;
    
        public ThreadB(Service service, MyObject object) {
            super();
            this.service = service;
            this.object = object;
        }
    
        @Override
        public void run() {
            super.run();
            service.testMethod1(object);
        }
    
    }

    Run1_1.java

    public class Run1_1 {
    
        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();
        }
    }

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

    修改Run1_1.java如下:

    public class Run1_2 {
    
        public static void main(String[] args) {
            Service service = new Service();
            MyObject object1 = new MyObject();
            MyObject object2 = new MyObject();
    
            ThreadA a = new ThreadA(service, object1);
            a.setName("a");
            a.start();
    
            ThreadB b = new ThreadB(service, object2);
            b.setName("b");
            b.start();
        }
    }

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

    四 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

    package ceshi;
    
    public class Service {
    
        public static void printA() {
            synchronized (Service.class) {
                try {
                    System.out.println(
                            "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
                    Thread.sleep(3000);
                    System.out.println(
                            "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        synchronized public static void printB() {
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
        }
    
        synchronized public void printC() {
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printC");
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printC");
        }
    
    }

    ThreadA.java

    public class ThreadA extends Thread {
        private Service service;
        public ThreadA(Service service) {
            super();
            this.service = service;
        }
        @Override
        public void run() {
            service.printA();
        }
    }

    ThreadB.java

    public class ThreadB extends Thread {
        private Service service;
        public ThreadB(Service service) {
            super();
            this.service = service;
        }
        @Override
        public void run() {
            service.printB();
        }
    }

    ThreadC.java

    public class ThreadC extends Thread {
        private Service service;
        public ThreadC(Service service) {
            super();
            this.service = service;
        }
        @Override
        public void run() {
            service.printC();
        }
    }
    

    Run.java

    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();
    
            ThreadC c = new ThreadC(service);
            c.setName("C");
            c.start();
        }
    }

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

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

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

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

        String s1 = "a";
        String s2="a";
        System.out.println(s1==s2);//true

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

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

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

    参考:

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

    如果你觉得博主的文章不错,欢迎转发点赞。你能从中学到知识就是我最大的幸运。

    欢迎关注我的微信公众号:“Java面试通关手册”(分享各种Java学习资源,面试题,以及企业级Java实战项目回复关键字免费领取)。另外我创建了一个Java学习交流群(群号:174594747),欢迎大家加入一起学习,这里更有面试,学习视频等资源的分享。

  • 相关阅读:
    洛谷P3763 [TJOI2017]DNA(后缀数组 RMQ)
    树莓派在身份证件核验领域应用
    Linux suse x86_64 环境上部署Hadoop启动失败原因分析
    用Spring Data JPA 基于内存存储pojo的简单案例
    短信发送AZDG加密算法
    【玩转Ubuntu】09. Ubuntu上安装apktool
    java遍历Map时remove删除元素
    wust 1061 链表的合并
    干货:yii日志功能详解
    xcode解决问题dyld: Library not loaded
  • 原文地址:https://www.cnblogs.com/snailclimb/p/9086415.html
Copyright © 2020-2023  润新知