synchronized关键字讲解

简介: synchronized关键字讲解

“ 在Java中使用 synchronized 关键字对线程加锁 !!

在正式学习 synchronized 关键字之前,我们一定要学会读它(掌握发音)!!!

synchronized百度翻译

1、synchronized 简单介绍

(1)为什么要用 synchronized  

      当两个线程同时对一个变量进行修改时,由于修改可能不是原子操作,就会导致一个一个线程正在操作时,另一个线程突然插入,导致第一个线程修改失败。


      而使用 synchronized 关键字就可以避免这种情况,把 synchronized 作用于该变量。当一个线程对该变量进行修改时,该进行就会对该变量进行加锁,另一个线程再进行操作时,就会出现互斥无法打断该操作。


举个例子:有两个男生A 和 B 同时追一个妹子,当妹子还是单身的时候,她可以接受两个男生的告白。一旦 A 追上了妹子,那 B 就不能给妹子表白,知道 A 和妹子分手。这种情况就相当于对妹子进行上锁 !!!      

(2)用synchronized 加锁的目的

       使用 synchronized 加锁的是让多个线程争一个对象,让线程在执行过程中阻塞等待,从而保证了线程安全!!!

(3)synchronized 加锁对象

       使用 synchronized 加锁时,我们需要确定一个对象,该对象也被称为锁对象。在 java 中任何一个对象,都可以作为锁对象!!!


       例如:成员变量、局部变量、静态变量、类对象....


       这些不同形态的对象,作为锁对象的时候没有任何区别,锁对象是指用来控制线程之间互斥的。


针对一个锁对象加锁,就会出现互斥。针对不同对象加锁,就不会互斥。    


        下面将根据不同的对象展示synchronized的用法

2、synchronized 几种用法

       我们先来看一个多线程修改一个对象的例子,如下两个线程对 counter 对象分别进行5w次修改,最后 count 预期结果应为 10w

