Java synchronized 关键字详解

前置技能点

  • 进程和线程的概念
  • 线程创建方式
  • 线程的状态状态转换
  • 线程安全的概念

synchronized 关键字的几种用法

  1. 修饰非静态成员方法

    synchronized public void sync(){
    
    }
  2. 修饰静态成员方法

    synchronized public static void sync(){
    
    }
  3. 类锁代码块

    synchronized (类.class){
    
    }
  4. 对象锁代码块

    synchronized (this|对象){
    
    }

synchronized 修饰非静态方法时可以看做是锁 this 对象,修饰静态方法时可以看做是锁方法所在的类。

synchronized 关键字的根本机制

各个线程想要访问被 synchronized 修饰的代码块,就要取得 synchronized 声明的锁。如果两个线程的目标是同一个锁,就会出现阻塞的现象,所以两个线程不能同时访问同一个锁下的代码,保证了多线程在执行时最终结果不会出错。这与共享变量是否为静态无关。

几个例子

对象锁

public class ThreadDemo extends Thread {
@Override
public synchronized void run() {
for (int i = 0; i < 10000; i++) {
Main.i++;
}
System.out.println("执行完成");
}
}

直接将继承的 run() 方法标记为 synchronized ,作用是对 Main 类中的 i 变量做 10000 次累加操作。

public class Main {
static int i = 0; public static void main(String[] args) throws InterruptedException {
ThreadDemo threadDemo=new ThreadDemo();
Thread t1 = new Thread(threadDemo);
Thread t2 = new Thread(threadDemo);
Thread t3 = new Thread(threadDemo);
Thread t4 = new Thread(threadDemo); t1.start();
t2.start();
t3.start();
t4.start(); t1.join();
t2.join();
t3.join();
t4.join();
System.out.println(i);
}
}
//输出结果:
//执行完成
//执行完成
//执行完成
//执行完成
//40000

可以看到当4个线程全部执行完毕之后,变量 i 成功的累加了 40000 次,没有出现丢失操作的情况。

如果我们将 main() 方法修改如下:

public static void main(String[] args) throws InterruptedException {
Thread t1 = new ThreadDemo();
Thread t2 = new ThreadDemo();
Thread t3 = new ThreadDemo();
Thread t4 = new ThreadDemo(); t1.start();
t2.start();
t3.start();
t4.start(); t1.join();
t2.join();
t3.join();
t4.join();
System.out.println(i);
}
//输出结果:
//执行完成
//执行完成
//执行完成
//执行完成
//27579

可以看到丢失了不少的累加操作。观察前后两个 main() 方法创建线程的方式可以发现,前面的 main() 方法是使用了同一个对象来创建了4个不同的线程,而后一个 main() 方法使用了4个不同的 ThreadDemo 对象创建了4个线程。我们用 synchronized 修饰的是一个非静态成员函数,相当于对该方法创建了 this 的对象锁。在第一个 main() 方法中使用同一个对象来创建 4 个不同线程就会让 4 个线程争夺同一个对象锁,这样,在同一时间内,仅能有一个线程能访问 synchronized 修饰的方法。而在第二种 main() 方法中,4 个线程各自对应一个对象锁,4 个线程之间没有竞争关系,对象锁自然无法生效。

类锁

public class ThreadDemo extends Thread {
@Override
public void run() {
synchronized (ThreadDemo.class) {
for (int i = 0; i < 10000; i++) {
Main.i++;
}
System.out.println("执行完成");
}
}
}

将修饰方法的 synchronized 改为对 ThreadDemo.class 上锁的代码块

public class ThreadDemo2 extends Thread {
@Override
public void run() {
synchronized (ThreadDemo2.class) {
for (int i = 0; i < 10000; i++) {
Main.i++;
}
System.out.println("执行完成");
}
}
}

再创建一个相同的类命名为 ThreadDemo2 ,与 ThreadDemo 不同的是,ThreadDemo2 中,synchronized 对 ThreadDemo2.class 上锁。

public static void main(String[] args) throws InterruptedException {
Thread t1 = new ThreadDemo();
Thread t2 = new ThreadDemo();
Thread t3 = new ThreadDemo2();
Thread t4 = new ThreadDemo2(); t1.start();
t2.start();
t3.start();
t4.start(); t1.join();
t2.join();
t3.join();
t4.join();
System.out.println(i);
}
//输出结果:
//执行完成
//执行完成
//执行完成
//执行完成
//33054

4 个线程分别由 ThreadDemo 和 ThreadDemo2 来创建,显然得到的结果与预期的 40000 不符。如果我们将 ThreadDemo2 中的 synchronized 改为对 ThreadDemo.class 上锁:

