浅谈Java多线程<最通俗易懂的讲解>

1、浅谈Java多线程CAS原理java

2、多线程锁的升级原理是什么? 安全

3、synchronized 和 volatile 的区别是什么?网络

1、浅谈Java多线程CAS原理多线程

(一)、锁并发

实现线程同步最直观的策略是加锁,如使用synchronizeed关键字进行加锁。app

悲观锁:悲观锁在一个线程进行加锁操做后使得该对象变为该线程的独有对象,其它的线程都会被悲观锁阻拦在外,没法操做。性能

悲观锁的缺陷:优化

一、一个线程得到悲观锁后其它线程必须阻塞。this

二、线程切换时要不停的释放锁和获取锁,开销巨大。atom

三、当一个低优先级的线程得到悲观锁后,高优先级的线程必须等待,致使线程优先级倒置问题,synchronized加锁是一种典型的悲观锁。
乐观锁:

乐观锁与悲观锁不一样,他乐观的认为对一个对象的操做不会引起冲突,因此每次操做都不进行加锁,只是在最后提交更改时验证是否发生冲突,若是冲突则再试一遍知道成功为止,这个尝试的过程被称为自旋。乐观锁其实并无加锁,但乐观锁也引入了诸如ABA、自旋次数过多等问题。

(二)、CAS操做

一、在JDK1.5以前,Java中全部锁都是重量级的悲观锁,1.5中引入了java.util.concurrent包,这个包中提供了乐观锁的使用,而整个JUC包实现的基石就是CAS操做。

二、CAS(compare and swap)即比较和替换,在juc包中进程会看多诸如此类的代码:

unsafe.compareAndSwapInt(this, valueOffset, expect, update);这即是使用CAS操做。

三、CAS操做的过程为判断某个内存地址的值是否为给定的原值,若是是则修改成心智并返回成功,不然返回该地址的值。CAS操做有三个参数:内存地址、原值、新值。当内存地址中存放的对象不等于提供的原值时则将其替换为新值。

四、以AtomicInteger的修改成例查看使用CAS时如何无锁并安全的修改某个值的:

public final int getAndUpdate(IntUnaryOperator updateFunction) {
        int prev, next;
        do {
            prev = get();
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev, next));
        return prev;
    }

先得到要修改的原值prev和要改为的新值next,当使用CAS替换新值不成功时,自旋,从新得到原值和新值再试一次直到成功为止。

五、这段代码中能够发现两个问题:

① compareAndSet操做必须是原子性的,即操做中间没法被打断。

② 获取原值时要保证这个原值对本线程可见

这两个问题是CAS操做的前提条件。

compareAndSet实际上是调用了JNI,使用本地方法来保证原子性。

JNI(Java native interface),经过使用Java本地接口书写程序,能够确保代码在不一样的平台上方便移植。

(三)、CAS带来的问题

一、ABA问题

CAS操做的流程为:

①读取原值。

②经过原子操做比较和替换。

虽然比较和替换是原子性的,可是读取原值和比较替换这两步不是原子性的,期间原值可能被其它线程修改。

ABA问题有些时候对系统不会产生问题,可是有些时候却也是致命的。

如:你在自动售货机上买了瓶饮料花了5块钱,你的帐户原有10块钱,此时售货机发出扣款CAS请求:若是是10元变为5元。可是因为网络问题,这个请求发了两次,若是第一次执行正常将帐户变为5元了,那么第二次请求时没法执行的,可是在第一次扣款操做执行完以后有人给你转帐5元了,此时你的帐户再次变为10元,那么第二遍的扣款请求是能够执行成功的,显然在这种状况下你多花了5块钱。
ABA问题的解决方法是对该变量增长一个版本号,每次修改都会更新其版本号。JUC包中提供了一个类AtomicStampedReference,这个类中维护了一个版本号,每次对值的修改都会改动版本号。

二、自旋次数过多

CAS操做在不成功时会从新读取内存值并自旋尝试,当系统的并发量很是高时即每次读取新值以后该值又被改动,致使CAS操做失败并不断的自旋重试,此时使用CAS并不能提升效率,反而会由于自旋次数过多还不如直接加锁进行操做的效率高。

三、只能保证一个变量的原子性

当对一个变量操做时,CAS能够保证原子性,但同时操做对个变量CAS就无能为力了。

Java中提供了atomicReference类来保证引用对象之间的原子性,就能够把多个变量放到一个对象里进行CAS操做,或者直接使用锁来操做多个对象。

2、多线程锁的升级原理是什么? 

锁的级别:

无锁->偏向锁->轻量级锁->重量级锁

锁分级别缘由:

没有优化前,sychroniezed是重量级锁(悲观锁),使用wait、notify、notifyAll来切换线程状态很是消耗系统资源,线程的挂起和唤醒间隔很短暂,这样很浪费资源,影响性能。因此JVM对sychronized关键字进行了优化,把锁分为无锁、偏向锁、轻量级锁、重量级锁。

