JVM并发编程&AQS&sync锁&ReentrantLock&线程池&ThreadLocal ...

打印 上一主题 下一主题

主题 1068|帖子 1068|积分 3204

synchronized锁实现

synchronized是一个关键字,实现同步,还需要我们提供一个同步锁对象,记载锁状态,记载线程信息
控制同步,是依赖底层的指令实现的.
如果是同步方法,在指令中会为方法添加ACC_SYNCHRONIZED标志
如果是同步代码块,在进入到同步代码块时,会实行monitorenter, 脱离同步代码块时或者出异常时,实行monitorexit
AQS

AQS(AbstractQueuedSynchronizer 抽象同队伍列) 是一个实现线程同步的框架
并发包中很多类的底层都用到了AQS
  1. class AbstractQueuedSynchronizer {
  2.     private transient volatile Node head;
  3.     private transient volatile Node tail;
  4.     private volatile int state; //表示有没有线程访问共享数据  默认是0 表示没有线程访问
  5.     //修改状态的方法
  6.      protected final boolean compareAndSetState(int expect, int update) {
  7.         return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
  8.     }
  9.    
  10.      static final class Node {
  11.          volatile Node prev;
  12.          volatile Node next;
  13.          volatile Thread thread;
  14.      }
  15.    
  16. }
复制代码
ReentrantLock实现

ReentrantLock完全同过java代码控制
  1. class ReentrantLock{
  2.      abstract static class Sync extends AbstractQueuedSynchronizer {
  3.             abstract void lock();
  4.      }
  5.      //非公平锁
  6.      static final class NonfairSync extends Sync {
  7.           void lock(){
  8.               
  9.           }
  10.          
  11.      }
  12.      //公平锁
  13.      static final class FairSync extends Sync {
  14.          void lock(){
  15.               
  16.           }
  17.      }
  18. }
复制代码

JUC 常用类

在集合类中,像Vector,Hashtable这些类加锁时都是直接把锁加载方法上了,性能就低, 在并发访问量小的环境下,还可以利用, 大并发访问量下,性能就太低了.
ConcurrentHashMap
  1. HashMap适合单线程场景下的,不允许多个线程同时访问操作,如果有多线程访问会报异常
  2. Hashtable 是线程安全的 直接给方法加锁,效率低
  3. ConcurrentHashMap 是线程安全的,没有直接给方法加锁, 用哈希表中每一个位置上的第一个元素(第一个是存在元素)作为锁对象
  4.       哈希表长度是16,那么就有16把锁对象,锁住自己的位置即可,
  5.       这样如果多个线程如果操作不同的位置,那么相互不影响,只有多个线程操作同一个位置时,才会等待
  6.       如果位置上没有任何元素,那么采用cas机制插入数据到对应的位置
  7.          Hashtable ,ConcurrentHashMap  键值都不能为null
  8.          为什么这样设计,键值都不能为null?
  9.           map.put("b","b")
  10.          为了消除歧义  System.out.println(map1.get("a"));//null  值是null  还是键不存在返回null
复制代码
CopyOnWriteArrayList
  1. ArrayList 是单线程场景下使用的,在多线程场景下会报异常
  2. Vector 是线程安全的,在方法上加了锁,效率低
  3. CopyOnWriteArrayList  写方法操作加了锁(ReentrantLock实现的),
  4. 在写入数据时,先把原数组做了备份,把要添加的数据写入到备份数组中,当写入完成后,再把修改的数组赋值到原数组中去
  5. 给写加了锁,读没有加锁,读的效率变高了, 这种适合写操作少,读操作多的场景
复制代码
CopyOnWriteArraySet
  1. CopyOnWriteArraySet 的实现基于 CopyOnWriteArrayList,不能存储重复数据。
复制代码
辅助类 CountDownLatch
池的概念

字符串常量池
String s1 = “abc”; String s2=“abc”; s1==s2//true
Integer主动装箱 缓存了-128 --+127之间的对象
Integer a = 100; Integer b = 100; a==b //true IntegerCache.cache[i + (-IntegerCache.low)];
数据库连接池
阿里巴巴Druid数据库连接池
帮我们缓存一定数量的链接对象,放在池子里,用完还回到池子中,
减少了对象的频繁创建和销毁的时间开销
线程池

为减少频繁的创建和销毁线程,
jdk5开始引入了线程池,
建议利用ThreadPoolExecutor类来创建线程池, 这样进步服从.
Java.uitl.concurrent.ThreadPoolExecutor
  1. public ThreadPoolExecutor(int corePoolSize,
  2.                               int maximumPoolSize,
  3.                               long keepAliveTime,
  4.                               TimeUnit unit,
  5.                               BlockingQueue<Runnable> workQueue,
  6.                               ThreadFactory threadFactory,
  7.                               RejectedExecutionHandler handler)
复制代码
7个参数
corePoolSize: 核心线程池中的数量(初始化的数量) 5
maximumPoolSize:线程池中最大的数量 10 5
keepAliveTime: 空闲线程存活时间 当核心线程池中的线程足以应付使命时, 非核心线程池中的线程在指定空闲时间到期后,会销毁.
unit: 时间单位
workQueue: 5 等待队列, 当核心线程池中的线程都在利用时,如果有使命继承到来,会先将等待的使命放到队列中,如果队列也满了,才会创建新的线程(非核心线程池中的线程)
threadFactory:线程工厂,用来创建线程池中的线程
handler:拒绝处置惩罚使命时的策略 4种拒绝策略
线程池工作流程
当有大量的使命到来时,先判定核心线程池中的线程是否都忙着,
​ 有空闲的,直接让核心线程中的线程实行使命
​ 没有空闲的, 判定等待队列是否已满,
​ 如果没满,把使命添加到队列等待
​ 如果已满,判定非核心线程池中的线程是否都忙着
​ 如果有空闲的,没满,交由非核心线程池中的线程实行
​ 如果非核心线程池野已经满了,那么就利用对应的拒绝策略处置惩罚.
4种拒绝策略:
AbortPolicy: 抛异常
CallerRunsPolicy: 由提交使命的线程实行 比方在main线程提交,则由main线程实行拒绝的使命 DiscardOldestPolicy: 丢弃等待时间最长的使命
DiscardPolicy: 丢弃末了不能实行的使命
提交使命的方法
  1. void   execute(任务);  提交任务没有返回值
  2. Future<?> submit = executor.submit(myTask);//提交任务可以接收返回值
  3. submit.get();  
复制代码
关闭线程池
  1. shutdown();  执行shutdown()后,不再接收新的任务,会把线程池中还有等待队列中已有的任务执行完,再停止
  2. shutdownNow(); 立即停止,队列中等待的任务就不再执行了.
复制代码
  1.   如果有空闲的,没满,交由非核心线程池中的线程执行
复制代码
​ 如果非核心线程池野已经满了,那么就利用对应的拒绝策略处置惩罚.
4种拒绝策略:
AbortPolicy: 抛异常
CallerRunsPolicy: 由提交使命的线程实行 比方在main线程提交,则由main线程实行拒绝的使命 DiscardOldestPolicy: 丢弃等待时间最长的使命
DiscardPolicy: 丢弃末了不能实行的使命
提交使命的方法
  1. void   execute(任务);  提交任务没有返回值
  2. Future<?> submit = executor.submit(myTask);//提交任务可以接收返回值
  3. submit.get();  
复制代码
关闭线程池
  1. shutdown();  执行shutdown()后,不再接收新的任务,会把线程池中还有等待队列中已有的任务执行完,再停止
  2. shutdownNow(); 立即停止,队列中等待的任务就不再执行了.
复制代码
ThreadLocal

ThreadLocal中添补的变量属于当火线程,改变量对其他线程而言是隔离的
ThreadLocak为变量在每个线程创建了一个副本,每个线程可以访问自己内部的副本变量
  1. static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
  2. @Override
  3.         protected Integer initialValue() {
  4.         return 1;
  5.         }
  6. };
复制代码
ThreadLocal原理

首 先 ThreadLocal 是 一 个 泛 型 类 , 保 证 可 以 接 受 任 何 类 型 的 对 象 ,ThreadLocal 内 部 维 护 了 一 个 Map , ThreadLocal 实 现 了 一 个 叫做 ThreadLocalMap 的静态内部类。
而我们利用的 get()、set() 方法其实都是由这个 ThreadLocalMap 类对应的 get()、set() 方法实现的。首 先 ThreadLocal 是 一 个 泛 型 类 , 保 证 可 以 接 受 任 何 类 型 的 对 象 。


终极变量是放在当火线程的ThreadLocalMap中,并不是存在ThreadLocal上,ThreadLocal重要作为key,用于存读操作
内存泄漏

当对象已经不再被引用,但是垃圾回收机制无法回收该对象,就会产生内存泄漏题目(比方数据库链接对象,流对象,socker)
强引用:

当内存不敷,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象举行回收,死都不收。
强引用是我们最常见的普遍对象引用,只要另有强引用指向一个对象,就能表明对象还活着,垃圾收集器不会碰这种对象。在JAVA最常见的就是强引用,把一个对象赋给一个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收,因此强引用时造成Java内存泄漏的重要原因之一。
对于一个平常的对象,如果没有其他的引用关系,只要凌驾了引用的作用域或者显式地将相应(强)引用复制为null,一版认为就是可以背垃圾收集了。
  1. public class StrongReferenceDemo {
  2.     public static void main(String []args) {
  3.         Object obj1 = new Object(); //这样定义默认是强引用
  4.         Object obj2 = obj1;
  5.         obj1 = null;
  6.         System.gc();
  7.         System.out.println(obj1);
  8.         System.out.println(obj2);
  9.     }
  10. }
复制代码
对象如果有强引用关系,必定不会被回收
软引用

软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集,对于只有软引用的对象来说,
当系统内存富足时它不会被回收,当系统内存不敷时它会被回收。
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时间就保存,不够用就回收!
  1. public class SoftReferenceDemo {
  2.     public static void main(String []args) {
  3.         Object o1 = new Object();
  4.         SoftReference<Object> softReference = new SoftReference<Object>(o1);
  5.         System.out.println(o1);
  6.         System.out.println(softReference.get());
  7.         o1 = null;
  8.         System.gc();
  9.         try {
  10.             byte[] bytes = new byte[30*1024*1024];
  11.         } finally {
  12.             System.out.println(o1);
  13.             System.out.println(softReference.get());
  14.         }
  15.     }
  16. }
复制代码
弱引用

弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存区更短。
对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否富足,都会回收该对象占用的内存。
  1. public class WeakReferenceDemo {
  2.     public static void main(String[] args) {
  3.         Object o1 = new Object();
  4.         WeakReference<Object> weakReference = new WeakReference<Object>(o1);
  5.         System.out.println(o1);
  6.         System.out.println(weakReference.get());
  7.         o1 = null;
  8.         System.gc();
  9.         System.out.println("----------------------------");
  10.         System.out.println(o1);
  11.         System.out.println(weakReference.get());
  12.     }
  13. }
复制代码
虚引用

虚引用需要java.lang.ref.PhantomReference类来实现。
顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时间都可能被垃圾回收
器回收,它不能单点利用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)团结利用。
虚引用的重要作用是跟踪对象被垃圾回收的状态,仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。PhantomReference的get方法总是返回null,因此无法访问对应的引用对象,其意义在于分析一个对象已经进入了finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。
换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时间收到一个系统通知或者后续添加进一步的处置惩罚。Java技术允许利用fianlize()方法在垃圾收集器将对象从内存中清除出去之前做须要的清算工作
  1. public class PhantomReferenceDemo {
  2.     public static void main(String []args) throws Exception {
  3.         Object o1 = new Object();
  4.         ReferenceQueue<Override> referenceQueue = new ReferenceQueue<>();
  5.         PhantomReference<Object> phantomReference = new PhantomReference(o1,referenceQueue);
  6.         System.out.println(o1);
  7.         System.out.println(phantomReference.get());
  8.         System.out.println(referenceQueue.poll());
  9.         System.out.println("-----------------");
  10.         o1 = null;
  11.         System.gc();
  12.         Thread.sleep(500);
  13.         System.out.println(o1);
  14.         System.out.println(phantomReference.get());
  15.         System.out.println(referenceQueue.poll());
  16.     }
  17. }
复制代码
ThreadLocal内存泄漏


TreadLocalMap 利用 ThreadLocal 的弱引用作为 key,如果一个 ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被 GC 回收,这样就会导致ThreadLocalMap 中 key 为 null, 而 value 还存在着强引用,只有 thead 线程退出以后,value 的强引用链条才会断掉。
但如果当火线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄漏。
ThreadLocal 正确的利用方法
每次利用完 ThreadLocal 都调用它的 remove()方法清除数据。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

笑看天下无敌手

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表