class Counter{
    public int count = 0;
    public void increase(){
        count++;
    }
}
public class Demo2 {
    private static Counter counter = new Counter();
    public static void main(String[] args) throws InterruptedException {
        //搞两个线程,每个线程都针对这个 counter 来进行 5w 次自增
        // 预期结果 10w!!
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                counter.increase();
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

结果如下:


这就是两个线程同时修改一个变量所造成的问题,下面我将在不同地方加入 synchronized,向大家展示该效果

(1)修饰代码块

明确指定那个对象

 synchronized(锁对象){
     //...
 }

1. this 对象

synchronized 里面写的锁对象是 this,也就相当于谁调用 increase,就针对谁进行加锁

此时我们再来观察代码结果为:1w

此时就说明,两个线程在执行过程中,产生了互斥也就是阻塞等待,一个线程执行完后,另一个线程才能执行!!

2. object对象创建一个 object 对象进行加锁

 此时我们再来观察代码结果为:1w

注意加 object 对象跟加 this对象效果一样,大部分情况在我们都是采用this对象

3. 类对象

添加类对象,相当于对整个类加锁

类对象: 类名.class

public synchronized void increase(){
        synchronized (Counter.class){
            count++;
        }
    }

结果也为1w,也能起到线程阻塞等待的效果。

(2)修饰普通方法

锁的 Counter 对象,这里跟修饰代码块中所this对象的效果是一样的。

(3)修饰静态方法

锁的 Counter 类的对象,为了方便展示,我把代码改成如下:

public class Counter {
    static int count;
    public synchronized static void increase(){
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                increase();
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

结果如下:


3、synchronized 特性

简单了解后,现在我们来具体看一下synchronized的几个特性

(1)互斥

       这个特性我们应该很熟悉了,synchronized 会起到互斥效果,某个线程执行到某个对象的 synchronized 中时,其他线程如果也执行到同一个对象 synchronized 就会阻塞等待。


加锁:进入 synchronized 修饰的代码块

解锁:退出 synchronized 修饰的代码块

注意:synchronized 用的锁是存在于Java对象里面的,也可以理解为,每个对象在内存中存储的时候,都存在一块内存表示当前的“锁定”状态。


 举个例子:“对象”比作公厕,锁门和开门代表厕所的两种状态(“有人/无人”)

89dedbd5cfe74826f9020927ba465076_0dd4b15bdedb4e72b262cb00f9127f14.png

   当一个线程占用厕所(“对象”),并上锁时,其他线程就只能排队等待(阻塞),直到这个线程释放该“对象”(释放)。

拓展:

上一个线程解锁之后,下一个线程并不是立即就能获取到锁,而是靠操作系统来“唤醒”,这也就是操作系统调度的一部分工作。

假设有 A B C 三个线程,线程 A 先获取到锁,然后 B 尝试获取锁,C 在 B之后尝试获取锁,此时 B 和 C 都在阻塞队列中排队等待。但当 A 释放锁之后,虽然 B 比 C 先来,但是 B 不一定就能获取到锁,而是和 C 重新竞争,并不遵守先来后到的规则。

(2)刷新内存

   synchronized 的工作过程


获得互斥锁

从主内存拷贝变量的最新副本到工作内存

执行代码

将更改后的共享变量的值刷新到主内存

释放互斥锁

   从上述可知,synchronized 拥有跟 volatile 关键字一样的效果,保证内存可见性,这里就不多加已说明了。(了解即可)

(3)可重入

synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题


什么是“不可重复”


      当一个线程加锁之后没有释放锁,然后又再次尝试加锁,按照对锁的设定,第二次加锁的时候就会阻塞等待。直到第一次的锁被释放,才能获取到第二个锁。


     但是释放第一个锁也是由该线程来完成的,但是这个线程正在阻塞等待,已经基本躺平,也就无法进行解锁操作。这时候就会死锁。


这样的锁就叫 不可重复锁


但是,在Java 中的使用 synchronized 上的锁是可重入的!!

例如:

       下面代码,对 increase1 和 increase3 两个方法都加了 synchronized,都是针对this对象(counter)加锁。

       线程执行 increase1 时对this加一次锁,当线程执行到 increase3 时对this又加一次锁。

       大家可以运行以下看看结果为几。

public class Counter {
    static int count;
    public synchronized void increase1(){
        increase2();
    }
    public void increase2(){
        increase3();
    }
    public synchronized void increase3(){
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t = new Thread(()->{
            counter.increase1();
        });
        t.start();
        t.join();
        System.out.println(count);
    }
}


这串代码的结果为1,说明线程并没有被自己锁死,反而能进入再次进入自己加的锁内。这也就证明了 synchronized 的可重入性。


拓展:

在可重入锁的内部,包含了 “线程持有者” 和 “计数器” 两个信息

如果某个线程加锁的时候,发现锁已经被人占了,但是恰好占用的正式自己,那么仍然可以继续获取到锁,并让计数器自增。

解锁的时候计数器递减为0的时候,才真正释放锁(才能被别的线程获取到)


相关文章
|
安全 Java
【Synchronized关键字】
【Synchronized关键字】
|
3月前
|
安全 Java
synchronized关键字
在Java中,`synchronized`确保多线程安全访问共享资源。应用于实例方法时,锁绑定于对象实例,仅阻止同一对象的其他同步方法访问;应用于静态方法时,锁绑定于整个类,阻止该类所有同步静态方法的同时访问。实例方法锁作用于对象级别,而静态方法锁作用于类级别,后者影响所有对象实例。正确应用可避免并发问题,提升程序稳定性和性能。
|
2月前
|
存储 安全 Java
Java并发编程之深入理解Synchronized关键字
在Java的并发编程领域,synchronized关键字扮演着守护者的角色。它确保了多个线程访问共享资源时的同步性和安全性。本文将通过浅显易懂的语言和实例,带你一步步了解synchronized的神秘面纱,从基本使用到底层原理,再到它的优化技巧,让你在编写高效安全的多线程代码时更加得心应手。
|
3月前
|
Java 程序员 开发者
Java并发编程之深入理解synchronized关键字
本文旨在探究Java语言中一个核心且经常被误解的并发控制工具——synchronized关键字。通过分析其内部机制、使用场景和性能考量,我们将揭示这个简单关键字背后隐藏的强大功能和潜在陷阱。文章将引导你重新认识synchronized,并学会如何在实际开发中高效利用它来构建健壮的多线程应用程序。
|
6月前
|
安全 Java 调度
Java多线程- synchronized关键字总结
Java多线程- synchronized关键字总结
49 0
|
6月前
|
安全 Java 程序员
synchronized关键字与ReentrantLock的区别和应用
synchronized关键字与ReentrantLock的区别和应用
38 0
|
存储 安全 Java
JUC第五讲:关键字synchronized详解
JUC第五讲:关键字synchronized详解
|
安全 Java 编译器
volatile 与 synchronized 关键字的区别?
volatile 与 synchronized 关键字的区别?
54 0
|
缓存 Java 编译器
深入理解synchronized关键字
synchronized关键字详解
88 0
|
存储 安全 Java
深入理解synchronized关键字(二)
深入理解synchronized关键字(二)
129 0
深入理解synchronized关键字(二)