synchronized关键字在多线程并发编程中一直是元老级角色的存在,是学习并发编程中必须面对的坎,也是走向Java高级开发的必经之路。

一、synchronized性质

synchronized是Java提供的内置锁机制,有如下两种特性:

  • 互斥性:即在同一时间最多只有一个线程能持有这种锁。当线程1尝试去获取一个由线程2持有的锁时,线程1必须等待或者阻塞,知道线程2释放这个锁。如果线程2永远不释放锁,那么线程1将永远等待下去。

  • 可重入性:即某个线程可以获取一个已经由自己持有的锁。

二、synchronized用法

Java中的每个对象都可以作为锁。根据锁对象的不同,synchronized的用法可以分为以下两种:

  • 对象锁:包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己制定锁对象)

  • 类锁:指的是synchronized修饰静态的方法或指定锁为Class对象。

三、多线程访问同步方法的7种情况

本部分针对面试中常考的7中情况进行代码实战和原理解释。

1. 两个线程同时访问一个对象的同步方法

/**
* 两个线程同时访问一个对象的同步方法
*/
public class Demo1 implements Runnable { static Demo1 instance = new Demo1(); @Override
public void run() {
fun();
} public synchronized void fun() {
System.out.println(Thread.currentThread().getName() + "开始运行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
} public static void main(String[] args) {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) { }
System.out.println("finished");
}
}

结果:两个线程顺序执行。

解释:thread1和thread2共用一把锁instance;同一时刻只能有一个线程获取锁;thread1先启动,先获得到锁,先运行,此时thread2只能等待。当thread1释放锁之后,thread2获取到锁,进行执行。

2. 两个线程访问的是两个对象的同步方法

public class Demo2 implements Runnable{

    static Demo2 instance1 = new Demo2();
static Demo2 instance2 = new Demo2(); @Override
public void run() {
fun();
} public synchronized void fun() {
System.out.println(Thread.currentThread().getName() + "开始运行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
} public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) { }
System.out.println("finished");
}
}

结果: 两个线程并行执行。

解释:thread1使用的锁对象是instance1,thread2使用的锁对象是instance2,两个对象使用的锁对象不是同一个,所以线程之间互不影响,是并行执行的。

3. 两个线程访问的是synchronized的静态方法

public class Demo3 implements Runnable{

    static Demo3 instance1 = new Demo3();
static Demo3 instance2 = new Demo3(); @Override
public void run() {
fun();
} public static synchronized void fun() {
System.out.println(Thread.currentThread().getName() + "开始运行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
} public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) { }
System.out.println("finished");
}
}

结果:两个线程顺序执行。

解释:虽然两个线程使用了两个不同的instance实例,但是只要方法是静态的,对应的锁对象是同一把锁,需要先后获取到锁进行执行。

4. 同时访问同步方法与非同步方法

public class Demo4 implements Runnable {

    static Demo4 instance = new Demo4();

