synchronized 关键字-监视器锁 monitor lock

1.代码示例:

package thread3;

import java.util.Scanner;

public class Test2 {
    public static Object object = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            while (true) {
                synchronized (object) {
                    System.out.println("请输入一个整数:");
                    int a = scanner.nextInt();
                    System.out.println("a: " + a);
                }
            }
        });
        thread1.start();

        Thread.sleep(2000);

        Thread thread2 = new Thread(() -> {
            synchronized (object) {
                System.out.println("hello thread2");
            }
        });
        thread2.start();
    }
}

 

 


 

 加锁的时候,如果针对不同的对象加锁,这就意味着俩个线程之间不会有任何的锁竞争.

2.synchronized 的特性

互斥

synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待.

  • 进入 synchronized 修饰的代码块, 相当于 加锁
  • 退出 synchronized 修饰的代码块, 相当于 解锁
     

可以粗略理解成, 每个对象在内存中存储的时候, 都存有一块内存表示当前的 "锁定" 状态(类似于厕所的 "有人/无人").
如果当前是 "无人" 状态, 那么就可以使用, 使用时需要设为 "有人" 状态.
如果当前是 "有人" 状态, 那么其他人无法使用, 只能排队 

 

一个线程先上了锁,其他线程只能等待这个线程释放! 

理解 "阻塞等待"
针对每一把锁, 操作系统内部都维护了一个等待队列. 当这个锁被某个线程占有的时候, 其他线程尝
试进行加锁, 就加不上了, 就会阻塞等待, 一直等到之前的线程解锁之后, 由操作系统唤醒一个新的
线程, 再来获取到这个锁.

注意:
上一个线程解锁之后, 下一个线程并不是立即就能获取到锁. 而是要靠操作系统来 "唤醒". 这
也就是操作系统线程调度的一部分工作.
假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B
和 C 都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B 比 C 先来的, 但是 B 不一定就能
获取到锁, 而是和 C 重新竞争, 并不遵守先来后到的规则.
 

synchronized的底层是使用操作系统的mutex lock实现的.
 

刷新内存

synchronized 的工作过程:

  • 获得互斥锁
  • 从主内存拷贝变量的最新副本到工作的内存
  • 执行代码
  • 将更改后的共享变量的值刷新到主内存
  • 释放互斥锁

 所以 synchronized 也能保证内存可见性. 具体代码参见后面 volatile 部分.

可重入

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

package thread1;

class Counter {
    public int count = 0;

    synchronized public void increase() {
        synchronized (this) {
            count++;
        }
    }
}

public class Test10 {
    private static Counter counter = new Counter();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        }, "thread1");

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        }, "thread2");

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println(counter.count);
    }
}

1.当调用increase的时候,先进行加锁操作,针对this来加锁(此时this就是一个锁定的状态了,把this对象头中的标志位给设置上了)

2.继续执行到下面的代码块的时候,也会尝试再次加锁,由于此时this已经是锁定状态了,按照之前的理解,这里的加锁操作就会出现阻塞,这里的阻塞啥时候结束呢?等到之前的代码把锁给释放了。要执行完这个方法,锁才能释放,但是由于此处的阻塞,导致当前这个方法没法继续执行了(僵住了)。

连续针对一个同一个对象(此处是this,也就是counter对象),加锁俩次,并且如果锁不是可重入的话,就会出现死锁。
 

Java设计锁的大佬就考虑到了这个情况~于是就把当前这里的synchronized设计成可重入锁了.针对同一个锁,连续加锁多次,不会有负面效果~~

锁中持有了两个信息:

  • 1.当前这个锁被哪个线程给持有了.
  • 2.当前这个锁被加锁几次了~~

当线程t已经加锁成功之后
后续再次尝试加锁,就会自动的判定出,当前这把锁就是t持有的.第二次加锁不会真的"加锁",而只是进行一个修改计数.( 1 ->2)后续往下执行的时候,出了synchronized 代码块,就触发一次解锁.(也不是真的解锁,而是计数-1)在外层方法执行完了之后,再次解锁,再次计数-1,计数减成0了,才真正的进行解锁~ 

死锁出现的情况,不仅仅是上述这一种情况,(针对同一把锁,连续加俩次)

1.一个线程,一把锁

2.俩个线程,俩把锁

3.N个线程,M把锁

哲学家就餐问题

这里会单独出一篇博客,实现一下哲学家就餐问题,先介绍一下这个问题~

 

该问题描述的是五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替的进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐完毕,放下筷子继续思考。

3.Java 标准库中的线程安全类

Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, 又没有任何加锁措施.

  • ArrayList
  • LinkedList
  • HashMap
  • TreeMap
  • HashSet
  • TreeSet
  • StringBuilder

但是还有一些是线程安全的. 使用了一些锁机制来控制.

  • Vector (不推荐使用)
  • HashTable (不推荐使用)
  • ConcurrentHashMap
  • StringBuffer

还有的虽然没有加锁, 但是不涉及 "修改", 仍然是线程安全的

  • String


 

 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
synchronized关键字Java中用于实现线程同步的关键字。它可以应用于方法或代码块,确保在同一时间只有一个线程能够访问被synchronized修饰的代码块或方法。 实现原理主要包括以下几个方面: 1. 监视器Monitor Lock):每个Java对象都有一个监视器,可以抽象地理解为对象内部的一种标记。当一个线程访问synchronized代码块或方法时,它会尝试获取对象的监视器,如果获取成功,则进入临界区执行代码;如果获取失败,则线程进入阻塞状态,直到获得为止。 2. 内存屏障(Memory Barrier):synchronized关键字不仅保证了互斥性(即同一时间只有一个线程能够执行synchronized代码块或方法),还保证了可见性和有序性。在释放之前,会将对共享变量的修改刷新到主内存中;在获取之前,会从主内存中重新读取共享变量的值,确保每个线程看到的共享变量值一致。 3. 重入性(Reentrancy):synchronized关键字是可重入的,即同一个线程可以多次获取同一个对象的监视器而不会发生死。每次获取时,的计数器会递增,释放时计数器递减,只有当计数器为0时才真正释放。 4. 互斥性(Mutual Exclusion):synchronized关键字保证了临界区的互斥性,同一时间只有一个线程能够执行被synchronized修饰的代码块或方法。其他线程需要等待当前占用的线程释放后才能继续执行。 需要注意的是,synchronized关键字只能用于同一个进程或线程内部的同步,不能用于不同进程或线程之间的通信和同步。在Java 5及之后,还引入了更灵活的Lock和Condition接口来替代synchronized关键字,提供了更多高级的线程同步操作和更细粒度的控制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿瞒有我良计15

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值