zoukankan      html  css  js  c++  java
  • synchronized 加锁 this 和 class 的区别!

    synchronized 是 Java 语言中处理并发问题的一种常用手段,它也被我们亲切的称之为“Java 内置锁”,由此可见其地位之高。然而 synchronized 却有着多种用法,当它修饰不同对象时,其意义也是不同的,下面我们一起来看。

    synchronized 用法

    synchronized 可以用来修饰普通方法、静态方法和代码块

    ① 修饰普通方法

    /**
     * synchronized 修饰普通方法
     */
    public synchronized void method() {
        // .......
    }
    

    当 synchronized 修饰普通方法时,被修饰的方法被称为同步方法,其作用范围是整个方法,作用的对象是调用这个方法的对象。

    ② 修饰静态方法

    /**
     * synchronized 修饰静态方法
     */
    public static synchronized void staticMethod() {
        // .......
    }
    

    当 synchronized 修饰静态的方法时,其作用的范围是整个方法,作用对象是调用这个类的所有对象。

    ③ 修饰代码块

    为了减少锁的粒度,我们可以选择在一个方法中的某个部分使用 synchronized 来修饰(一段代码块),从而实现对一个方法中的部分代码进行加锁,实现代码如下:

    public void classMethod() throws InterruptedException {
        // 前置代码...
        
        // 加锁代码
        synchronized (SynchronizedExample.class) {
            // ......
        }
        
        // 后置代码...
    }
    

    以上代码在执行时,被修饰的代码块称为同步语句块,其作用范围是大括号“{}”括起来的代码块,作用的对象是调用这个代码块的对象。

    但以上代码,除了可以加锁 class 之外,还可以加锁 this,具体示例如下:

    public void classMethod() throws InterruptedException {
        // 前置处理代码...
        synchronized (this) {
            // ......
        }
        // 后置处理代码...
    }
    

    那问题来了,使用 synchronized 加锁 this 和 class 的区别是什么?不都是加锁同一个类吗?

    答案还真不是,加锁 this 和 class 区别还是很大的。下面我们通过以下 4 个示例,来看二者之间的区别。

    1.加锁 class 共享一个类实例

    首先,我们创建 5 个线程,调用同一个对象下 synchronized 加锁的 class 代码,具体示例如下:

    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    
    public class SynchronizedExample {
    
        public static void main(String[] args) {
            // 创建当前类实例
            final SynchronizedExample example = new SynchronizedExample();
            // 创建 5 个线程执行任务
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 调用 synchronized 修饰的 class 方法
                            example.classMethod();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
    
        /**
         * synchronized 修饰的 class 方法
         * @throws InterruptedException
         */
        public void classMethod() throws InterruptedException {
            synchronized (SynchronizedExample.class) {
                System.out.println(String.format("当前执行线程:%s,执行时间:%s",
                        Thread.currentThread().getName(), new Date()));
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }
    

    以上程序的执行结果如下:
    image.png
    从上述结果可以看出,这 5 个线程共享的是同一把锁。

    2.加锁 class 创建多个实例

    接下来,我们创建 5 个线程,调用不同对象下 synchronized 加锁的 class 代码,具体示例如下:

    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    
    public class SynchronizedExample {
    
        public static void main(String[] args) {
            // 创建 5 个线程执行任务
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 创建类实例
                            SynchronizedExample example = new SynchronizedExample();
                            // 调用 synchronized 修饰的 class 方法
                            example.classMethod();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
        
        /**
         * synchronized 修饰的 class 方法
         * @throws InterruptedException
         */
        public void classMethod() throws InterruptedException {
            synchronized (SynchronizedExample.class) {
                System.out.println(String.format("当前执行线程:%s,执行时间:%s",
                        Thread.currentThread().getName(), new Date()));
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }
    

    以上程序的执行结果如下:
    image.png
    从上述结果可以看出,虽然是不同的对象,但它们使用的仍然是同一把锁。

    3.加锁 this 共享一个类实例

    接下来,我们创建 5 个线程,调用 synchronized 加锁 this 的示例。首先我们这 5 个线程调用同一个对象的加锁方法,示例代码如下:

    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    
    public class SynchronizedExample {
    
        public static void main(String[] args) {
            // 创建当前类实例
            final SynchronizedExample example = new SynchronizedExample();
            // 创建 5 个线程执行任务
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 调用 synchronized 修饰的 this 方法
                            example.thisMethod();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
        
        /**
         * synchronized 修饰的 this 方法
         * @throws InterruptedException
         */
        public void thisMethod() throws InterruptedException {
            synchronized (this) {
                System.out.println(String.format("当前执行线程:%s,执行时间:%s",
                        Thread.currentThread().getName(), new Date()));
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }
    

    以上程序的执行结果如下:
    image.png
    从上述结果可以看出,以上线程使用的都是同一把锁。

    4.加锁 this 创建多个类实例

    最后一个示例最为特殊,我们使用 synchronized 加锁 this,让这 5 个线程调用各自创建对象的方法,具体示例如下:

    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    
    public class SynchronizedExample {
    
        public static void main(String[] args) {
            // 创建 5 个线程执行任务
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 创建(多个)类实例
                            SynchronizedExample example = new SynchronizedExample();
                            // 调用 synchronized 修饰的 this 方法
                            example.thisMethod();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
        
        /**
         * synchronized 修饰的 this 方法
         * @throws InterruptedException
         */
        public void thisMethod() throws InterruptedException {
            synchronized (this) {
                System.out.println(String.format("当前执行线程:%s,执行时间:%s",
                        Thread.currentThread().getName(), new Date()));
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }
    

    以上程序的执行结果如下:
    image.png
    从上述结果可以看出,当使用 synchronized 加锁 this 时,如果线程调用的不是同一个对象,那么这些线程之间使用的锁都是自己独立的锁,这个结果就和 synchronized 加锁 class 的结果完全不同了。

    总结

    通过以上 4 个示例我们可以得出结论,当使用 synchronized 加锁 class 时,无论共享一个对象还是创建多个对象,它们用的都是同一把锁,而使用 synchronized 加锁 this 时,只有同一个对象会使用同一把锁,不同对象之间的锁是不同的

    本系列推荐文章

    1. 并发第一课:Thread 详解
    2. Java中用户线程和守护线程区别这么大?
    3. 深入理解线程池 ThreadPool
    4. 线程池的7种创建方式,强烈推荐你用它...
    5. 池化技术到达有多牛?看了线程和线程池的对比吓我一跳!
    6. 并发中的线程同步与锁
    7. volatile 和 synchronized 的区别
    8. 轻量级锁一定比重量级锁快吗?
    9. 这样终止线程,竟然会导致服务宕机?
    10. SimpleDateFormat线程不安全的5种解决方案!
    11. ThreadLocal不好用?那是你没用对!
    12. ThreadLocal内存溢出代码演示和原因分析!
    13. Semaphore自白:限流器用我就对了!
    14. CountDownLatch:别浪,等人齐再团!
    15. CyclicBarrier:人齐了,司机就可以发车了!

    关注公号「Java中文社群」查看更多有意思、涨知识的 Java 并发文章。

    关注下面二维码,订阅更多精彩内容。
    微信打赏
    关注公众号(加好友):

  • 相关阅读:
    《Linux就该这么学》第十二课
    《Linux就该这么学》第十一课
    《Linux就该这么学》第十课
    《Linux就该这么学》第九课
    《Linux就该这么学》第八课
    模板层
    路由层
    git的使用
    属性选择器
    高级选择器
  • 原文地址:https://www.cnblogs.com/vipstone/p/15089487.html
Copyright © 2011-2022 走看看