Java中的监视器锁 (synchronized 关键字)

本文详细介绍了Java中synchronized关键字的特性,包括互斥、内存刷新和可重入性,并探讨了synchronized锁的对象类型。此外,还区分了Java标准库中线程安全与不安全类的使用案例。
摘要由CSDN通过智能技术生成

在使用多线程的时候,我们会经常遇到线程不安全的问题,即多个线程访问共享数据时出现不确定的结果或异常,此时引入我们今天要介绍的synchronized 关键字


一.synchronized 的特性

1) 互斥

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

进入 synchronized 修饰的代码块, 相当于 加锁

退出 synchronized 修饰的代码块, 相当于 解锁

public class SynchronizedTest {
    //进入increase方法内部->加锁
    //此时其他对象访问increase方法时将会停滞等待
    synchronized void increase(int count){
        count++;
    }
    //此时increase方法执行完毕->解锁
    //其它对象可以访问increase方法
}

2) 刷新内存

synchronized 的工作过程:  

1. 获得互斥锁

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

3. 执行代码

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

5. 释放互斥锁 所以 synchronized 也能保证内存可见性.

(内存可见性是指当一个线程修改了共享变量的值后,其他线程能够立即看到这个修改后的值)

3) 可重入

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

自己锁自己:对于某些锁,在加锁后没有释放,而又加了一个锁后,会与前面的锁互斥而锁死

而synchronized是可重入锁,不会出现上述问题

public class SynchronizedTest {
    synchronized void increase1(int count){
        count++;
    }
    synchronized void increase2(int count){
        increase1(count);
    }

    public void test() {
        int count=0;
        increase2(count);
        //此时会先调用increase2(),再调用increase1(),
        //因为synchronized是可重入锁,所以不会出现卡死现象
    }
}

可重入锁的原理:

在可重入锁的内部, 包含了 "线程持有者" "计数器" 两个信息. 如果某个线程加锁的时候, 发现锁已经被人占用, 但是恰好占用的正是自己, 那么仍然可以继续获取到锁, 并让计数器自增. 解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)

二.synchronized 所锁的对象

我们说当不同线程获取同一把锁时,才会出现锁互斥,才能保证线程安全,那么怎样判断是不是同一把锁呢?

在上面的例子中,synchronized关键字都是修饰在increase方法中的,那么锁的对象是increase()方法吗?显然不是,锁的是调用increase()方法的对象;

现在我们分几种情况讨论锁的对象有哪些:

1) 修饰普通方法:

锁的是调用普通方法的对象

class Add{
    public synchronized void increase3(int count){
        count++;
    } 
}

class Test{
    public static void main(String[] args) {
        int count=0;
        //将Add类实例化,得到对象add
        Add add=new Add();
        //使add对象调用increase3方法
        add.increase3(count);
        //此时锁的对象就是add
    }
}

2) 修饰静态方法:

锁的是方法所属的类的对象

//这是一个类,它的名称为Add,里面有一个被synchronized修饰的静态方法
//注意:方法increase3已改为静态方法
class Add{
    public static synchronized void increase3(int count){

        count++;

    }
}

class Test{
    public static void main(String[] args) {

        int count=0;
        
        //静态方法可以不用实例化而直接调用

        Add.increase3(count);

        //此时锁的对象为Add类,本质是Add的class,即Add.class
    }
}

3) 修饰代码块:

自己明确指定锁哪个对象

我们不一定要用锁去修饰方法,而是可以修饰方法中的某一部分,即修饰自己想要修饰的代码

同时,我们就要自己选择所锁的对象了

1.锁当前对象
class Add2{
    public void increase4(int count){

        System.out.println("下面是我们要修饰的代码块");

        //this即调用increase4的对象
        synchronized (this){
            count++;
        }
    }
}
class Test{
    public static void main(String[] args) {

        Add2 add2=new Add2();
        add2.increase4(count);
        //此时锁的对象就是add2
        //和第一种本质上是一样的,只是锁所管辖的范围不一样了而已

    }
}
2.锁类对象
class Add2{
    public static void increase4(int count){

        System.out.println("下面是我们要修饰的代码块");

        //锁的对象是Add.class对象
        synchronized (Add2.class){
            count++;
        }

    }
}
class Test{
    public static void main(String[] args) {
       
        Add2.increase4(count);

        //此时锁的对象为Add类,本质是Add的class,即Add.class
        //和第二种本质上是一样的,同样是锁所管辖的范围不一样了而已

    }
}

我们重点要理解,synchronized 锁的是什么. 两个线程竞争同一把锁, 才会产生阻塞等待.

此时我们就可以更好的理解可重入锁中所描述的同一个对象是什么了

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

在日常的使用中,我们要了解一些常用的安全与不安全类

1)不安全的类

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

ArrayList

LinkedList

HashMap

TreeMap

HashSet

TreeSet

StringBuilder

2)安全的类

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

Vector (不推荐使用)

HashTable (不推荐使用)

ConcurrentHashMap

StringBuffer

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

String类


那么到这里,有关synchronize的基本介绍就结束了~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值