一、无锁

没有对资源进行锁定,全部的线程都能访问并修改同一个资源,但同时只有一个线程能修改为功,其它修改失败的线程会不断重试直到修改为功。

二、偏向锁

对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续执行中自动获取锁,下降获取锁带来的性能开销。偏向锁,指的是偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其余线程尝试竞争偏向锁才会被释放。

偏向锁的撤销,须要在某个时间点上没有字节码正在执行时,先暂停偏向锁的线程,而后判断锁对象是否处于被锁定状态,若是线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁。

若是线程处于活动状态,升级为轻量级锁的状态。

三、轻量级锁

轻量级锁是指当锁是偏向锁的时候,被第二个线程B访问,此时偏向锁就会升级为轻量级锁,线程B会经过自旋的形式尝试获取锁,线程不会阻塞,从er提高性能。

当前只有一个等待线程,则该线程将经过自旋进行等待。可是当自旋超过必定次数时,轻量级锁边会升级为重量级锁,当一个线程已持有锁,另外一个线程在自旋,而此时第三个线程来访时,轻量级锁也会升级为重量级锁。

注:自旋是什么?

自旋(spinlock)是指当一个线程获取锁的时候,若是锁已经被其它线程获取,那么该线程将循环等待,而后不断的判断锁是否可以被成功获取,直到获取到锁才会退出循环。

四、重量级锁

指当有一个线程获取锁以后,其他全部等待获取该锁的线程都会处于阻塞状态。

重量级锁经过对象内部的监听器(monitor)实现,而其中monitor的本质是依赖于底层操做系统的Mutex Lock实现,操做系统实现线程之间的切换须要从用户态切换到内核态,切换成本很是高。

五、锁状态对比:

  偏向锁 轻量级锁 重量级锁
使用场景 只有一个线程进入同步块 虽然不少线程,但没有冲突,线程进入时间错开于是并未争抢锁 发生了锁争抢的状况,多条线程进入同步块争用锁
本质 取消同步操做 CAS操做代替互斥同步 互斥同步
优势 不阻塞,执行效率高(只有第一次获取偏向锁时须要CAS操做,后面只是比对ThreadId) 不会阻塞 不会空耗CPU
缺点

适用场景太局限。若竞争产生,会有额外的偏向锁撤销的消耗

长时间获取不到锁空耗CPU 阻塞,上下文切换,重量级操做,消耗操做系统资源

六、CAS是什么呢?

CAS即即比较和替换,当使用CAS替换新值不成功时,自旋,从新得到原值和新值再试一次直到成功为止。

CAS经过无锁操做提升了系统的吞吐率,高效的解决了原子操做问题。

3、synchronized 和 volatile 的区别是什么?

一、notify() 和 notifyAll() 有什么区别?

先解释两个概念:

等待池:假设一个线程调用了wait方法,线程就会释放该对象的锁,进入到该对象的等待池

锁池:只有得到了对象的锁,线程才会执行对象的synchronizeed代码,对象的锁每次只有一个线程能够得到,其它线程只能在锁池中等待

notify()方法随机唤醒对象等待池中的一个线程,进入锁池。

notifyAll()唤醒对象的等待池中的全部线程,进入锁池。

二、execute()和submit()有什么区别?

线程任务分两类,一类是实现了runnable接口,一类是实现了callable接口。

execute(Runnable x)没有返回值,能够执行任务,但没法判断任务是否成功完成,实现runnable接口

submit(Runnable x)返回一个future。能够用这个future来判断任务是否成功完成,实现Callable接口

三、sleep() 和 wait() 有什么区别? 

①sleep()是thread类的静态本地方法

wait()是Obejct类的成员本地方法

②sleep()方法能够在任何地方使用

wait()方法只能在同步方法或同步代码块中使用

③sleep()会休眠当前线程指定时间,释放CPU资源,不释放对象锁,休眠时间到自动苏醒继续执行

wait()方法放弃持有的对象锁,进入等待队列,当该对象被调动notify()或notifyAll()方法后才有机会竞争获取对象锁,进行运行状态

④均需捕获interruptedException异常

四、synchronized 和 volatile 的区别是什么?

做用:

synchronized表示只有一个线程能够获取做用对象的锁,执行代码,阻塞其它线程。

volatile表示变量在CPU寄存器中是不肯定的,必须从主存中读取,保证多线程环境下变量的可见性和有序性。

区别:

synchronized能够做用于方法、变量;volatile只能做用于变量。

synchronized能够保证线程间的有序性、原子性和可见性;volatile纸包装了可见性和有序性,没法保证原子性。

synchronized线程阻塞,volatile线程不阻塞。

 

素小暖讲Java@目录

相关文章
相关标签/搜索