Java synchronized 关键字详解

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

原文地址:https://www.cnblogs.com/supermaskv/p/12375331.html

时间: 2024-10-27 10:56:22

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

[java] java synchronized 关键字详解

Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块.然而,当一个线程访问object的一个加锁代码块时,另一个线程仍然可以访问该object中的非加锁代码块. 一.synchronized同步方法 1.synchronized 同步方法,为对象锁 publ

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

Java 多线程(六) synchronized关键字详解 多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问时可能出现的问题. 同步机制可以使用synchronized关键字实现. 当synchronized关键字修饰一个方法的时候,该方法叫做同步方法. 当synchronized方法执行完或发生异常时,会自动释放锁. 下面通过一个例子来对synchronized关键字的用法进行解析. 1.是否使用synchronized关键字的不同 例子

synchronized关键字详解

1.    把synchronized当作函数修饰符时,示例代码如下:public synchronized void method(){//-.}这也就是同步方法,那这时synchronized锁定的是哪个对象呢?他锁定的是调用这个同步方法对象.也就是说,当一个对象P1在不同的线程中执行这个同步方法时,他们之间会出现阻塞,达到同步的效果.但是这个对象所属的Class所产生的另一对象P2却能够任意调用这个被加了synchronized关键字的方法. 2.同步块,示例代码如下:public voi

synchronized关键字详解(二)

synchronized关键字的性质 1.可重入:同一线程的外层函数获得锁之后,内层函数可直接再次获得该锁,好处:避免死锁,提升封装性 证明可重入粒度:1.同一个方法是可重入的 2.可重入不要求是同一个方法 3.可重入不要求是同一个类 可重入原理:加锁次数计数器 JVM负责跟踪对象被加锁的次数: 线程第一次给对象加锁的时候计数变为1,每当这个相同的线程在这个对象上加锁时,计数递增: 每当任务离开时,计数会减1,计数为0时,锁被完全释放. 2.不可中断:如果一个线程拿到一把锁,另一个线程要想获得这

8.Java this关键字详解

this 关键字用来表示当前对象本身,或当前类的一个实例,通过 this 可以调用本对象的所有方法和属性.例如: public class Demo{ public int x = 10; public int y = 15; public void sum(){ // 通过 this 点取成员变量 int z = this.x + this.y; System.out.println("x + y = " + z); } public static void main(String[

java-this关键字详解

Java this关键字详解 this 关键字用来表示当前对象本身,或当前类的一个实例,通过 this 可以调用本对象的所有方法和属性.例如: public class Demo{ public int x = 10; public int y = 15; public void sum(){ // 通过 this 点取成员变量 int z = this.x + this.y; System.out.println("x + y = " + z); } public static vo

java关键字详解

Java关键字及其作用 目录 Java关键字及其作用--- 1 一.     关键字总览:2 二.     详细解释--- 3 1.访问控制--- 3 1)私有的-- 3      private 2)受保护的-- 3      protected 3)公共的-- 3      public 2.类.方法和变量修饰符--- 3 1)声明抽象-- 3      abstract 2)类-- 4      class 3)继承.扩展-- 4      extends 4)最终.不可改变-- 4   

“全栈2019”Java异常第九章:throws关键字详解

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异常第九章:throws关键字详解 下一章 "全栈2019"Java异常第十章:throw与throws区别详解 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复"Ja

Java多线程编程详解

线程的同步 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问. 由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块. 1. synchronized 方法:通过在方法声明中加入 synch