java基础----Synchronized、Lock的区别与Volatile

引用了

Lock与synchronized 的区别
详解synchronized与Lock的区别与使用
Java并发编程:volatile关键字解析
volatile

['vɒlətaɪl] 易变化的

synchronized

['sɪŋkrənaɪzd] 同步的

reentrant (lock)

[ri:'entrənt] 可重入

为什么要有锁

①cpu处理以线程为单位,cpu又有一块硬件叫cache(快速缓存区),每个线程对应了自己的一块快速缓存区。因为cpu的处理速度很快,内存处理速度太慢了,所以中间有一块缓存区,我们熟悉的cache,经常听到的几级缓存。

②假设现在是单核cpu,所有的线程看起来并行,其实是串行。只是不停地在线程之间切换。

③现在两个线程执行a++,a=10。如果线程1读入a,cpu切换到了线程2,线程2读入a,结果a=11,本来应该是a=12。

④所以我们要保护值,保持值是新鲜的。于是我们使用了volatile。单线程成功了。线程1执行了a++,数据马上会被写入内存,并要求线程2重新读取a的值。我们成功了,好像不需要锁了。

⑤回到常用的多核cpu,线程1与线程2真正的同时执行了,两个核心同一时刻执行,更新变量来不及了。

⑥所以又想出了另外的一个办法,那就只能去管理过程了,这那些危险操作一个一个执行,所以到了锁。

很推荐这个----Java并发编程:volatile关键字解析

区别

√----保证 ×----不保证
3个属性更好的解释请到这里----Java并发编程:volatile关键字解析
属性 Synchronized lock volatile 解释
可见性 变量被操作之后,能够快速写入内存,并提醒其他线程重读,加锁是通过一个一个执行保证了可见性。
原子性 × 做的过程中,不要有相关的来打扰,不相关的我们也不关心,加锁是通过一个一个执行保证了流程不会被相关的打扰。
有序性 在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
很明显如果我们满足了原子性,我们就可以只使用volatile

使用场景

Volatile

通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。
(转自Java并发编程:volatile关键字解析)

public class Main {

    public static volatile int a;
    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i < 10; i++){
            int finalI = i;
            new Thread(){
                @Override
                public void run() {
                    for (int j = 0; j < 1000; j++) {
                        a = finalI * j;
                    }
                }
            }.start();
        }

        for (int i = 0; i < 100; i++){
            System.out.println(a);
        }
    }
}

Synchronized(线程少的时候效率更高)----2线程 1000000次增加

public class Main{
    public volatile int inc = 0;

    public synchronized void increase() {
        inc++;
    }

    public static void main(String[] args) throws InterruptedException {
        final Main test = new Main();
        long a = System.currentTimeMillis();
        for(int i=0;i<2;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000000;j++) {
                        test.increase();
                    }
                    System.out.println(System.currentTimeMillis() - a + "ms");
                }
            }.start();
        }

        Thread.sleep(1000);
        System.out.println(test.inc);
    }
}

输出:65ms 2000000
public class Main{
    public volatile int inc = 0;

    public ReentrantLock lock = new ReentrantLock();
    public void increase() {
        lock.lock();
        inc++;
        lock.unlock();
    }

    public static void main(String[] args) throws InterruptedException {
        final Main test = new Main();
        long a = System.currentTimeMillis();
        for(int i=0;i<2;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000000;j++) {
                        test.increase();
                    }
                    System.out.println(System.currentTimeMillis() - a + "ms");
                }
            }.start();
        }

        Thread.sleep(1000);
        System.out.println(test.inc);
    }
}

输出:100ms 2000000

ReentrantLock(线程多的时候效率更高)----200线程 10000次增加

public class Main{
    public volatile int inc = 0;

    public ReentrantLock lock = new ReentrantLock();
    public void increase() {
        lock.lock();
        inc++;
        lock.unlock();
    }

    public static void main(String[] args) throws InterruptedException {
        final Main test = new Main();
        long a = System.currentTimeMillis();
        for(int i=0;i<200;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<10000;j++) {
                        test.increase();
                    }
                    System.out.println(System.currentTimeMillis() - a + "ms");
                }
            }.start();
        }

        Thread.sleep(1000);
        System.out.println(test.inc);
    }
}

输出:91ms 2000000
public class Main{
    public volatile int inc = 0;

    public synchronized void increase() {
        inc++;
    }

    public static void main(String[] args) throws InterruptedException {
        final Main test = new Main();
        long a = System.currentTimeMillis();
        for(int i=0;i<200;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<10000;j++) {
                        test.increase();
                    }
                    System.out.println(System.currentTimeMillis() - a + "ms");
                }
            }.start();
        }

        Thread.sleep(1000);
        System.out.println(test.inc);
    }
}

输出:196ms 2000000

比较

类别 synchronized Lock
存在层次 Java的关键字,在jvm层面上 是一个类
锁的释放 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 在finally中必须释放锁,不然容易造成线程死锁
锁的获取 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁状态 无法判断 可以判断
锁类型 可重入 不可中断 非公平 可重入 可判断 可公平(两者皆可)
性能 少量同步 大量同步
回到为什么线程少的时候Synchronized快,多的时候慢
引用了:https://blog.csdn.net/ganyao939543405/article/details/52486316

A).一般认为synchronized关键字的实现是源自于像信号量之类的线程同步机制,涉及到线程运行状态的切换,在高并发状态下,CPU消耗过多的时间在线程的调度上,从而造成了性能的极大浪费。
B).lock实现原理则是依赖于硬件,现代处理器都支持CAS指令,所谓CAS指令简单的来说Compare And Set,CPU循环执行指令直到得到所期望的结果,换句话来说就是当变量真实值不等于当前线程调用时的值的时候(说明其他线程已经将这个值改变),就不会赋予变量新的值。这样就保证了变量在多线程环境下的安全性。

当JDK版本高于1.6的时候,synchronized已经被做了CAS的优化:具体是这样的,当执行到synchronized代码块时,先对对象头的锁标志位用lock cmpxchg的方式设置成“锁住“状态,释放锁时,在用lock cmpxchg的方式修改对象头的锁标志位为”释放“状态,写操作都立刻写回主内存。JVM会进一步对synchronized时CAS失败的那些线程进行阻塞操作(调用操作系统的信号量)(此段来摘自别处)。也就是先CAS操作,不行的话继而阻塞线程。

除此之外,系统环境,CPU架构,虚拟机环境都会影响两者的性能关系。

你可能感兴趣的:(java基础----Synchronized、Lock的区别与Volatile)