synchronized 使用及深入理解

为什么要使用 synchronized?

在并发编程中存在多个线程访问共享数据的情况,多线程操作共享数据,就会引发线程安全问题,synchronized 可以保证同一时刻只能有一个线程在访问共享数据,以此来保证线程安全性,同时可以保证共享变量在内存中的可见性。

关于 synchronized 升级传送门如下:
synchronized 锁升级详解

synchronized 的原理了解吗?

synchronized 是依赖是 JVM 实现的,synchronized 底层是依赖对象的监视器 monitor 完成的,当 monitor 被占用的时候表示处于锁定状态,线程执行 monitorenter 命令尝试获取锁,如果 monitor 进入数为 0,则当前线程获取锁成功,并将进入数设置为 1,如果当前线程是重复获取这个锁的话,则进入数加 1,如果其他线程占有了 monitor,则当前线程进入等待状态,直到 monitor 的进入数为 0,再重新去获取 monitor 的所有权,monitorenter 对应的命令是 monitorexit,当线程执行 monitorexit 命令时,线程进入数减 1,直到进入数为 0,线程就退出 monitor,其他线程就可以来尝试获取 monitor,主要注意的是执行 monitorexit 命令的线程必须是 monitor 的所有者。

synchronized 的作用?

  • 确保线程互斥的访问同步代码。
  • 确保共享变量的修改及时可见。
  • 解决指令重排序问题。

synchronized 的使用方式?

  • 修饰代码块:锁住的是括号里面的对象,进入代码块之前要获取指定的对象来作为锁。
  • 修饰普通方法:锁住的是当前实例对象,进入代码块之前要获得当前实例对象来作为锁。
  • 修饰静态方法:锁住的是类的 Class 对象,进入同步方法之前需要获取当前类的 Class 对象来作为锁。

synchronized 使用演示:

修饰代码块:

public class SynchronizedDemo implements Runnable {

    public static int a = 0;

    private Object lock;

    public SynchronizedDemo(Object lock) {
        this.lock = lock;
    }

    @Override
    public  void run() {
        for (int i = 0; i < 1000; i++) {
            synchronized(lock){
                add();
            }
        }
    }

    public void add() {
        a++;
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedDemo synchronizedDemo = new SynchronizedDemo(new Object());
        //SynchronizedDemo synchronizedDemo2 = new SynchronizedDemo(new Object());
        new Thread(synchronizedDemo).start();
        new Thread(synchronizedDemo).start();
        //new Thread(synchronizedDemo2).start();
        Thread.sleep(2000);
        System.out.println("执行结果:" + a);
    }


}

执行结果:

执行结果: 2000

执行结果分析:

两个线程同时做 1000 次循环,结果 2000,线程安全没有问题,注意如果我们开启注释的那行代码,将会线程不安全,因为两个线程用的锁不是同一个,做不到互斥性,所以线程不安全,有兴趣的朋友可以去试一下,这也证明了修饰代码块时候,锁对象是指定的对象。

修饰普通方法:

public class SynchronizedDemo implements Runnable {

    public static int a = 0;

    private Object lock;

    public SynchronizedDemo() {
    }


    public SynchronizedDemo(Object lock) {
        this.lock = lock;
    }

    @Override
    public synchronized void run() {
        System.out.println("当前准备开始执行任务的线程名称:"+Thread.currentThread().getName());
        for (int i = 0; i < 1000; i++) {
            add();
        }
        System.out.println("任务执行结束线程名称:"+Thread.currentThread().getName());
    }

    public void add() {
        a++;
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
        new Thread(synchronizedDemo).start();
        new Thread(synchronizedDemo).start();
        Thread.sleep(2000);
        System.out.println("执行结果:" + a);
    }


}

执行结果:

当前准备开始执行任务的线程名称:Thread-0
任务执行结束线程名称:Thread-0
当前准备开始执行任务的线程名称:Thread-1
任务执行结束线程名称:Thread-1
执行结果:2000

执行结果分析:

两个线程同时做 1000 次循环,结果 2000,线程安全没有问题,根据打印结果我们可以知道,两个线程本质是顺序执行的,也就是一个线程获取到锁后,直到他执行完了另外一个线程才可以获取到锁继续执行,两个不同的线程也就是两个不同的实例,这也证明了锁住的是实例对象。

修饰静态方法:

静态方法我们就不演示了,我们知道静态方法可以通过类名调用,一个类只有一个 Class 对象,显然锁住的就是 Class。

为什么要锁住代码块?

在有些业务场景下,方法涉及的业务较多,但是又只有部分代码需要同步,如果锁住整个方法,显然是不太合理的,这个时候就可以使用同步代码块了。

synchronized 可以在分布式环境中保证线程的安全性吗?

不能,synchronized 底层实现依赖于 JVM 指令,而分布式系统涉及到跨 JVM 了,自然不能保证线程的安全性了,这个时候我们考虑使用分布式锁来保证线程安全性。

synchronized 和 volatile 的区别?

  • synchronized 可以保证多线程之间共享变量的可见性和原子性(线程安全),volatile 只能保证多线程之间共享变量的可见性,无法保证原子性,线程不安全。
  • synchronized 使用了锁机制,有加锁和释放锁的消耗,而且会有阻塞情况,volatile 没有使用锁,没有加锁释放锁,也不会有阻塞的情况。
  • synchronized 可以修饰方法和代码块,volatile 只能修饰变量。

synchronized 和 ReentrantLock 的区别?

  • synchronized 隐式的获取锁释放锁,ReentrantLock 显示的获取锁释放锁。
  • synchronized 不可中断,ReentrantLock 可以中断,通过lock.lockInterruptibly()来实现。
  • synchronized 是由 JVM 来实现的,ReentrantLock 则是代码层面实现的,源码可读性更强。
  • synchronized 只能是非公平锁,ReentrantLock 可以指定公平锁和非公平锁。
  • synchronized 是悲观锁的实现方式,ReentrantLock 是乐观锁的实现方式。
  • synchronized 不可设置锁超时时间,ReentrantLock 可以设置锁超时时间。
  • synchronized 程序发生异常会自动释放锁,ReentrantLock 程序发生异常可能会导致死锁,因此使用 ReentrantLock 时候一定要在 finally 中释放锁。

如有错误的地方欢迎指出纠正。

  • 29
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值