public class ThreadDemo2 extends Thread {
@Override
public void run() {
synchronized (ThreadDemo.class) {
for (int i = 0; i < 10000; i++) {
Main.i++;
}
System.out.println("执行完成");
}
}
}
//输出结果:
//执行完成
//执行完成
//执行完成
//执行完成
//40000

可以看到,虽然是声明在两个不同的类中的 synchronized 代码块,但是由于都是对 ThreadDemo.class 上锁,所以 4 个线程之间还是建立了竞争关系,同时只能有一个线程访问被 synchronized 修饰的代码。

总结

所以 synchronized 关键字的本质是限制线程访问一段代码,而限制的条件就是,在所有被加上相同锁的代码上,同一时间,只能有一个线程在运行。这与你要修改什么样的共享变量无关。在我刚接触到的时候以为类锁和对象锁是分别针对静态共享变量和非静态共享变量的,但事实上锁的是要执行的代码块,而不是代码块将要访问的共享变量。

Java synchronized 关键字详解的更多相关文章

  1. [java] java synchronized 关键字详解

    Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一 ...

  2. Java多线程(三)—— synchronized关键字详解

    一.多线程的同步 1.为什么要引入同步机制 在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源.必须对这种潜在资源冲突进行预防. 解决方法:在线程使用一个资源时为其加锁即可. 访问资 ...

  3. “全栈2019”Java多线程第十六章:同步synchronized关键字详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  4. Java volatile关键字详解

    Java volatile关键字详解 volatile是java中的一个关键字,用于修饰变量.被此关键修饰的变量可以禁止对此变量操作的指令进行重排,还有保持内存的可见性. 简言之它的作用就是: 禁止指 ...

  5. Java 多线程(六) synchronized关键字详解

    多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问时可能出现的问题. 同步机制可以使用synchronized关键字实现. 当synchroniz ...

  6. 从线程池到synchronized关键字详解

    线程池 BlockingQueue synchronized volatile 前段时间看了一篇关于"一名3年工作经验的程序员应该具备的技能"文章,倍受打击.很多熟悉而又陌生的知识 ...

  7. java synchronized 同步详解

    记下来,很重要. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchron ...

  8. 简单的互斥同步方式——synchronized关键字详解

    目录 1. 关于synchronized关键字 2. synchronized的原理和实现细节 2.1 synchronized可以用在那些地方 2.2 synchronized是如何实现线程互斥访问 ...

  9. synchronized关键字详解(二)

    synchronized关键字的性质 1.可重入:同一线程的外层函数获得锁之后,内层函数可直接再次获得该锁,好处:避免死锁,提升封装性 证明可重入粒度:1.同一个方法是可重入的 2.可重入不要求是同一 ...

随机推荐

  1. 解决android sdk无法更新 更新慢的问题

    使用不同平台开发android应用的时候都要先搭建开发环境. 这里介绍一下搭建开发环境过程中更新和下载android sdk的一种方法: 第一步:配置android sdk manager的代理服务, ...

  2. [UVA1494] Qin Shi Huang's National Road System

    题目 戳这里 题解 从今天起我要改邪归正,好好刷题准备联赛! 这是一道经典的最小生成树题目. 枚举每一条边作为道士要修的路,求出包含这条边的最小生成树. 先求出原图的最小生成树. 如果要删的边在最小生 ...

  3. chrome最耐看的主题

    google chrome最耐看的主题James White大家可以尝试一下

  4. 异想家Golang学习笔记

    1. 简介 官网:https://golang.google.cn/ 2. 编译器.工具链 编译 go build .\demo.go 编译和执行指令合二为一 go run demo.go 3. 注释 ...

  5. Java 数据持久化系列之池化技术

    在上一篇文章<Java 数据持久化系列之JDBC>中,我们了解到使用 JDBC 创建 Connection 可以执行对应的SQL,但是创建 Connection 会消耗很多资源,所以 Ja ...

  6. 工具之grep

    转自:http://www.cnblogs.com/dong008259/archive/2011/12/07/2279897.html grep (global search regular exp ...

  7. Ubuntu安装openjdk8

    sudo apt-get update sudo apt-get install openjdk-8-jdk 通过 which java 找到java安装路径 添加环境变量 sudo vim ~/.b ...

  8. CUDA 计算pi (π)

    通过简单的程序设计熟练CUDA的使用步骤 下面是cuda代码及相关注释 #include <stdio.h> #include <iostream> #include < ...

  9. GO异常 | runnerw.exe: CreateProcess failed with error 21

    背景 今天创建了一个GO项目,写了几行代码 package chapter1 import "fmt" func main() { fmt.Println("hello ...

  10. webdriver高级应用 -无人干预地自动上传文件

    本节主要介绍通过程序代码无人干预地上传文件附件,并进行提交操作. 1.使用send_keys方法上传文件 #!/usr/bin/env python # -*- coding: utf-8 -*- # ...