承接上文,欢迎关注
实现原理
ThreadLocal类的get, set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //获取threadlocalmap
if (map != null)
map.set(this, value); //键位当前threadlocal对象,value为保存值
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
回收清理:
线程类Thread在退出时,会进行一些清理工作,其中包括清理ThreadLocalMap
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null; //置空
inheritableThreadLocals = null; //置空
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
注意:
使用线程池未必会清除threadlocal,因为线程总会存在,而不会退出,多次使用后可能造成内存泄漏.故此处推荐使用ThreadLocal.remove()方法来移除变量.
ThreadLocalMap实现了弱引用,即进行GC时,一旦发现弱引用则立即回收.ThreadLocalMap中每一个Entry是一个WeakReference,ThreadLocal为key. 当ThreadLocal的外部强引用被回收时,ThreadLocalMap的key将会变成null.放系统进行ThreadLocalMap清理时(添加新变量进入表中时,会自动进行清理),垃圾数据会被回收.
无锁操作
无锁属于乐观锁,不采用重量级锁,主要实现原理为CAS比较交换.
比较交换CAS
特点:非阻塞性,免疫死锁问题,线程间影响比锁要小,总结来说,性能更强
CAS(V,E,N) V表示要更新的变量,E表示预期值,N表示新值.当V=E时才会去更新V=N,否则不作任何操作,多个线程去CAS更新时,只会有一个成功
线程安全整数:AtomicInteger
特点:线程安全,使用CAS,性能强
Java中的指针Unsafe类
sun.misc.Unsafe类型,仅能在JDK中使用,第三方无法调用
AtomicReference
AtomicReference与AtomicInteger相似,是对普通对象的引用.
在CAS中,存在一个问题,当一个变量连续被修改两次,最后一次修改成原来的值,这样当前对象就无法判断该对象是否被修改过
当存在这样一个场景时,AtomicReference便无效了:
//有一家蛋糕店,当客户余额低于20元时,主动给客户赠送20元余额,每位客户只能被赠送一次
//如果使用原子类的话,当客户低于20元,获赠20元余额时,客户再次消费20元整数额,此时,赠予前和赠予后金额一致,故可能存在多次赠予的情况
public class AtomicReferenceDemo {
static AtomicReference money=new AtomicReference();
public static void main(String[] args) {
money.set(19);
//模拟多个线程同时更新后台数据库,为用户充值
for(int i = 0 ; i < 3 ; i++) {
new Thread() {
public void run() {
while(true){
while(true){
Integer m=money.get();
if(m<20){
if(money.compareAndSet(m, m+20)){
System.out.println("余额小于20元,充值成功,余额:"+money.get()+"元");
break;
}
}else{
//System.out.println("余额大于20元,无需充值");
break ;
}
}
}
}
}.start();
}
//用户消费线程,模拟消费行为
new Thread() {
public void run() {
for(int i=0;i<100;i++){
while(true){
Integer m=money.get();
if(m>10){
System.out.println("大于10元");
if(money.compareAndSet(m, m-10)){
System.out.println("成功消费10元,余额:"+money.get());
break;
}
}else{
System.out.println("没有足够的金额");
break;
}
}
try {Thread.sleep(100);} catch (InterruptedException e) {}
}
}
}.start();
}
}
带时间戳的AtomicStampedReference
为解决上述问题,AtomicStampedeference内部维护了对象值,也维护了更新时间戳.
public class AtomicStampedReferenceDemo {
static AtomicStampedReference money=new AtomicStampedReference(19,0);
public static void main(String[] args) {
//模拟多个线程同时更新后台数据库,为用户充值
for(int i = 0 ; i < 3 ; i++) {
final int timestamp=money.getStamp();
new Thread() {
public void run() {
while(true){
while(true){
Integer m=money.getReference();
if(m<20){
if(money.compareAndSet(m, m+20,timestamp,timestamp+1)){ //CAS修改值,并且修改时间戳
System.out.println("余额小于20元,充值成功,余额:"+money.getReference()+"元");
break;
}
}else{
//System.out.println("余额大于20元,无需充值");
break ;
}
}
}
}
}.start();
}
//用户消费线程,模拟消费行为
new Thread() {
public void run() {
for(int i=0;i<100;i++){
while(true){
int timestamp=money.getStamp();
Integer m=money.getReference();
if(m>10){
System.out.println("大于10元");
if(money.compareAndSet(m, m-10,timestamp,timestamp+1)){
System.out.println("成功消费10元,余额:"+money.getReference());
break;
}
}else{
System.out.println("没有足够的金额");
break;
}
}
try {Thread.sleep(100);} catch (InterruptedException e) {}
}
}
}.start();
}
}
无锁的数组AtomicIntegerArray
原子数组还有AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
public class AtomicIntegerArrayDemo {
static AtomicIntegerArray arr = new AtomicIntegerArray(10);
public static class AddThread implements Runnable{
public void run(){
for(int k=0;k<10000;k++)
arr.getAndIncrement(k%arr.length()); //k%length索引处+1
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] ts=new Thread[10];
for(int k=0;k<10;k++){
ts[k]=new Thread(new AddThread());
}
for(int k=0;k<10;k++){ts[k].start();}
for(int k=0;k<10;k++){ts[k].join();}
System.out.println(arr);
}
}
普通变量升级原子变量AtomicIntegerFieldUpdater
updater升级有三种: AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater
public class AtomicIntegerFieldUpdaterDemo {
public static class Candidate{
int id;
volatile int score; //不支持静态变量,且必须申明为volatile,且访问权限要支持(private不行)
}
public final static AtomicIntegerFieldUpdater scoreUpdater
= AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score"); //声明要升级的类以及成员变量.
//检查Updater是否工作正确
public static AtomicInteger allScore=new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
final Candidate stu=new Candidate();
Thread[] t=new Thread[10000];
for(int i = 0 ; i < 10000 ; i++) {
t[i]=new Thread() {
public void run() {
if(Math.random()>0.4){
scoreUpdater.incrementAndGet(stu); //使用升级类去做CAS原子自增
allScore.incrementAndGet(); //验证升级类是否正确
}
}
};
t[i].start();
}
for(int i = 0 ; i < 10000 ; i++) { t[i].join();}
System.out.println("score="+stu.score); //最终打印 score和allscore一致.
System.out.println("allScore="+allScore);
}
}
注意:
1.只能修改可见范围内的变量,因为Updater使用反射得到变量.
2.为了保证变量被正确读取,必须为volatile
3.CAS通过对象实例中的偏移量进行复制,因此,不支持static变量
无锁的LockFreeVector
待续.....
SynchronousQueue
此类的容量为0,每一个写都要对应一个读操作,其内部也有很多无锁操作
对SynchronousQueue来说,它将put()和take()均抽象为一个共同方法Transferer.transfer().从字面上来看,就是数据传递
Object transfer(Object e,boolean timed,long nanos) //e不为空时,表示入队,e为空时表示出队
//timed 表示是否有超时,nanos表示超时时间
//返回值为空表示失败,不为空表示成功
SynchronousQueue内部会维护一个线程等待队列,保存等待线程及相关数据信息,如生产者将数据放入SynchronousQueue中,如果没有消费者接受,则数据本身和对象都会打包在队列中等待(SynchronousQueue本身容量为0,数据是不能放入的,但是内部的线程等待队列可以存放)
Transferer.transfer()函数的实现是SynchronousQueue的核心,大提分为三个步骤.
(1)如果等待队列为空,或者队列中节点的类型和本次操作是一致的,那么将当前操作压入队列等待。比如,等待队列中是读线程等待,本次操作也是读,因此这两 个读都需要等待。进入等待队列的线程可能会被挂起,它们会等待一个“匹配” 操作
(2)如果等待队列中的元素和本次操作是互补的(比如等待操作是读,而本次操作是写),那么就插入一个“完成”状态的节点,并且让它“匹配”到一个等待节点 上。接着弹出这两个节点,并且使得对应的两个线程继续执行。
(3)如果线程发现等待队列的节点就是“完成”节点,那么帮助这个节点完成任务,其流程和步骤(2)是一致的。
死锁
定位:
jps:查看java进程的进程ID
jstack (ID):查看线程的线程堆栈
为了避免死锁问题,可以使用无锁函数以及重入锁,通过重入锁的中断或者限时等待可以有效规避死锁带来的问题
快来关注我把~
努力更新中
ovo