synchronized、lock、volatile对比学习记录

一、synchronized的作用:

synchronized是Java 关键字,用于保证线程之间的同步,也就说同一时刻只能有一个线程访问同步方法或同步代码块。其主要有三种表现形式:

1.对于普通方法,其锁住的是当前的实例对象。(对于不同的实例对象是不起作用的)。

2.对于静态方法,锁住的是class类对象。

3.对于代码块,锁住的是括号里的对象。

synchronized底层实现原理:

synchronized修饰的方法:其是根据常量池中的acc_synchronized标识符来实现的,当线程访问同步方法时,查看是否有ACC_SYNCHRONIZED标识,如果有就获得监视器锁(加锁),执行方法体,执行完释放锁。

synchronized修饰的代码块:是根据monitorenter和monitorexit两个指令来实现的。线程执行到monitorenter指令时,就会获得监视器锁,执行代码块中的内容,当执行到monitorexit指令时,就释放监视器锁。每个对象维护着一个锁计数器。

二、关于Lock:

Lock的作用:

lock的作用就是将“多线程”单线程化,从而实现多线程按照一定的顺序执行。lock的原理就是编译器将lock转换成monitor,根据monitorenter和monitorexit指令来执行同步代码。只要锁定同一个对象就会顺序执行。相比较于synchronized,无论是功能还是性能上都有很大的提升,但是还是需要

Lock的定义及使用:

Lock本质上是一个接口,主要包含以下方法:

void lock();

void lockInterruptibly() throws InterruptedException;

boolean tryLock();

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

void unlock();

Condition newCondition();

Lock接口有三个实现类,ReentrantLock类,以及ReentrantReadWriteLock类中的ReadLock和WriteLock两个静态内部类。

Lock的使用方式:在多线程访问共享资源时,访问时需要加锁,访问结束需要解锁,解锁一般放在finally块中。

Lock接口的基本思想:

实现锁的两个基本要素:一个是表示锁状态的变量,(0表示没有线程获取锁,1表示已有线程占用了锁),此变量必须声明为volatile类型;另一个是队列,队列中的节点表示未能获取到锁而阻塞的线程。(为了解决多核CPU多线程缓存不一致的问题,故而声明为volatile类型)

线程获取锁的大致过程:

 线程释放锁的大致过程:当线程要释放锁的时候,就将状态变量置为1,并唤醒队列中的队首节点,然后去继续执行后面的代码。但是,如果是非公平锁,被唤醒线程可能会和未在队列中的线程一起竞争锁。

公平锁和非公平锁:

上边提到了公平锁和非公平锁,ReentrantLock默认是非公平锁。

公平锁意思就是严格按照顺序去获得锁。如果是公平锁,他想要获取锁,不仅要判断状态变量,还需要判断队列是否为空,如果队列为空,线程通过cas操作尝试获取锁;如果不为空,就进入等待队列,自身阻塞。

对于非公平锁,不需要判断队列是否为空,当状态变量为0时,就尝试获取锁。

三、关于volatile

宏观上理解:

volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取。

一旦一个变量被volatile所修饰,那么他就具有了两层语义:

一是保证了不同线程之间对共享变量的可见性,也就是说当前线程对共享变量进行了修改,其他线程是可见的。

二是有序,禁止指令重排。

volatile底层实现原理:

参考自:volatile底层实现原理 - myf008 - 博客园

首先是一个简单的例子:

public class volatileEx {
    private static boolean flag = false;
    public static void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1");
                for (;;){
                    if(flag){
                        System.out.println("跳出循环");
                        break;
                    }
                }

            }
        }).start();
        Thread.sleep(100);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2");
                flag = true;
                System.out.println("flag变化");
            }
        }).start();
    }
}

结果如下:

此时是感知不到flag的变化的。

public class volatileEx {
    private static volatile boolean flag = false;
    public static void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1");
                for (;;){
                    if(flag){
                        System.out.println("跳出循环");
                        break;
                    }
                }

            }
        }).start();
        Thread.sleep(100);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2");
                flag = true;
                System.out.println("flag变化");
            }
        }).start();
    }
}

结果如下:

 

 此时是可以感知的。

那么接下来说一下原理,就要从JMM开始说起。上篇博客已经介绍了JMM。

(转载自volatile底层实现原理 - myf008 - 博客园) 

cpu和内存并不能直接交互数据,是通过总线。volatile会开启总线的MESI缓存一致性协议。

主要的流程:

1.当前线程修改值,经过总线,写入主内存。

2.其他线程通过总线嗅探机制感知到共享变量的变化,使自身工作内存中的贡献变量失效。

3.其他线程去主内存读取新值。

volatile底层是根据LOCK指令,保证其他线程读到最新值。在store之前对主内存加锁,知道修改完再释放锁。这期间其他线程不能访问。

四、说一说大家的区别:

首先synchronized是由JVM管理的,可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定。且当有线程占用锁时,其他线程只能阻塞等待。synchronized采用的是悲观锁机制,即线程获取的是独占锁,而cpu在转换线程阻塞时会引起上下文切换,导致效率低。

再次是lock是一个接口,由Java代码实现。lock需要手动释放锁,需要在finally块中写入释放锁的语句。每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作。

在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;

volatile和synchronized区别:
1.volatile 仅能使用在变量级别;
synchronized 则可以使用在变量、方法、和类级别的;

2.volatile 仅能实现变量的修改可见性,并不能保证原子性;
synchronized 则可以保证变量的修改可见性和原子性;

3.volatile 不会造成线程的阻塞;
synchronized 可能会造成线程的阻塞;

4.volatile 标记的变量不会被编译器优化;
synchronized 标记的变量可以被编译器优化。
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值