剑指Offer(锁)——synchronized的基本使用方法

到了并发模块,就不得不介绍一下Synchronized和ReentrantLock等等方面,锁的知识。

针对于互斥锁,我们先来介绍一下引发线程安全问题的主要诱因是什么?

  1. 存在共享数据(也称临界资源)。
  2. 存在多条线程共同操作共享数据。

而解决上面提到的问题的解决方法就是:同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再对共享数据进行操作

为了能让我们的解决方法得以实现,就引入了互斥锁。

互斥锁有以下几个特性

  1. 互斥性

也就是让同一时间只让一个线程持有某个对象锁,通过这种特性来实现多线程的协调机制,这样在同一时间只有一个线程对需要同步的代码块(复合操作)进行访问。互斥性,也被称作为原子性

  1. 可见性

我们必须保证在锁释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁的时候获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作,从而引发不一致。

但是我们必须明确一点,Synchronized锁住的不是普通的代码,而是对象。

前面学习jvm的时候,我们知道,堆是线程共享的,也是我们经常会操作的地带,所以,给一个对象去上合适锁,是解决线程问题的关键

而根据获取锁 的分类,又可以分为两类:

  1. 获取对象锁

获取对象锁的两种用法:

①同步代码块

synchronized(this),synchronized(实例),而我们所说的“锁”,也就是小括号内的实例对象。

②同步非静态方法

synchronized(method),这里的锁指的是当前对象的实例对象。

下面也是举一个例子来说明:

package thread;

import java.text.SimpleDateFormat;
import java.util.Date;

public class SyncThread implements Runnable {

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        /**
         * 按照不同线程的名字执行不同的方法
         */
        if (threadName.startsWith("A")) {
            async();
        } else if (threadName.startsWith("B")) {
            syncObjectBlock1();
        } else if (threadName.startsWith("C")) {
            syncObjectMethod1();
        }
    }

    /**
     * 异步方法:打印当前时间,和1s之后的时间
     */
    private void async() {
        try {
            System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 方法中有 synchronized(this|object) {} 同步代码块
     */
    private void syncObjectBlock1() {
        System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (this) {
            try {
                System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * synchronized 修饰非静态方法
     */
    private synchronized void syncObjectMethod1() {
        System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

输出如下:

我们先来看上面的A线程去执行async()方法的结果:

由于async方法本身和内部属性都没有被synchronized包裹,所以可以正常执行。

然后再来看看B线程执行的情况

在B线程执行的方法里,我们发现一个很有意思的事情,如果我们使用this锁,那被this锁包裹着的就能按照顺序去访问执行,而没有使用this锁的,就会异步正常执行。

再来看看C线程执行的方法,它使用的是用synchronized来修饰一个方法:

于是我们发现,被synchronized包裹起来的整个方法,都是只能允许一次性让一个线程去访问的。

  1. 获取类锁

获取类锁的两种用法:

① 同步代码块(synchronized(类.class)),锁是小括号()中的类对象(class 对象)
② 同步静态方法(synchronized static method) , 锁是当前对象的类对象(class 对象)

也是举一个例子来说明

package thread;

import java.text.SimpleDateFormat;
import java.util.Date;

public class SyncThread implements Runnable {

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        if (threadName.startsWith("D")) {
            syncClassBlock1();
        } else if (threadName.startsWith("E")) {
            syncClassMethod1();
        }

    }


    private void syncClassBlock1() {
        System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (SyncThread.class) {
            try {
                System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private synchronized static void syncClassMethod1() {
        System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出如下:

在这里插入图片描述

二者的使用差别,只有一个被static额外修饰,一个则没有。

其实我们可以发现,其执行的流程和之前是一样的,我们先看看D线程就知道了:D最开始的输出语句,也是异步执行,于是,线程1和线程2,就都打印出了没被synchronized包裹着那句话,之后,逐个获得锁,按照顺序打印出对应的话(这里只不过是线程2先获得了锁而已)

在这里插入图片描述
然后,再来看E,其实道理也和上面差不多,这次是锁完全覆盖,所以就会整体的获得锁,按照顺序执行
在这里插入图片描述

至此,我们来总结一下对象锁和类锁:

  1. 有线程访问对象的同步代码块的时候,另外的线程可以访问该对象的非同步代码块
  2. 如果锁住的是同一个对象,一个线程在访问对象的同步代码块的时候,另一个访问对象的同步代码块的线程会被阻塞。
  3. 如果锁住的是同一个对象,一个线程在访问对象的同步方法的时候,另一个访问对象的同步方法会被阻塞。
  4. 如果锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象同步方法的线程会被阻塞,反之亦然。
  5. 同一个类的不同对象的锁互不干扰。
  6. 类锁也是一把特殊的对象锁,所以同一个类的不同对象使用类锁将是同步的。
  7. 类锁和对象锁互不干扰。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值