zoukankan      html  css  js  c++  java
  • Java并发—synchronized关键字

    synchronized关键字的作用是线程同步,而线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。

    synchronized用法

    1、 在需要同步的方法的方法签名中加入synchronized关键字

    synchronized public void getValue() {
        ...
    }

    上面的代码修饰的synchronized是非静态方法,如果修饰的是静态方法(static)含义是完全不一样的。具体不一样在哪里,后面会详细说清楚。 

    synchronized static public void getValue() {
        ...
    }

     

    2、使用synchronized块对需要进行同步的代码段进行同步。 

    public void synchronizedMethod() {
            try {
                synchronized (this) {
                    System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    }

    上面的代码块是synchronized (this)用法,还有synchronized (非this对象)以及synchronized (类.class)这两种用法,这些使用方式的含义也是有根本的区别的。我们先带着这些问题继续往下看。

    对象锁与类锁

    synchronized关键字的使用大致有五种情况,其中三种是对象锁,两种是类锁:

    • synchronized修饰非静态方法、同步代码块的synchronized (this)用法和synchronized (非this对象)的用法锁的是对象锁。
    • synchronized修饰静态方法以及同步代码块的synchronized (类.class)用法锁的是类锁。

    下面看一些例子,首先看一下线程不同步的情况:

    public class SynchronizedTest {
        public void synchronizedMethod() {
            try {
                    System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public class MyThread extends Thread {
        private SynchronizedTest synchronizedTest;
    
        public MyThread(SynchronizedTest synchronizedTest) {
            super();
            this.synchronizedTest = synchronizedTest;
        }
    
        @Override
        public void run() {
            super.run();
            synchronizedTest.synchronizedMethod();
        }
    }
    
    public class Main {
        public static void main(String[] args) throws InterruptedException {
            SynchronizedTest synchronizedTest1 = new SynchronizedTest();
    
            Thread a = new MyThread(synchronizedTest1);
            a.setName("a");
            a.start();
    
            Thread b = new MyThread(synchronizedTest1);
            b.setName("b");
            b.start();
        }
    }

    运行结果:

    Thread[a,5,main]begin at:2017-09-13 16:52:54
    Thread[b,5,main]begin at:2017-09-13 16:52:54
    Thread[a,5,main]end at:2017-09-13 16:52:56
    Thread[b,5,main]end at:2017-09-13 16:52:56

     可以看到两个线程交叉执行,要让这两个线程依次执行,则需要使用对象锁同步,可以将SynchronizedTest类修改成下面的三种方式来添加对象锁:

    public class SynchronizedTest {
        synchronized public void synchronizedMethod() {
            try {
                    System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    或者
    public class SynchronizedTest {
        public void synchronizedMethod() {
            try {
                synchronized (this) {
                    System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    或者
    public class SynchronizedTest {
        
        Object object = new Object();
        
        public void synchronizedMethod() {
            try {
                synchronized (object) {
                    System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    运行结果:

    Thread[a,5,main]begin at:2017-09-13 16:59:12
    Thread[a,5,main]end at:2017-09-13 16:59:14
    Thread[b,5,main]begin at:2017-09-13 16:59:14
    Thread[b,5,main]end at:2017-09-13 16:59:16

    从上面可以看出,synchronized代码块(后两种方式)使用起来比synchronized方法(第一种方式)要灵活得多。因为也许一个方法中只有一部分代码只需要同步,如果此时对整个方法用synchronized进行同步,会影响程序执行效率。而使用synchronized代码块就可以避免这个问题,synchronized代码块可以实现只对需要同步的地方进行同步。 

    如果将Main类修改成下面这样,则对象锁失效:

    public class Main {
        public static void main(String[] args) throws InterruptedException {
            SynchronizedTest synchronizedTest1 = new SynchronizedTest();
            SynchronizedTest synchronizedTest2 = new SynchronizedTest();
    
            Thread a = new MyThread(synchronizedTest1);
            a.setName("a");
            a.start();
    
            Thread b = new MyThread(synchronizedTest2);
            b.setName("b");
            b.start();
        }
    }

    运行结果:

    Thread[b,5,main]begin at:2017-09-13 17:03:26
    Thread[a,5,main]begin at:2017-09-13 17:03:26
    Thread[b,5,main]end at:2017-09-13 17:03:28
    Thread[a,5,main]end at:2017-09-13 17:03:28

    因为上面两个线程调用的是两个对象中的方法,对象锁是不起作用的,这种情况下应该使用类锁,可以将SynchronizedTest类修改成下面的两种方式来添加类锁:

    public class SynchronizedTest {
        public void synchronizedMethod() {
            try {
                synchronized (SynchronizedTest.class) {
                    System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    或者
    public class SynchronizedTest {
        synchronized public static void synchronizedMethod() {
            try {
                    System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    运行结果:

    Thread[a,5,main]begin at:2017-09-13 17:07:02
    Thread[a,5,main]end at:2017-09-13 17:07:04
    Thread[b,5,main]begin at:2017-09-13 17:07:04
    Thread[b,5,main]end at:2017-09-13 17:07:06

    需要特别说明:

    对于同一个类A,线程1争夺A对象实例的对象锁,线程2争夺类A的类锁,这两者不存在竞争关系。也就说对象锁和类锁互不干预。

    静态方法则一定会同步,非静态方法需在单例模式才生效,但是也不能都用静态同步方法,总之用得不好可能会给性能带来极大的影响。另外,有必要说一下的是Spring的bean默认是单例的。

    参考:

    Java对象锁和类锁全面解析(多线程synchronized关键字)

  • 相关阅读:
    【Java学习】01. Java基本介绍及环境搭建
    vue组件之间的传值
    检测上传的文件类型js实现方式
    正则表达式学习笔记
    Nuxt.js学习笔记
    Vue+typescript+vuex项目实践学习笔记
    项目依赖&开发依赖
    vuex学习笔记
    export与export default的区别
    ES6 数组去重方法
  • 原文地址:https://www.cnblogs.com/Jason-Xiang/p/7515138.html
Copyright © 2011-2022 走看看