JAVA常见的并发知识点,概念和使用方法。
一、Synchronized和Lock的区别
Synchronized:Synchronized是Java提供的关键字,可以在需要同步的对象中加入此控制。其可以用来修辞方法,也可以加在特定代码块中,而修辞特定代码块时括号中表示需要锁的对象。
JVM底层实现:
Synchronized是托管给JVM执行的,JVM通过进入、退出对象监视器来实现对方法、同步代码块的同步。而对象监视器本质依赖于底层操作系统的互斥锁实现。对于同步代码块的同步,通过javap指令编译可以发现同步块的入口和出口分别有monitorenter和monitorexit指令,执行monitorenter指令时线程试图获取锁也就是获取monitor(monitor对象存在于每个Java对象的对象头中,synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因)的持有权。计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行monitorexit指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。而Synchronized修辞方法则是添加ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法,JVM通过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
Synchronized特点:
首先Synchronized可以保证原子性、可见性以及有序性。Synchronized保证原子性指的是任何时刻只有一个线程对互斥资源进行操作,也就是保证了同步代码中的代码要么不执行。如果执行,中间不会穿插有其他线程对同步数据的操作。而Synchronized保证可见性则是当释放锁时,所有的写入都会写回内存,获得锁后,都会从内存读取最新数据。有序性指的是同步代码块之间是串行执行的,其并不能防止指令重排。
Synchronized具有可重入性,即对同一个线程获取锁后,调用其他需要同样锁的代码时可直接调用。原理是底层会记录锁的持有线程、持有数量,当执行Synchronized时如果锁是被当前线程锁定,计数器值会加1.同样释放时会减1,因此多次重入最后全部释放也可以是计数器达到0。
Synchronized是重量级的,底层通过监视器对象完成,其wait()、notify()操作也依赖于监视器对象。监视器依赖操作系统互斥锁实现。操作系统线程切换需从用户态转为内核态,因此切换过程长、效率低。
Synchronized优化(JDK1.6后):
轻量锁本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。内部主要使用CAS来获取锁和释放锁,且内部根据锁的竞争状态将轻量锁转换为重量锁,以阻塞其他等待锁的进程。
偏向锁为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。
适应性自旋即使用CAS时,如果操作失败,CAS会自旋再次尝试。由于自旋是需要消耗CPU资源的,所以如果长期自旋就白白浪费了CPU。JDK1.6 加入了适应性自旋,即如果某个锁自旋很少成功获得,那么下一次就会减少自旋。可通过JVM参数设置。
锁消除指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。
锁粗化指写代码时推荐将同步块的作用范围限制得尽量小,只在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。
Lock:Java中一般使用ReentrantLock类做为锁,需要显示指定起始位置和终止位置。多个线程中必须要使用一个ReentrantLock类做为对象才能保证锁的生效。且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
ReentrantLock和Synchronized相比,不仅能实现非公平锁,还能实现公平锁。ReentrantLock基于JDK实现,同时内置了Condititon类可实现选择性的通知。另外和Synchronized还有的不同就是ReentrantLock可以实现中断。ReentrantLock涉及到了CAS、AQS和内存可见性知识。
CAS
即比较和交换(Conmpare And Swap)是用于实现多线程同步的原子指令,它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。 因此CAS操作包含三个参数,分别为需要读写的内存位置、进行比较的预期原值、打算写入的新值。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,说明该值被其他线程修改,处理器不做任何操作。同时CAS是实现自旋锁的基础,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果。自旋用一个无限循环实现,执行一个CAS操作,当操作成功,返回true时,循环结束。当返回false时,接着执行循环,继续尝试CAS操作,直到返回true。
AQS
AbstractQueuedSynchronizer抽象队列同步器简称AQS,是实现同步器的基础组件。AQS通过内置的FIFO双向队列来完成线程的排队工作,队列内部结点通过维护不同状态来表示线程的不同状态。工作时当一个线程尝试获取锁,如果已经被占用,那么当前线程就会被构造成一个Node节点加入到同步队列的尾部,队列的头节点是成功获取锁的节点,当头节点线程释放锁时,会唤醒后面的节点并释放当前头节点的引用。AQS内部还提供了独占(如ReentrantLock)和共享(如Semaphore/CountDownLatch)的方式。
ReentrantLock的通知、等待机制
相比于Synchronized的wait()和notify()/notifyAll(),ReentrantLock借助Condition接口与newCondition()方法。Condition具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例,线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。即使用notify/notifyAll()方法进行通知时,被通知的线程是由JVM选择的,而用ReentrantLock类结合Condition实例就可以选择特定的Condition对象进行通知。
中断机制
Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。Java中断模型中,每个线程对象都有一个boolean类型的标识,代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。Java中操作中断状态的方法有interrupted()、isInterrupted()、 interrupt()。其中interrupted()方法可以测试当前线程是否已经中断,且线程的中断状态由该方法清除。isInterrupted()方法只是测试线程是否已经中断,不改变状态。interrupt()就是中断线程的方法。另外Thread.stop()虽然和中断有相似之处,但是Thread.stop()会直接在代码执行过程中抛出ThreadDeath错误,且Thread.stop()可能导致对象状态不一致,不推荐使用。
当检测到可中断的阻塞方法(如wait()、sleep()、join())抛出InterruptedException或检测到中断后,一种做法是向方法调用栈的上层抛出该异常(检测到中断则清除中断状态)。另外便是捕获异常,并通过Thread.currentThread.interrupt()来重新设置中断状态。
使用ReentrantLock获取锁可以使用lockInterruptibly()和lock()方法。其中lockInterruptibly()方法优先考虑响应中断,而不是响应锁的普通获取或重入获取。lock()方法则优先考虑获取锁,待获取锁成功后,才响应中断。因此lockInterruptibly()允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException。lock方法不允许Thread.interrupt中断,即使检测到Thread.isInterrupted,一样会继续尝试获取锁,失败则继续休眠。只是在最后获取锁成功后再把当前线程置为interrupted状态,然后再中断线程。
二、Volatile关键字
Volatile:是Java语言的关键字之一,用来修辞变量。是Java语言提供的一种稍弱的同步机制,用Volatile修辞的变量可以确保变量的更新操作通知到其他线程。编译器运行时也会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。其次Volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取Volatile类型的变量时总会返回最新写入的值。
访问Volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,是一种比Sychronized关键字更轻量级的同步机制。对于非Volatile变量进行读写时,每个线程先从内存拷贝对象到CPU缓存中,如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的CPU cache中。而声明变量是Volatile 的,JVM 保证了每次读变量都从内存中读,跳过CPU cache这一步。
所以变量定义为Volatile之后,其将具备两种特性。一个便是保证此变量对所有的线程的可见性,每次使用前立即从主内存刷新,保证修改对其他线程立即可见。另外就是禁止指令重排序优化,有Volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障。(指令重排序是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)
对于Volatile变量的性能,其读性能消耗与普通变量几乎相同。但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
三、ThreadLocal类
ThreadLocal:当创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。ThreadLocal就使用这种思想,是除了加锁这种同步方式之外的一种保证规避多线程访问出现线程不安全的类。ThreadLocal由JDK包提供,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。
使用
使用ThreadLocal类主要就是调用其set()、get()、remove()方法。每个线程都可以调用同一个ThreadLocal对象实例的set()方法设置自己的线程变量,当不需要使用变量时应该调用remove()方法移出,防止内存占用问题。
原理
在Thread类内部有两个变量分别是threadLocals和inheritableThreadLocals,题目都是ThreadLocal类的内部类ThreadLocalMap对象。通过查看ThreadLocalMap类可以发现其实际上类似HashMap。默认情况下Thread类的threadLocals和inheritableThreadLocals对象都为null,只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建它们。且每个线程的本地变量不是存放在ThreadLocal实例中,而是放在Thread类的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。
调用set()方法时,首先获取当前调用线程的threadLocals变量,如果其非空,就直接将value值设置到threadLocals中(key为当前的ThreadLocal对象引用)。如果为空,则创建该对象并在构造函数中传入key和value。get()方法实现时,首先获取当前调用者线程,如果当前线程threadLocals变量非空,直接返回当前线程绑定的本地变量值。否则创建threadLocals对象实例,添加null值作为本地变量。remove()方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量。
同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的,即ThreadLocal不支持继承性。但InheritableThreadLocal类可以实现,InheritableThreadLocal继承自ThreadLocal类。并重写了childValue、getMap、createMap三个方法,且set()、get()、remove()方法操作的都是Thread类中的inheritableThreadLocals对象实例。
对于ThreadLocalMap类而言,其内部Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉。但是对应的本地变量值不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。(一个线程可以有多个ThreadLocal对象,因此第一次设置使得Thread类中的threadLocals非空,其他ThreadLocal都是基于其进行操作,然后用ThreadLocal对象作为键做映射获取各自不同的本地变量值。)
标签:JAVA,变量,Synchronized,中断,知识,并发,ThreadLocal,线程,内存 From: https://www.cnblogs.com/idempotent/p/12392873.html