并发和多线程-八面玲珑的synchronized

上篇《并发和多线程-说说面试常考平时少用的volatile》主要介绍的是volatile的可见性、原子性等特性,同时也通过一些实例简单与synchronized做了对比。

相比较volatile,其实我们应该更加熟悉synchronized,平时开发中接触和使用也更多一些。

那么为什么说synchronized是八面玲珑呢,因为它可以混迹在很多“场所”(方法、代码块),与各种角色(类、对象)打交道。

也正是因为它的八面玲珑,所以就显得比较神秘,也比较复杂,今天就来追踪下synchronized常去的地方和经常搭讪的角色。核心概念主要是介绍对象锁和类锁。

背景

synchronized,作为一种锁,主要是用于解决在多线程下的同步问题。

上篇中,我们在介绍可见性的时候提到了java的内存模型,有主内存和工作内存。

对应到我们常见的堆、栈的理解是这样的。


主内存主要包括本地方法区和堆。每个线程都有一个工作内存,工作内存中主要包括两个部分,一个是属于该线程私有的栈和对主存部分变量拷贝的寄存器(包括程序计数器PC和cup工作的高速缓存区)。??

1.所有的变量都存储在主内存中(虚拟机内存的一部分),对于所有线程都是共享的。

2.每条线程都有自己的工作内存,工作内存中保存的是主存中某些变量的拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。

3.线程之间无法直接访问对方的工作内存中的变量,线程间变量的传递均需要通过主内存来完成。

在JVM中,每个对象和类都会与一个监听器关联,为了实现监听器的排他监视能力,每个对象和类都会关联一个锁。当某个线程获取了某个对象的锁,则由于排他性,其他线程就会阻塞等待获取锁以获取执行权。

每个对象都只有唯一一个锁,同一时间,也只有一个线程可以拥有该锁。

类锁,其实可以理解为一种特殊的对象锁,因为在JVM并不存在所谓的类锁。

当JVM加载某个class时,加在这个Class对象上的就是类锁。所有该类的实例共享这个类锁,当某对象获取类锁权限时,则对于所有静态方法具有相同的执行权。

使用synchronized和未使用synchronized的对比

1、 不使用synchronized


package com.jackie.thread;

public class Run {

? ? public static void main(String[] args) {

? ? ? ? HasSelfPrivateNum numRef = new HasSelfPrivateNum();

? ? ? ? ThreadA athread = new ThreadA(numRef);

? ? ? ? athread.start();

? ? ? ? ThreadB bthread = new ThreadB(numRef);

? ? ? ? bthread.start();

? ? }

}

class HasSelfPrivateNum {

? ? private int num = 0;

? ? public void addI(String username) {

? ? ? ? try {

? ? ? ? ? ? if (username.equals("a")) {

? ? ? ? ? ? ? ? num = 100;

? ? ? ? ? ? ? ? System.out.println("a set over!");

? ? ? ? ? ? ? ? Thread.sleep(2000);

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? num = 200;

? ? ? ? ? ? ? ? System.out.println("b set over!");

? ? ? ? ? ? }

? ? ? ? ? ? System.out.println(username + " num=" + num);

? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

}

class ThreadA extends Thread {

? ? private HasSelfPrivateNum numRef;

? ? public ThreadA(HasSelfPrivateNum numRef) {

? ? ? ? super();

? ? ? ? this.numRef = numRef;

? ? }

? ? @Override

? ? public void run() {

? ? ? ? super.run();

? ? ? ? numRef.addI("a");

? ? }

}

class ThreadB extends Thread {

? ? private HasSelfPrivateNum numRef;

? ? public ThreadB(HasSelfPrivateNum numRef) {

? ? ? ? super();

? ? ? ? this.numRef = numRef;

? ? }

? ? @Override

? ? public void run() {

? ? ? ? super.run();

? ? ? ? numRef.addI("b");

? ? }

}
  • 该代码实例是多线程环境(两个线程)
  • 两个线程共用一个实例,HasSelfPrivateNum类的实例
  • 在main主线程中分别启动ThreadA和ThreadB
  • 不考虑重排序,首先创建ThreadA并启动,此时判断username.equal("a"),成立,此时赋值num=100,并休眠2秒钟
  • 在线程A休眠期间,因为没有实现同步,所以ThreadB启动也进入该方法,判定username.equal("a")不符合(此时username="b"),所以此时num=200
  • 等到ThreadA的2秒睡眠时间过去后,此时发现num已经被赋值200,所以此时也打印出num=200

最后的执行结果如下

注意:

这里有一个可见性的思考。当我们如果没有接触或者不了解可见性这个概念之前,我们想当然的认为ThreadA和ThreadB都是操作了num变量,那么对于同一个变量操作肯定最终都是保持一致的,所以都是num=200。

其实这里的num变量是共享变量,所以会存在被覆盖的情况。如果这个num变量是声明在addI(String username)方法里面,那么这时候鉴于可见性,虽然都是操作num,但是每个线程都持有自己的num副本,所以最后的结果是这样的


a set over!

b set over!

b num=200

a num=100

2、使用synchronized

上面的例子是没有使用synchronized的情况,如果加上synchronized关键字,这时候相当于在addI()方法上加锁了,更准确的说是在HasSelfPrivateNum类的实例化对象上获取了对象锁。

鉴于一个对象在同一时间只能被一个线程占有,所以当ThreadA进入方法后,会一直执行知道结束,即使这里有休眠2秒钟,ThreadB只能乖乖的等ThreadA执行完才能获取执行权继续执行。最终执行结果如下

synchronized使用的四种同步场景

synchronized使用场景主要包括如下四种同步场景

  • 实例方法(对象锁)
  • 静态方法(类锁)
  • 实例方法中的代码块(对象锁)
  • 静态方法中的代码块(类锁)

1、实例方法

参见上面对比例子中“加synchronized”的情况

2、静态方法


<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(255, 255, 255); color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo; font-size: 9pt;">package com.jackie.thread;

public class RunWithSynchronizedStaticMethod {

    public static void main(String[] args) {

        SynchronizedStaticMethodThreadA a = new SynchronizedStaticMethodThreadA();
        a.setName("A");
        a.start();

        SynchronizedStaticMethodThreadB b = new SynchronizedStaticMethodThreadB();
        b.setName("B");
        b.start();

    }

}

class SynchronizedStaticMethodService {

    synchronized public static void printA() {
        try {
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "进入printA");
            Thread.sleep(3000);
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "离开printA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public static void printB() {
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
 + System.currentTimeMillis() + "进入printB");
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
 + System.currentTimeMillis() + "离开printB");
    }

}

