【JavaEE】_synchronized关键字——监视器锁monitor lock

目录

[1. synchronized的特性](#1. synchronized的特性)

[2. synchronized的使用](#2. synchronized的使用)

[3. Java标准库中的线程安全类](#3. Java标准库中的线程安全类)


1. synchronized的特性

(1)互斥:

前文已经介绍,某个线程执行到某个对象的synchronized中时,其他线程如果也执行到同一个对象,synchronized就会阻塞等待,进入synchronized修饰的代码块相当于加锁,退出synchronized修饰的代码块相当于解锁;

(2)刷新内存:

前文介绍内存可见性时已经提到:synchronized的工作过程是:

① 获得互斥锁,② 从内存拷贝变量的最新内存到寄存器,③ 执行代码,

④ 将更改后的共享变量的值刷新到寄存器,⑤ 释放互斥锁;

故而synchronized也可以保证内存的可见性;

(3)可重入:

同一个线程针对同一个锁连续加两次,如果出现了死锁就是不可重入,不会死锁就是可重入;

(3.1)死锁:

java 复制代码
class Counter{
    public int count=0;
    synchronized public void increase(){
        synchronized (this){
            count++;
        }
    }
}

外层先加了一次锁,内层又对同一对象再次加锁,此时由于外层锁需要执行完内部代码才能解锁,而内层锁需要等待已经先锁的外层锁解锁后才能执行,此时就会形成死锁;

(3.2)可重入锁:

为了解决这个问题,JVM内部将synchronized实现为可重入锁。

可重入锁会记录当前占用锁的线程以及加锁次数,线程a第一次加锁成功后,锁内部就会记录当前占用锁的线程为a,同时加锁次数为1,后续线程a再加锁时,进行的加锁操作就非真实的加锁操作而是一个伪加锁,是没有实质影响的,只是将加锁次数增加为2;

代码执行完毕解锁时,会将计数-1,当锁的计数减到0时,才会真的解锁;

可重入锁降低了程序员的编写负担,降低了使用成本,提高了开发效率,但同时由于需要维护锁所属的线程以及加减计数会降低运行效率,程序的开销也会更大;

(3.3)死锁的必要条件:

① 互斥使用:一个锁被一个线程占用后,其他线程就无法占用;

② 不可抢占:一个锁被一个线程占用后,其他线程不能抢占该锁;

③ 请求与保持:当一个线程占据了多把锁之后除非显式释放锁,否则这些锁始终被该线程持有;

(以上三条都是锁本身的特点)

④ 环路等待:等待关系成环;

在实际开发中需要避免死锁,关键还是从避免环路等待入手:

针对多把锁加锁时约定好固定的顺序,就可以避免等待关系成环;

但实际情况中很少出现一个线程套锁的问题;

2. synchronized的使用

使用synchronized的本质是修改了Object对象中"对象头"内的一个标记;

(1)直接修饰普通方法:此时锁对象是this:

java 复制代码
class Counter{
    public int count=0;
    synchronized public void increase(){
        count++;
    }
}

当两个线程同时对同一个对象进行加锁的时候才存在竞争;

(2)修饰一个代码块:需要显式指定锁对象:

java 复制代码
class Counter{
    public int count=0;
    public void increase(){
        synchronized (this){
            count++;
        }
    }

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

counter.increase()表示针对this对象进行加锁操作;

注:任何对象都可以进行加锁是java语言的特色;

(3)修饰一个静态方法:

静态方法其实就是类方法,普通方法就是实例方法;故而

synchronized修饰一个静态方法就相当于针对当前类的类对象加锁

java 复制代码
class Counter{
    synchronized public static void func1(){
        
    }
    public static void fun2(){
        synchronized (Counter.class){
            
        }
    }
}

如在上文代码中,静态方法func1是表示为synchronized修饰的静态方法,其效果等效于静态方法func2;

注:(1)类对象就是运行程序时.class文件被加载到JVM内存中时的形态;

(2)使用synchronized很容易造成线程阻塞,一旦线程阻塞,此时放弃CPU,再次回到CPU的时间就不可控了,一旦代码中使用了synchronized,则"高性能"几乎无法实现;

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

Java标准库中已经实现的类中有些是线程安全的,有些是线程不安全的:

线程不安全类:

①ArrayList ②LinkedList ③HashMap ④TreeMap ⑤HashSet ⑥TreeSet ⑦StringBuilder

线程安全类:

①Vector(不推荐) ②HashTable(不推荐)③ConcurrentHashMap ④StringBuffer ⑤String

注:(1)线程安全类由于一些关键方法都被synchronized修饰,保证了多线程环境下修改同一个对象不会出现线程不安全问题;

(2)String是线程安全类不是因为synchronized修饰,而是因为String是不可变对象,不存在多线程中修改造成的线程不安全问题;

同时请注意不可变对象与常量以及final没有必然联系:

不可变对象是指在该类中没有提供public的修改属性的方法,final修饰类表示类不可继承;

相关推荐
九圣残炎9 分钟前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge12 分钟前
Netty篇(入门编程)
java·linux·服务器
Re.不晚39 分钟前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐1 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven
码农派大星。1 小时前
Spring Boot 配置文件
java·spring boot·后端
顾北川_野1 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
江深竹静,一苇以航1 小时前
springboot3项目整合Mybatis-plus启动项目报错:Invalid bean definition with name ‘xxxMapper‘
java·spring boot
confiself1 小时前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
Wlq04151 小时前
J2EE平台
java·java-ee
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee