本文主要是介绍多线程(3)-源码分析(ReentrantLock-AQS,ThreaLocal)、强软弱虚引用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1. synchronized
public void hello(){synchronized (this){System.out.println("a");}
}
经过编译的字节码
0 aload_01 dup2 astore_13 monitorenter4 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>7 ldc #3 <a>9 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>
12 aload_1
13 monitorexit
14 goto 22 (+8)
17 astore_2
18 aload_1
19 monitorexit
20 aload_2
21 athrow
22 return
monitorenter:进入对象监视器
monitorexit:退出监视器,释放锁。两个monitorexit的原因是1. 当正常执行完结束时,monitorexit退出。2. 当执行报错时,自动monitorexit,自动退出。
synchronized实现的原理:
每个对象都与一个监视器相关联。当且仅当监视器有所有者时,监视器才被锁定。执行monitorenter的线程 尝试获得与对象关联的监视器的所有权,如下所示:
如果与对象关联的监视器的条目计数 为零,则线程进入监视器并将其条目计数设置为 1。该线程然后是监视器的所有者。
如果线程已经拥有与对象关联的监视器 ,它会重新进入监视器,增加其条目计数。
如果另一个线程已经拥有与对象关联的监视器 ,线程会阻塞,直到监视器的条目计数为零,然后再次尝试获得所有权。
- 多线程同时调用synchronized块时,其中一个线程进入对象监视器,将条目计数为1,该线程然后是监视器的所有者,持有对象锁。
- 其他的线程进入监视器失败,会进入阻塞队列中
- 当持有锁的对象释放锁,监视器条目计数-1,当为0时,通知队阻塞队列中的线程尝试进入监视器,如果进入成功,该线程就持有锁。
- 如果持有锁的线程又一次去进入监视器,那么条目计数加1. synchronized重入。
2. ReenTrantLock-AQS
- volatile int state
- cas
- 原理
https://blog.csdn.net/fuyuwei2015/article/details/83719444 - lock() 源码
- unlock() 源码
3. ThreadLocal
- ThreadLocal 称为ThreadLocal变量,线程局部变量。一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的(每个线程都只能看到自己线程的值)。也就是说,同一个ThreadLocal对象,在不同的线程中是不一样的。
- API
set方法,向ThreadLocal中设置当前线程的的值。
get方法,获取当前线程在ThreadLocal中设置的值。 - 为什么叫局部变量,可以把这个对象当成一个局部变量。变量person 等同于 threadLocal。使用时直接threadLocal.get();
每一个threadLocal中都可以保存一个值。
Person person = new Person();
//相当于
ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();
threadLocal.set(new Person());
- 上代码
package com.tzw.juc.threadLocal;public class C11_threadLocal {private static ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();public static void main(String[] args) {//主线程// 1. 在同一线程中,threadLocal相当于一个局部变量,可以进行赋值操作。// 2. 同一个线程中,threadLocal可以修改这个局部变量的值。C11_threadLocal c11_threadLocal = new C11_threadLocal();threadLocal.set(c11_threadLocal.new Person("张三"));threadLocal.set(c11_threadLocal.new Person("王武"));Person person = threadLocal.get();System.out.println(person);//线程1//1. 输出结果为null,说明ThreadLocal对象虽然是同一个,但是不同的线程中threadLocal对象保存的值不一样,//线程不共享。new Thread(()->{Person person1 = threadLocal.get();System.out.println(person1);}).start();//线程2// 输出结果为:李四。说明同一个threadLocal对象,不同线程中ThreadLocal保存的值是不一样的。new Thread(()->{threadLocal.set(c11_threadLocal.new Person("李四"));Person person2 = threadLocal.get();System.out.println(person2);}).start();}public class Person{String name;public Person(String name){this.name=name;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +'}';}}
}
输出结果:
Person{name=‘王武’}
null
Person{name=‘李四’}
- 原理:源码
看一个set方法的源码
public void set(T value) {//1. 获取当前线程Thread t = Thread.currentThread();//2. 获取当前线程对象中的mapThreadLocal.ThreadLocalMap map = getMap(t);//map不为空,设置值if (map != null)map.set(this, value);//map为空,创建一个mapelsecreateMap(t, value);}//从当前线程对象中获取threadLocals属性。ThreadLocal.ThreadLocalMap getMap(Thread t) {return t.threadLocals;}void createMap(Thread t, T firstValue) {//给t线程对象的threadLocals属性赋值一个ThreadLocalMapt.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);}
总结:
- set值时实际上是set到map中。
- 而这个map是维护在Thread类中的。所以也就是说每个一个Thread对象都会维护一个自己的Map。通过ThreadLocal设的值(set)其实是存放在当前线程的map属性中的。这也就解释了同一个threadLocal对象设置的值在不同的线程中是不共享的。每个线程都会维护一个map来存放。
- ThreadLocal的应用场景:声明式事物。
一个事物方法,方法中有对数据库增删改等操作,操作需要获取connect连接,这一个事物中的连接必须都一样,所以在ThreadLocal中设置一个connect,每一个线程调用这个方法的时候都有自己维护的connect。
4.强软弱虚引用
4.1 强引用
一般情况下普通的引用都是强引用。
eg:
/*** 强引用* 在强引用消失,在垃圾回收的时候会被回收。*/
public class NormalReference {public static void main(String[] args) {A a = new A();a=null;System.gc();while(true){}}
}
类似于这种的都是强引用。
当强引用消失的时候才能够通过System.gc()进行回收垃圾,将person对象回收。
重写Object的finalize()方法。当该对象被回收时会调用finalize方法。我们可以通过观察finalize方法的调用判断A对象是否被回收。
/*** 重写Object类的finalize方法* 该方法在进行回收本类对象的时候会调用。* 回收A对象时会被调用*/
public class A {@Overridepublic void finalize(){System.out.println("进行垃圾回收");}
}
4.2 软引用
- 软引用在内存足够的时候发生gc不会被回收,只有在jvm内存不够使用的时候进行回收。
- 软引用适用于做缓存。
package com.tzw.juc.reference;import java.lang.ref.SoftReference;import static java.lang.Thread.sleep;/**** 1. softReference---》new SoftReference() 强引用* SoftReference() -----> new byte[1024*1024*10] 软引用* 2. 设置软引用分配 10M内存* 3. 调用System.gc(),进行垃圾回收* 4. 回收后重新看软引用是否被回收* 5. 在分配15M内存空间* 6. 查看软引用是否被回收** 设置vm参数,-Xms20M -Xmx20M* 执行结果:** [B@28d93b30* [B@28d93b30* null** 总结:软引用内存够大时,gc不会回收软引用,* 当内存不足时,gc会回收软引用。* 当强引用消失时, new SoftReference()和 软引用都会被回收。**/
public class C_SoftReference {public static void main(String[] args) {SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024*1024*10]);System.out.println(softReference.get());System.gc();try {sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(softReference.get());byte[] b = new byte[1024*1024*15];System.out.println(softReference.get());}
}
4.3 弱引用
- 只要发生gc就会回收弱引用,适用于ThreadLocal
package com.tzw.juc.reference;import java.lang.ref.WeakReference;public class C_WeakReference {public static void main(String[] args) {WeakReference<A> weakReference = new WeakReference<>(new A());System.out.println(weakReference.get());System.gc();//执行为null,弱引用在gc时一定会被回收。System.out.println(weakReference.get());//使用弱引用场景,ThreadLocalThreadLocal threadLocal = new ThreadLocal();threadLocal.set(new A());threadLocal.remove();// 可以当作// map中为 map(WeakReference(ThreadLoack),v);// threadLocal --->new ThreadLocal()强引用,threadLocal强引用指向ThreadLocal对象// map 中的key---》new WeakReference()---->ThreadLocal(弱引用),map中key弱引用指向ThreadLocal对象//分析:如果是强引用的话,如果threadLocal=null时,// 当gc的时候,由于key强引用指向ThreadLocal对象那么,ThreadLocal就永远不会被回收。造成内存泄露。//但是弱引用时,虽然gc会回收弱引用,但是相当于key=null,value值访问不到,那么也会造成内存泄露。//所以使用ThreadLocal时,如果使用完后,需要将remove方法。}}
4.4 虚引用
- 只要发生gc就会回收弱引用。
- 适用于堆外内存回收。由于堆外引用不在jvm内存中,所以无法直接控制,只能同过虚引用控制,当发现队列中有对象时,就去回收堆外内存。
package com.tzw.juc.reference;import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.List;/*** 虚引用时指向直接内存(堆外内存的)不在jvm堆中* 1 虚引用对象不能同个get获取到虚引用的对象* 2 虚引用对象在gc时一定会被回收* 3 虚引用对象被回收之后会放入到引用队列中。就是说引用队列中的对象已经是被回收过的。*/
public class C_PhantomReference {//创建一个引用队列private static final ReferenceQueue<A> QUEUE = new ReferenceQueue<>();public static void main(String[] args) {PhantomReference<A> phantomReference = new PhantomReference(new A(), QUEUE);// 获取A对象System.out.println(phantomReference.get());new Thread(() -> {while (true) {Reference<? extends A> poll = QUEUE.poll();if (poll != null) {System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);}}}).start();List<byte[]> list = new ArrayList<>();while(true){list.add(new byte[1024*1024]);}}
}
这篇关于多线程(3)-源码分析(ReentrantLock-AQS,ThreaLocal)、强软弱虚引用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!