class SynchronizedStaticMethodThreadA extends Thread {
    @Override
 public void run() {
        SynchronizedStaticMethodService.printA();
    }

}

class SynchronizedStaticMethodThreadB extends Thread {
    @Override
 public void run() {
        SynchronizedStaticMethodService.printB();
    }
}</pre>

执行结果如下


线程名称为:A在1528626219469进入printA

线程名称为:A在1528626222473离开printA

线程名称为:B在1528626222473进入printB

线程名称为:B在1528626222474离开printB

这里的synchronized是加载静态方法上的,我们知道静态方法是通过类直接调用的,不需要实例化的。这里用的就是类锁,也就是类的Class对象的锁,所以这里两个线程在同一时间也只会有一个获取到该类锁从而获得执行权。

3、实例方法中的同步块

直接看代码


<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(255, 255, 255); color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo; font-size: 9pt;">package com.jackie.thread;

public class RunWithSynchronizedBlock {

    public static void main(String[] args) {
        ObjectService service = new ObjectService();

        SynchronizedBlockThreadA a = new SynchronizedBlockThreadA(service);
        a.setName("a");
        a.start();

        SynchronizedBlockThreadB b = new SynchronizedBlockThreadB(service);
        b.setName("b");
        b.start();
    }

}

class ObjectService {

