java并发编程核心方法与框架(java并发编程深度解析)
导语:Java 并发编程安全问题-核心理论和解决方案
线程安全包括3个方面:①.可见性、②.原子性、③.有序性
核心理论
可见性可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。
原子性原子性一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
有序性有序性就是程序执行的顺序按照代码的先后顺序执行。
解决方案
1.volatile关键字
①、修改volatile变量时会强制将修改后的值刷新到主内存中
②、解决可见性,不能解决原子性
要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下:
从图中可以看出:
一、每个线程都有一个自己的本地内存空间--线程栈空间,当线程执行时,先把共享变量从主内存读取到线程自己的本地内存空间,然后再对该变量进行操作。
二、对该变量操作完后,在某个时间再把变量刷新回主内存。
③、volatile的实现原理
修改volatile变量时会强制将修改后的值刷新的主内存中,修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再次读取该变量值的时候就需要重新从读取主内存中的值,这就是可见性的实现。
禁止进行指令重排序,从而实现有序性。
④、volatile注意事项:
volatile不能保证原子性。比如,i = 1;是原子操作。变量的自增操作 i++,分三个步骤:从内存中读取出变量 i 的值,将 i 的值加1,将加1后的值写回内存,这说明 i++ 并不是一个原子操作。
⑤、volatile关键字的使用场景
对变量的写操作不依赖于当前值,该变量没有包含在具有其他变量的不变式中。
2.synchronize同步锁
Synchronized的作用主要有三个:
一、确保线程互斥的访问同步代码。
二、保证共享变量的修改能够及时可见。
三、有效解决重排序问题。
Synchronized总共有三种用法:
一、修饰代码块
格式:
synchronized(对象) {
需要被同步的代码;
}
这里的锁对象可以是任意对象(new 出来的对象,也可以用this标识)
synchronized(类名.class) {
需要被同步的代码;
}
此时的锁对象为当前类的字节码文件对象
二、修饰普通方法
synchronized 返回值 方法名(){
需要被同步的代码;
}
这时的锁对象(new 出来的对象)是this,如果有2个对象调用同一个方法,则不能实现阻塞等待。
三、修饰静态方法
static synchronized 返回值 方法名(){
需要被同步的代码;
}
此时的锁对象为当前类的字节码文件对象
synchronized的缺陷
代码块被synchronized修饰后,当一个线程获取了对应的锁,并执行该代码块时,其他线程只能一直等待,等待获取锁的线程释放锁。这里获取锁的线程释放锁只会有两种情况:
一、获取锁的线程执行完了该代码块线程释放锁、线程执行发生异常时JVM会让线程自动释放锁。
二、如果这个获取锁的线程由于被阻塞了,但是又没有释放锁,其他线程只能等待,因此会影响程序执行效率。
Synchronized的实现
实现如下图所示:
它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。
一、Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;
二、Entry List:竞争队列中那些有资格获取锁的线程被移动到Entry List中;
三、OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被称为OnDeck;
四、当前已经获取到锁资源的线程被称为Owner;
五、Wait Set:那些调用wait方法被阻塞的线程被放置在这里;
处于ContentionList、EntryList、WaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的。
3.ThreadLocal (线程本地变量)
ThreadLocal作为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量,线程之间相互隔离,互不影响,会占用一定内存。
ThreadLocal原理:
一、在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键为当前ThreadLocal变量,value为变量副本。
二、在进行get之前,必须先set,否则会报空指针异常。
三、如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。
ThreadLocal类提供的几个方法
public T get() { } get()方法是用来获取ThreadLocal在当前线程中保存的变量副本
public void set(T value) { } set()用来设置当前线程中变量的副本
public void remove() { } remove()用来移除当前线程中变量的副本
protected T initialValue() { } initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法
ThreadLocal回收机制:
Thread、ThreadLocal和ThreadLocalMap没有任何依赖关系,当一个线程运行结束销毁时,所有的资源副本都是可以被垃圾回收的,因为每一个线程对资源副本都有一个弱引用(WeakReference)
4.锁
锁的相关概念介绍
①、可重入锁是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。例如,当一个线程执行到某个synchronized修饰的方法A时,在方法A中会调用另外一个synchronized修饰的方法B,此时线程不必重新去申请锁,而是可以直接执行方法B。synchronized 和 ReentrantLock 都是可重入锁。
②、公平锁FairSync即尽量以请求锁的顺序来获取锁。
③、非公平锁NonfairSync在等待锁的过程中,如果有任意新的线程妄图获取锁,都是有很大的几率直接获取到锁的。
ReentrantLock lock = new ReentrantLock(true);如果参数为true表示为公平锁,为fasle为非公平锁。默认情况下,如果使用无参构造器,则是非公平锁。
ReentrantLock lock = new ReentrantLock(true);如果参数为true表示为公平锁,为fasle为非公平锁。默认情况下,如果使用无参构造器,则是非公平锁。
④、读写锁
读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁,因此多个线程之间的读操作不会发生冲突。ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。ReentrantReadWriteLock实现了ReadWriteLock接口,如果有一个线程已经占用了读锁,则此时其他线程如果申请者读锁,则申请的线程立即获取一直等待。如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待。如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待。
本文内容由快快网络小媛创作整理编辑!