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

news/2024/5/17 19:42:23 标签: 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. 类锁和对象锁互不干扰。

http://www.niftyadmin.cn/n/784216.html

相关文章

java 三种循环

循环结构的基本组成部分,一般可以分成四部分; 1.初始化语句:在循环开始最初执行,而且只做唯一一次。 2.条件判断:如果成立,则循环继续,如果不成立,则循环退出。 3.循环体&#xff1…

剑指Offer(锁)——synchronized的底层实现原理

首先来说说实现synchronized的基础: Java对象头Monitor 然后接下来就对这二者进行详细的讲解。 Hospot虚拟机中,对象在内存中的布局分为三块区域:对象头、实例数据、对齐填充。 这里主要来说说对象头。 对象头的结构如下: Ma…

webstorm 格式化代码及常用快捷键

centOS 下webstorm 格式化代码的快捷键 CtrlShiftl windows 下 webstorm 格式化代码的快键键 CtrlAltl mac 下 webstorm 格式化代码的快捷键 OptionCommandl转载于:https://www.cnblogs.com/gouqitou/p/9225773.html

剑指Offer(锁)——Synchronized与ReentrantLock的区别

https://blog.csdn.net/qq_41936805/article/details/94905395 可参考我的这篇文章

ASFNU SC Day1

暑期训练的第一天,学习图论。 包括强/双连通分量,割点与桥,二分图,2-Sat等。 ----算法模板--- 坑放这,以后再填。 ------例题------- 坑放这,以后再填。 -----模拟赛----- 地址:https://cn.vjud…

java数组的概念

java数组 java的数组中,数组是一种容器,可以同时存放多个数据值。 数组的特点; 1.数组是一种引用数据类型。 2.数组当中的多个数据类型必须统一。 3.数组的长度在程序运行期间不可改变。 使用数组 1 .数组的初始化(在内存当值…

剑指Offer(锁)——JMM内存模型

首先来介绍一下Java内存模型JMM Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段&am…

Python 遍历List的三种方法

转载至https://www.cnblogs.com/pizitai/p/6398276.html #!/usr/bin/env python# -*- coding: utf-8 -*-if __name__ __main__: list [html, js, css, python] # 方法1 print 遍历列表方法1: for i in list: print ("序号:%s 值:%s&qu…