    @Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
fun1();
}else{
fun2();
}
} public synchronized void fun1() {
System.out.println(Thread.currentThread().getName() + "开始运行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "fun1运行结束");
} public void fun2() {
System.out.println(Thread.currentThread().getName() + "fun2开始运行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) { }
System.out.println("finished");
}
}

结果:两个线程并行执行。

解释:synchronize的关键字只对fun1起作用,不会对其他方法造成影响。也就是说同步方法不会对非同步方法造成影响,两个方法并行执行。

5. 访问同一个对象的不同的普通同步方法

public class Demo5 implements Runnable {

    static Demo5 instance = new Demo5();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            fun1();
        }else{
            fun2();
        }
    }     public synchronized void fun1() {
        System.out.println(Thread.currentThread().getName() + "开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "fun1运行结束");
    }     public synchronized void fun2() {
        System.out.println(Thread.currentThread().getName() + "fun2开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }     public static void main(String[] args) {
        Thread thread1 = new Thread(instance);
        Thread thread2 = new Thread(instance);
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {         }
        System.out.println("finished");
    }
}

结果:顺序执行。

解释:两个方法共用了instance对象锁,两个方法无法同时运行,只能先后运行。

6. 同时访问静态synchronized和非静态的synchronized方法

public class Demo6 implements Runnable{

    static Demo6 instance = new Demo6();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            fun1();
        }else{
            fun2();
        }
    }     public static synchronized void fun1() {
        System.out.println(Thread.currentThread().getName() + "开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "fun1运行结束");
    }     public synchronized void fun2() {
        System.out.println(Thread.currentThread().getName() + "fun2开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }     public static void main(String[] args) {
        Thread thread1 = new Thread(instance);
        Thread thread2 = new Thread(instance);
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {         }
        System.out.println("finished");
    }
}

结果:两个线程并行执行

解释:有static关键字,锁的是类本身;没有static关键字,锁的是对象实例;锁不是同一把锁,两个锁之间是没有冲突的;所以两个线程可以并行执行。

7. 方法抛异常后,会释放锁

public class Demo7 implements Runnable{

    static Demo7 instance = new Demo7();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            fun1();
        }else{
            fun2();
        }
    }     public synchronized void fun1() {
        System.out.println(Thread.currentThread().getName() + "开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        throw new RuntimeException();
        //System.out.println(Thread.currentThread().getName() + "fun1运行结束");
    }     public synchronized void fun2() {
        System.out.println(Thread.currentThread().getName() + "fun2开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }     public static void main(String[] args) {
        Thread thread1 = new Thread(instance);
        Thread thread2 = new Thread(instance);
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {         }
        System.out.println("finished");
    }
}

结果:thread1运行时遇到异常,并未运行结束,thread2开始运行,并运行至结束。

解释:方法抛出异常后,JVM自动释放锁。

8. 上述7种情况总结

3点核心思想:

  1. 一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待。

  2. 每个实例都对应有自己的一把锁,不同实例之间互不影响;例外:锁对象是.class以及synchronized修饰的是static方法的时候,所有对象共用同一把锁。

  3. 无论是方法正常运行完毕或者方法抛出异常,都会释放锁。

四、synchronized和ReentrantLock比较

虽然ReentrantLock是更加高级的锁机制,但是synchronized依然存在着如下的优点:

  1. synchronized作为内置锁为更多的开发人员所熟悉,代码简洁;

  2. synchronized较ReentrantLock更加安全,ReentrantLock如果忘记在finally中释放锁,虽然代码表面上运行正常,但实际上已经留下了隐患

  3. synchronized在线程转储中能给出在哪些调用帧中获得了哪些琐,并能够检测和识别发生死锁的线程。

五、总结

  1. synchronized关键字是Java提供的一种互斥的、可重入的内置锁机制。

  2. 其有两种用法:对象锁和类锁。

  3. 虽然synchronized与高级锁相比有着不够灵活、效率低等不足,但也有自身的优势:安全,依然是并发编程领域不得不学习的重要知识点。

用代码说话:synchronized关键字和多线程访问同步方法的7种情况的更多相关文章

  1. JAVA之旅(十三)——线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this

    JAVA之旅(十三)--线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this 我们继续上个篇幅接着讲线程的知识点 一.线程的安全性 当我们开启四个窗口(线程 ...

  2. synchronized关键字简介 多线程中篇(十一)

    前面说过,Java对象都有与之关联的一个内部锁和监视器 内部锁是一种排它锁,能够保障原子性.可见性.有序性 从Java语言层面上说,内部锁使用synchronized关键字实现 synchronize ...

  3. Synchronized关键字与多线程

    在java中,每一个对象有且仅有一个同步锁.这也意味着,同步锁是依赖于对象而存在.当我们调用某对象的synchronized方法时,就获取了该对象的同步锁.例如,synchronized(obj)就获 ...

  4. java之结合代码理解synchronized关键字

    为了保证数据的一致性即实现线程的安全性,java虚拟机提供了同步和锁机制.synchronized关键字是最基本的互斥同步手段.除此之外,还可以使用java.util.concurrent包中的重入锁 ...

  5. tomcat 访问400 的一种情况

    tomcat 高版本对访问url做了较高的校验,如果url中包含特殊字符,tomcat会自动拦截,返回400错误.如果要包含特殊字符,需要事先进行转译. 我原来用的apache-tomcat-6.0. ...

  6. Synchronized关键字整理

    Synchronized关键字整理 作用:能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全效果. 两个用法: 1.对象锁: 包括方法锁(默认锁对象为this当前实例对象)和同步代码块 ...

  7. Java 多线程 —— synchronized关键字

    java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...

  8. Java多线程学习(二)synchronized关键字(2)

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79670775 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...

  9. Java多线程学习(二)synchronized关键字(1)

    转载请备注地址: https://blog.csdn.net/qq_34337272/article/details/79655194 Java多线程学习(二)将分为两篇文章介绍synchronize ...

随机推荐

  1. GLFW+GLEW搭建opengl环境(备忘)

    使用VS2017社区版本(免费版) 下载GLFW和GLEW源码. 使用CMAKE生成工程文件 打开右击GLFW和GLEW项目编译 GLFW默认是静态库 编译GLEW时调整为静态库.将生成的lib和源码 ...

  2. UVA11988 【Broken Keyboard (a.k.a. Beiju Text)】:题解

    题目链接:https://www.luogu.org/problemnew/show/UVA11988 这题虽说是和链表有关的模拟,但其实并不是很需要啊,但蒟蒻用了(说的好听是练手,说的难听是太弱), ...

  3. Excel催化剂开源第25波-Excel调用百度AI,返回AI结果

    现成的这些轮子,无需调用网页,直接本地离线即可生成). 当然在AI时代,少不了各种AI接口的使用场景,普通开发者只需聚焦在自己的业务场景上,这些AI底层技术,只需类似水煤电一般去BAT这些大厂那里去消 ...

  4. Hadoop之WritableComprale 排序

    Hadoop之WritableComprale 排序 Hadoop只对key进行排序 排序是 MapReduce 框架中最重要的操作之一.Map Task 和 Reduce Task 均会对数据(按照 ...

  5. fiddle知识点六、如何使用fiddle进行模拟弱网

    为什么要模拟弱网 随着互联网的快速发展,越来越多的应用核心功能需要网络进行实现.同一应用在2G.3G.4G和WiFi的不停网络下,响应各有不同.但是因为现在的网络普遍为4G网络,为了保证应用在不同的网 ...

  6. Kotlin学习快速入门(1)——基本数据类型以及String常用方法使用

    本文适合有Java基础的人 Kotlin语法特点 相比java,省略括号,可以自动判断类型,省略new关键字,空指针捕获 主函数 kotlin文件(kt文件)中,只有要下列的方法,就可以运行,无需像之 ...

  7. windos10专业版激活(可用)

    电脑提示Windows许可证即将到期,于是自己就在网上找了一些教程,但是并没有激活成功,反而由即将到期变为了通知状态,尝试了各种密钥都不行,也下载了激活工具如暴风激活工具,KMS都不管用,尝试了好多方 ...

  8. 用margin还是padding ?

    margin是用来隔开元素与元素的间距:padding是用来隔开元素与内容的间隔. margin用于布局分开元素使元素与元素互不相干:padding用于元素与内容之间的间隔,让内容(文字)与(包裹)元 ...

  9. java 第二章

    变量:变量就是代表程序运行时存放数据的地方 数据存放在:磁盘,内存卡,U盘,光盘,内存条,固态硬盘,机械硬盘等 字节:8个二进制位构成1个"字节(Byte)",它是存储空间的基本计 ...

  10. [系列] Go gRPC Hello World

    目录 概述 四类服务方法 安装 写个 Hello World 服务 推荐阅读 概述 开始 gRPC 了,这篇文章学习使用 gRPC,输出一个 Hello World. 用 Go 实现 gRPC 的服务 ...