关于面试:synchronized有几种用法

在 Java 语言中,保障线程安全性的次要伎俩是加锁,而 Java 中的锁次要有两种:synchronized 和 Lock,咱们明天重点来看一下 synchronized 的几种用法。

用法简介

应用 synchronized 无需手动执行加锁和开释锁的操作,咱们只须要申明 synchronized 关键字就能够了,JVM 层面会帮咱们主动的进行加锁和开释锁的操作。
synchronized 可用于润饰一般办法、静态方法和代码块,接下来咱们别离来看。

1、润饰一般办法

synchronized 润饰一般办法的用法如下:

/**
 * synchronized 润饰一般办法
 */
public synchronized void method() {
    // ....
}

当 synchronized 润饰一般办法时,被润饰的办法被称为同步办法,其作用范畴是整个办法,作用的对象是调用这个办法的对象。

2、润饰静态方法

synchronized 润饰静态方法和润饰一般办法相似,它的用法如下:

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

当 synchronized 润饰静态方法时,其作用范畴是整个程序,这个锁对于所有调用这个锁的对象都是互斥的。

所谓的互斥,指的是同一时间只能有一个线程能应用,其余线程只能排队期待。

润饰一般办法 VS 润饰静态方法

synchronized 润饰一般办法和静态方法看似雷同,但二者齐全不同,对于静态方法来说 synchronized 加锁是全局的,也就是整个程序运行期间,所有调用这个静态方法的对象都是互斥的,而一般办法是针对对象级别的,不同的对象对应着不同的锁,比方以下代码,同样是调用两次办法,但锁的获取齐全不同,实现代码如下:

import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class SynchronizedUsage {
    public static void main(String[] args) throws InterruptedException {
        // 创立线程池同时执行工作
        ExecutorService threadPool = Executors.newFixedThreadPool(10);

        // 执行两次静态方法
        threadPool.execute(() -> {
            staticMethod();
        });
        threadPool.execute(() -> {
            staticMethod();
        });
        
        // 执行两次一般办法
        threadPool.execute(() -> {
            SynchronizedUsage usage = new SynchronizedUsage();
            usage.method();
        });
        threadPool.execute(() -> {
            SynchronizedUsage usage2 = new SynchronizedUsage();
            usage2.method();
        });
    }

    /**
     * synchronized 润饰一般办法
     * 本办法的执行须要 3s(因为有 3s 的休眠工夫)
     */
    public synchronized void method() {
        System.out.println("一般办法执行工夫:" + LocalDateTime.now());
        try {
            // 休眠 3s
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * synchronized 润饰静态方法
     * 本办法的执行须要 3s(因为有 3s 的休眠工夫)
     */
    public static synchronized void staticMethod() {
        System.out.println("静态方法执行工夫:" + LocalDateTime.now());
        try {
            // 休眠 3s
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

以上程序的执行后果如下:

从上述后果能够看出,静态方法加锁是全局的,针对的是所有调用者;而一般办法加锁是对象级别的,不同的对象领有的锁也不同。

3、润饰代码块

咱们在日常开发中,最罕用的是给代码块加锁,而不是给办法加锁,因为给办法加锁,相当于给整个办法全副加锁,这样的话锁的粒度就太大了,程序的执行性能就会受到影响,所以通常状况下,咱们会应用 synchronized 给代码块加锁,它的实现语法如下:

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

从上述代码咱们能够看出,相比于润饰办法,润饰代码块须要本人手动指定加锁对象,加锁的对象通常应用 this 或 xxx.class 这样的模式来示意,比方以下代码:

// 加锁某个类
synchronized (SynchronizedUsage.class) {
    // ......
}

// 加锁以后类对象
synchronized (this) {
    // ......
}

this VS class

应用 synchronized 加锁 this 和 xxx.class 是齐全不同的,当加锁 this 时,示意用以后的对象进行加锁,每个对象都对应了一把锁;而当应用 xxx.class 加锁时,示意应用某个类(而非类实例)来加锁,它是应用程序级别的,是全局失效的,如以下代码所示:

import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class SynchronizedUsageBlock {
    public static void main(String[] args) throws InterruptedException {
        // 创立线程池同时执行工作
        ExecutorService threadPool = Executors.newFixedThreadPool(10);

        // 执行两次 synchronized(this)
        threadPool.execute(() -> {
            SynchronizedUsageBlock usage = new SynchronizedUsageBlock();
            usage.thisMethod();
        });
        threadPool.execute(() -> {
            SynchronizedUsageBlock usage2 = new SynchronizedUsageBlock();
            usage2.thisMethod();
        });

        // 执行两次 synchronized(xxx.class)
        threadPool.execute(() -> {
            SynchronizedUsageBlock usage3 = new SynchronizedUsageBlock();
            usage3.classMethod();
        });
        threadPool.execute(() -> {
            SynchronizedUsageBlock usage4 = new SynchronizedUsageBlock();
            usage4.classMethod();
        });
    }

    /**
     * synchronized(this) 加锁
     * 本办法的执行须要 3s(因为有 3s 的休眠工夫)
     */
    public void thisMethod() {
        synchronized (this) {
            System.out.println("synchronized(this) 加锁:" + LocalDateTime.now());
            try {
                // 休眠 3s
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * synchronized(xxx.class) 加锁
     * 本办法的执行须要 3s(因为有 3s 的休眠工夫)
     */
    public void classMethod() {
        synchronized (SynchronizedUsageBlock.class) {
            System.out.println("synchronized(xxx.class) 加锁:" + LocalDateTime.now());
            try {
                // 休眠 3s
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

以上程序的执行后果如下:

总结

synchronized 用 3 种用法,用它能够来润饰一般办法、静态方法和代码块,其中最罕用的是润饰代码块,而润饰代码块时须要指定一个加锁对象,这个加锁对象通常应用 this 或 xxx.class 来示意,当应用 this 时,示意应用以后对象来加锁,而应用 class 时,示意示意应用某个类(非类对象实例)来加锁,它是全局失效的。

是非审之于己,毁誉听之于人,得失安之于数。

公众号:Java面试真题解析

面试合集:https://gitee.com/mydb/interview

【腾讯云】轻量 2核2G4M,首年65元

阿里云限时活动-云数据库 RDS MySQL  1核2G配置 1.88/月 速抢

本文由乐趣区整理发布,转载请注明出处,谢谢。

您可能还喜欢...

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据