    public void serviceMethod() {
        try {
            synchronized (this) {
                System.out.println("begin time=" + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("end ? ?end=" + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class SynchronizedBlockThreadA extends Thread {

    private ObjectService service;

    public SynchronizedBlockThreadA(ObjectService service) {
        super();
        this.service = service;
    }

    @Override
 public void run() {
        super.run();
        service.serviceMethod();
    }

}

class SynchronizedBlockThreadB extends Thread {
    private ObjectService service;

    public SynchronizedBlockThreadB(ObjectService service) {
        super();
        this.service = service;
    }

    @Override
 public void run() {
        super.run();
        service.serviceMethod();
    }
}
</pre>

执行结果如下


begin time=1528625980467

end? ? end=1528625982471

begin time=1528625982471

end? ? end=1528625984472

这里的this就是ObjectService类的实例化对象,因为一个对象只有一个对象锁,所以这里可以保证同步,只有前一个线程执行完后,后一个线程才有机会执行。

4、静态方法中的同步块

参见2和3,只是在静态方法内部加上synchronized。本质还是类锁。

文中肯定有理解偏差的地方,写博客的好处就是,本来已经认为理所当然的地方,当需要一字一句写出来的时候,就会加深思考一些问题的细节。

好比文中没有加synchronized的例子,突然想到可见性,又想到主内存和工作内存以及堆栈之类的内存结构,虽然一度被绕晕,查了两小时的资料,最终也算是找了一套理论勉强把自己说服。

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

原文地址:https://www.cnblogs.com/bigdataZJ/p/concurrency-synchronized.html

时间: 2024-10-10 15:23:02

并发和多线程-八面玲珑的synchronized的相关文章

Java并发和多线程基础(一)

1.java线程状态 Java中的线程可以处于下列状态之一: NEW: 至今尚未启动的线程处于这种状态. RUNNABLE: 正在 Java 虚拟机中执行的线程处于这种状态. BLOCKED: 受阻塞并等待某个监视器锁的线程处于这种状态. WAITING: 无限期地等待另一个线程来执行某一特定操作的线程处于这种状态. TIMED_WAITING: 等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态. TERMINATED: 已退出的线程处于这种状态. 在给定时间点上,一个线程只能处于

java 多线程8 : synchronized锁机制 之 方法锁

脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量或者全局静态变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过的.注意这里 局部变量是不存在脏读的情况 多线程线程实例变量非线程安全 看一段代码: public class ThreadDomain13 { private int num = 0; public void addNum(String userName) { try { if ("

怎么理解分布式、高并发、多线程?(含面试题和答案解析)

看到分布式.高并发.多线程这三个词的时候,很多人是不是都认为分布式=高并发=多线程?当面试官问到高并发系统可以采用哪些手段来解决,或者被问到分布式系统如何解决一致性的问题,是不是一脸懵逼?确实,在一开始接触的时候,不少人都会分布式.高并发.多线程将三者混淆,误以为所谓的分布式高并发的系统就是能同时供海量用户访问,而采用多线程手段不就是可以提供系统的并发能力吗?实际上,他们三个总是相伴而生,但侧重点又有不同. 接下来我就看看分布式.高并发.多线程这三者之间到底有什么区别? 什么是分布式? 分布式更

多线程(一)高并发和多线程的关系

"高并发和多线程"总是被一起提起,给人感觉两者好像相等,实则 高并发 ≠ 多线程 多线程是完成任务的一种方法,高并发是系统运行的一种状态,通过多线程有助于系统承受高并发状态的实现.   高并发是一种系统运行过程中遇到的一种"短时间内遇到大量操作请求"的情况,主要发生在web系统集中大量访问或者socket端口集中性收到大量请求(例如:12306的抢票情况:天猫双十一活动).该情况的发生会导致系统在这段时间内执行大量操作,例如对资源的请求,数据库的操作等.如果高并发处

python并发编程&amp;多线程(一)

本篇理论居多,实际操作见:  python并发编程&多线程(二) 一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程 车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线 流水线的工作需要电源,电源就相当于cpu 所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位. 多线程(即多个控制线程)的概念

python并发编程&amp;多线程(二)

前导理论知识见:python并发编程&多线程(一) 一 threading模块介绍 multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性 官网链接:https://docs.python.org/3/library/threading.html?highlight=threading#(装B模式加载中…………) 二 开启线程的两种方式  方式一  方式二 三 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别  1 谁的开启速度快  2 瞅

1-3 Java并发与多线程基础

1.并发与多线程简介 最初计算机是单任务的,后来发展到可以并行运行多任务(进程),由操作系统来调度,每个任务可以获得一个时间片.多任务下,每个任务在使用系统资源结束后需要释放资源给其他任务. 后来,同一个任务内部发展出多个线程并发操作,会对相同的内存空间进行并发读写操作.更现代的计算机伴随着多核CPU的出现,也就意味着不同的线程能被不同的CPU核得到真正意义的并行执行.有些在多线程中出现的问题会和多任务以及分布式系统中出现的存在类似,因此该系列会将多任务和分布式系统方面作为参考,所以叫法上称为"

python并发编程--多线程2

并发编程--多线程2 实战部分: threading模块介绍 开启线程的两种方式 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别 练习 线程相关的其他方法 守护线程 python GIL(Global Interpreter Lock) 同步锁 死锁现象与递归锁 信号量Semaphore Evect 条件Condition 定时器 线程queue python标准模块-concurrent.futures 一.threading模块介绍 说明:threading用于提供线程相关的操作

Java多线程-同步:synchronized 和线程通信:生产者消费者模式

大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同步:synchronized 多个线程同时访问一个对象,可能造成非线程安全,数据可能错误,所谓同步:就是控制多个线程同时访就是控制多线程操作同一个对象时,注意是同一个对象,数据的准确性, 确保数据安全,但是加入同步后因为需要等待,所以效率相对低下. 如:一个苹果,自己一个人去咬怎么都不会出问题,但是