搞定Java多线程、并发编程
2018-12-20 13:16:04 46 举报
AI智能生成
Java多线程、并发编程相关知识整理,希望与大家交流,共同进步。
作者其他创作
大纲/内容
让多个线程顺序执行的方式
利用<font face="宋体"> Thread </font>的 <font face="宋体">join</font> 方法
如 <font face="宋体">t.join() </font>表示将当前线程阻塞,直到线程 <font face="宋体">t</font> 执行完成<br>
定时任务<code></code>
<font face="宋体">Timer & TimerTask </font>类
<font face="宋体">Timer</font>:任务调度器,通过<code>schedule(TimerTask task, long delay)等方法进行调度。</code><br><font face="宋体">TimerTask</font>:实现了 <font face="宋体">Runnable</font> 接口。表示需要调度的任务,里面有一个run方法定义具体的任务。<br>
<font face="宋体">Timer</font> 缺点:内部是单线程,因此如果有异常产生,线程将退出,整个定时任务就会失败
线程池
<font face="宋体">ScheduledExecutorService</font>,它是 <font face="宋体">ExecutorService </font>的子接口。弥补了 <font face="宋体">Timer</font> 的缺陷
通过<br><font face="宋体">scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)</font><br>和<br><font face="宋体">scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)</font><br>等方法来实现定时任务调度
异步任务
只需创建并启动一个线程执行该任务即可,或者使用线程池更好
Callable、Future
CompletableFuture
FutureTask
实现了Future和Runnable接口
并发编程
多线程的三大特性
原子性
线程对共享变量的操作是原子性操作,就是说该操作或多个操作,要么都成功要么都失败
<font face="宋体">对于复合操作而言,synchronized </font>关键字可以保证原子性,而 <font face="宋体">volatile</font> 关键字不能
可见性
线程对共享变量的修改对其它线程立即可见
<font face="宋体">synchronized</font> 和 <font face="宋体">volatile</font> 关键字都能保证可见性
有序性
程序代码执行的结果不受JVM指令重排序的影响<br>
由于 <font face="宋体">volatile</font> 关键字可以禁止指令重排序,因此能保证有序性。
<b><font face="宋体">Lock </font>锁机制可以同时保证以上三个特性</b>
JMM
<font face="宋体">Java Memory Model</font>,即<font face="宋体"> Java </font>内存模型<br>
关键概念
共享内存
即主存,所有线程共享。
(线程)本地内存
也称为“工作内存”。<font face="宋体">JVM</font> 给每个线程都分配了一块内存区域,该块内存是线程独有的。
从架构上看,类似于计算机硬件架构中 <font face="宋体">CPU</font> 与内存间的高速缓存<br>
<font face="宋体">JMM</font> 规定:线程不能直接操作主存,而是只操作属于自己的那部分内存。如果多个线程间需要进行变量共享,必须经过主存。
由于<font face="宋体"> JMM </font>的限制,线程操作变量都要经过以下几个基本步骤:<br>1、从主存中读取变量放入工作内存;<br>2、在工作内存中对变量进行修改操作;<br>3、将操作后的结果同步回主存。
重排序
为了提高程序执行性能,编译器和CPU会对指令进行重排序
hapens-before 原则
如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。
线程安全问题
衡量标准:如果同一个程序在单线程环境与在多线程环境下执行结果一致,就说是线程安全的,反之则是线程不安全
所谓的线程安全问题,其本质在于线程对共享变量操作的原子性、可见性、有序性不能同时满足,因此解决线程安全问题的关键就在于使其同时满足以上三个特性。<br>
<font face="宋体">J.U.C</font>
线程池<font face="宋体"> (Executor)</font><br>
线程池的作用<br>
1、减少资源消耗。通过重复利用池中已创建的线程,减少频繁创建、销毁线程带来的资源消耗。<br>2、提高响应速度。当线程池中有空闲线程,任务到来时无需创建线程就能立即被执行。 <br>3、提高线程的可管理性。由线程池对池中的线程进行统一的管理和监控,可以防止无限制创建线程造成的资源浪费。
<font face="宋体">Executor</font> 接口<br>
其中只有一个<font face="宋体"> void execute(Runnable task)</font> 方法,用于执行任务
<font face="宋体">ExecutorService</font> 接口
<font face="宋体">Executor</font> 接口的子接口
扩展了 <font face="宋体">Executor </font>的功能,提供了管理线程的方法,并且提供了一系列能返回 <font face="宋体">Futrue</font> 对象的异步任务创建方法<br>
常用方法
<font face="宋体">shutdown()、shutdownNow()</font>
通过关闭线程池来拒绝处理新任务
<font face="宋体">shutdown </font>与 <font face="宋体">shutdownNow </font>的区别在于后者会立即关闭,不会等待正在执行的任务执行完毕
<font face="宋体">isShutdown()、isTerminated()</font>
获取线程池是否被关闭的状态
<font face="宋体"><T> Futrue<T> submit(Callable<T> task)<br><T> Futrue<?> submit(Runnable task)<br><T> Futrue<T> submit(Runnable task,T result)</font>
向线程池中提交一个任务,并返回一个<font face="宋体"> Futrue </font>对象用于获取执行结果
<font face="宋体"><T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)<br><T> T invokeAny(Collection<? extends Callable<T>> tasks)</font>
向线程池提交一个任务集合,执行集合中的一个或多个任务,并返回执行一个或多个 Futrue
<font face="宋体">ThreadPoolExecutor</font> 类
是线程池的真正实现,通过在构造方法传入不同的配置参数来创建不同的线程池。
核心配置参数
<font face="宋体">corePoolSize</font>
核心线程数<br>
线程池中存活的最少线程数量,除非 <font face="宋体">allowCoreThreadTimeout</font> 属性值被设成 <font face="宋体">true</font> 那么最少就是0<br>
<font face="宋体">maximumPoolSize</font>
最大线程数
线程池中最多能容纳的线程数量
<font face="宋体">keepAliveTime</font>
指定空闲线程的存活时间,目的是为了减少资源消耗<br>
在以下两种情况下生效:<br>1、当线程池中线程数大于 <font face="宋体">corePoolSize</font>,多余的空闲线程达到 <font face="宋体">keepAliveTime</font> 指定的时间时会被停掉;<br>2、<font face="宋体">当 allowCoreThreadTimeOut <font face="微软雅黑">值为</font> true</font>,核心线程空闲时间达到 <font face="宋体">keepAliveTime</font> 指定的时间时也会被停掉
<font face="宋体">workQueue</font>
任务队列,<font face="宋体">BlockingQueue </font>类型
用于存放待执行的任务,可以是有界队列或无界队列
<font face="宋体">handler</font><code></code>
任务拒绝策略,<font face="宋体">RejectedExecutionHandler </font>类型
用于处理线程池无法执行的任务
4种实现<br>
<font face="宋体">AbortPolicy</font><br>
默认策略,直接抛出<code class="hljs java has-numbering"> RejectedExecutionException</code> 异常
<font face="宋体">CallerRunsPolicy</font>
使用调用线程去处理任务
<font face="宋体">DiscardPolicy</font>
什么事都不干,相当于丢弃任务
<font face="宋体">DiscardOldestPolicy</font>
移除队列头部的任务,然后再次尝试执行当前任务
当以下两种情况发生时,就会执行任务拒绝策略:<br> ① 任务队列满、同时达到最大线程数 <font face="宋体">maximumPoolSize</font>;<br>② 线程池已经被 <font face="宋体">shutdown</font>,无法执行新任务。
工作原理
线程池被创建的时候里面是没有线程的,除非调用了<font face="宋体"> prestartAllCoreThreads()</font> 方法。<br>1、当有任务需要执行并且线程池中线程数量小于<font face="宋体"> corePoolSize </font>时,新创建一个线程去执行这个任务。<br>2、当线程数量达到<font face="宋体"> corePoolSize</font>,并且小于 <font face="宋体">maximumPoolSize</font> 时,分两种情况:<br>1)若任务队列未满,则将后续任务放入任务队列;<br>2)若任务队列已满,则新创建一个线程去执行任务,直到达到最大线程数<font face="宋体"> maximumPoolSize</font>;<br>3、达到最大线程数 <font face="宋体">maximumPoolSize</font> 后,后续来的任务将会被执行拒绝任务策略。<br>
<font face="宋体">Executors</font> 工具类
包含一系列创建线程池的工厂(静态)方法,简化线程池的创建
常用方法<br>
<font face="宋体">Callable<Object> callable(Runnable task)<br><T> Callable<T> callable(Runnable task, T result)</font><br>
包装 <font face="宋体">Runnable <font face="微软雅黑">类型的线程</font></font>为 <font face="宋体">Callable </font>类型<br>
<font face="宋体">newSingleThreadExecutor()</font><br>
创建一个只有单个工作线程的线程池,使用的队列是无界队列
<font face="宋体">newFixedThreadPool(int nThreads)<br></font>
创建固定大小的线程池
<font face="宋体">newCachedThreadPool()</font>
创建一个可根据需要创建线程的线程池,但是可以重用已经创建好的线程。<br>注:它使用的是 <font face="宋体">SynchronousQueue</font>作为任务队列
<font face="宋体">newScheduledThreadPool(int corePoolSize)</font>
创建一个指定大小的线程池,用于定时或周期性的执行任务
<font face="宋体">newSingleScheduledThreadExecutor()</font>
创建一个单线程化的线程池,用于定时或周期性的执行任务
<font face="宋体">newWorkStealingPool()<br>newWorkStealingPool(int parallelism)</font>
JDK 1.8 新增,创建使用 <font face="宋体">Fork/Join</font> 框架执行任务的线程池<br>
线程池大小配置
一般需要根据任务类型来配置线程池大小:<br><p style="text-align: left;">1、如果是 <font face="宋体">CPU</font> 密集型任务,就需要尽量压榨<font face="宋体"> CPU</font>,参考值可以设为 <font face="宋体"><em>N<sub>CPU</sub></em> + 1</font></p><br><p style="text-align: left;">2、如果是 <font face="宋体">IO</font> 密集型任务,参考值可以设置为 <font face="宋体">2 *<em> N<sub>CPU</sub></em></font></p>
通过 <font face="宋体">Runtime.getRuntime().availableProcessors()</font> 获得当前 <font face="宋体">CPU </font>个数
<font face="宋体">Fork/Join</font> (多线程并行框架)
JDK1.7引入的<b>支持多核CPU</b>的多线程并行框架,以充分利用多核CPU的优势。<br>通过它可以实现多线程在多个CPU核心中并行处理任务。<br>
利用分而治之的思想,将大任务分成小任务执行,然后合并结果;分别对应 fork、join 两个操作。
<font face="宋体">Fork/Join</font> 框架的核心是 <font face="宋体">ForkJoinPool</font> 类,它是对 <font face="宋体">AbstractExecutorService</font> 类的扩展。<br><font face="宋体">ForkJoinPool</font> 实现了工作窃取(<font face="宋体">work-stealing</font>)算法,并可以执行<font face="宋体"> ForkJoinTask</font> 任务。
原子操作类
位于 <font face="宋体">java.util.concurrent.atomic</font> 包,使用 <font face="宋体">CAS</font> 实现的原子操作。
包括 AtomicInteger 、AtomicLong 、AtomicDouble 等常用类
比 <font face="宋体">synchronized </font>控制的粒度更细、量级更轻,并且在多核处理器具有高性能<br>
Java 8 又增加了四个类<br>
<font face="宋体">DoubleAccumulator、</font><font face="宋体">DoubleAdder、LongAccumulator、LongAdder</font>
<b>弥补原有的 Atomic 系列类的不足:</b><br>虽然通过CAS保证了并发时操作的原子性,但是高并发也就意味着CAS的失败次数会增多,从而引起更多线程的重试,最后导致效率降低。
<font face="宋体">LongAdder VS AtomicLong</font><br>
较低并发时两个类差不多,而高并发时使用<font face="宋体"> LongAdder</font> 更高效,但也会消耗更多的空间
并发集合
<font face="宋体">ConcurrentHashMap</font>
由于<font face="宋体"> HashMap <font face="微软雅黑">在多线程并发 <font face="宋体">put </font>操作情况下的 <font face="宋体">rehash</font> 会导致死循环</font></font>,Java 7 引入了分段锁技术 <font face="宋体">Segment</font> 来保证一定的线程安全,<font face="宋体">Segment</font> 继承于 <font face="宋体">ReentrantLock</font>
为了进一步提高并发性及访问效率,Java 8 采用 <font face="宋体">CAS</font> 替代<font face="宋体"><font face="微软雅黑">了</font> Segment,<font face="微软雅黑">同时</font></font>加入了红黑树(当链表中结点数达到临界值8时就将其转成红黑树)
<font face="宋体">BlockingQueue</font>
<b>阻塞队列:</b><br>1)当队列为空时,获取元素的线程会等待,直到队列中有元素;<br>2)当队列满时,存储元素的线程会等待,直到队列可以存储元素。<br>通常用于生产者-消费者场景
<font face="宋体">ArrayBlockingQueue</font>
有界队列,即队列容量有限,内部是数组结构
<font face="宋体">LinkedBlockingQueue</font>
既可以是有界队列,又可以是无界队列,内部是单向链表结构。若创建时不指定容量,则默认是 <font face="宋体">Integer.MAX_VALUE</font>
<font face="宋体">SynchronousQueue</font> (同步队列)
不保存元素,仅仅作为两个线程进行单向数据通信(如生产者/消费者场景)的临时通道
<font face="宋体">PriorityBlockingQueue</font> (优先级阻塞队列)
属于无界队列,队列中的元素按优先级排列
使用了和 <font face="宋体">PriorityQueue</font> 一样的排序规则,同时具备 <font face="宋体">BlockingQueue</font> 的特性<br>
通过在构造方法传入 <font face="宋体">Comparator</font> 比较器来决定元素顺序;<br>如果不提供比较器,则采用自然排序(即通过 <font face="宋体">Comparable<font face="微软雅黑"> 的 </font>compareTo<font face="微软雅黑">方法,因此插入队列中的元素必须实现 <font face="宋体">Comparable</font> 接口)</font></font><br>
<font face="宋体">CopyOnWriteArrayList</font>
从字面上看就是”写时复制“。原理是当需要对集合中元素进行增删改操作时首先复制一个副本,对副本进行操作
适用于“读多写少”的并发场景
......
并发工具<br>
<font face="宋体">CountDownLatch</font> (倒计数器)
利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他几个任务执行完毕之后才能执行,就可以使用<font face="宋体"> CountDownLatch </font>来实现。
内部使用了 <font face="宋体">AQS </font>的共享锁机制
两个关键方法:<br>1. <font face="宋体">await() <font face="微软雅黑">当前线程</font></font>等待其它线程执行完毕(即计数器减到0),当前线程恢复执行;<br>2. <font face="宋体">countDown()</font> 当有一个线程执行完毕就将计数器减1,直至减到0。<br>
应用场景
模拟多个线程并发
<font face="宋体">CyclicBarrier</font>(回环栅栏)
通过它可以实现让一组线程等待至某个状态之后再全部同时执行。<br>叫做回环是因为当所有等待线程都被释放以后,<font face="宋体">CyclicBarrier </font>可以被重用(通过调用 <font face="宋体">reset() </font>方法)。
<font face="宋体">await() </font>方法,此方法作用是等待其它线程都到同一状态时开始同时执行。类似于长跑比赛中,当所有运动员都准备完毕才开始比赛。<br>
<font face="宋体">Semaphore <font face="微软雅黑">(</font></font>信号量)<br>
用于限制访问的最大线程数
维护一个访问许可集,其大小由初始化时的构造器参数指定。
主要方法<br>
通过<font face="宋体"> acquire </font>和 <font face="宋体">release </font>方法获取和释放访问许可。其获取的方式有公平和非公平两种,默认是非公平
<font face="宋体">acquire(int permits)</font>、<font face="宋体">tryAcquire(int permits)</font> <br>
<font face="宋体"><font face="微软雅黑">获取指定数量的访问许可,相当于在可用的数量上减去</font></font>
<font face="宋体">release(int</font><font face="宋体"><font face="宋体"> permits</font>)、tryRelease(int</font><font face="宋体"><font face="宋体"> permits</font>) </font><br>
<font face="宋体"><font face="微软雅黑"><font face="宋体"><font face="微软雅黑">释放指定数量的访问许可,</font></font>相当于在可用的数量上增加</font></font>
<font face="宋体">Exchanger <font face="微软雅黑">(交换器</font>)<br></font>
用于成对的线程间进行数据交换,可以看成是一个双向的 <font face="宋体">SynchronousQueue</font><br>
<font face="宋体">V exchange(V x)</font> 方法
<font face="宋体">V exchange(V x, long time, TimeUnit unit) <font face="微软雅黑">方法</font></font><br>
<font face="宋体">ThreadLocal</font> (线程本地变量)
它为每个线程都提供一个独立的变量副本,各个线程都可以改变自己的变量副本,各个线程间互不影响。<br>
实现的思路:<br>1)在 <font face="宋体">ThreadLocal</font> 类中有一个静态内部类 <font face="宋体">ThreadLocalMap</font>,用于存储每一个线程的变量副本,键为当前<font face="宋体"> ThreadLocal</font> 对象,而值则是对应线程的变量副本;<br>2)每个 <font face="宋体">ThreadLocal</font> 实例都有一个唯一的<font face="宋体"> threadLocalHashCode</font>(这个值将会用于在 <font face="宋体">ThreadLocalMap</font> 中找到<font face="宋体"> ThreadLocal</font> 对应的<font face="宋体"> value</font> 值)。
工作原理:<br>1)在 <font face="宋体">Thread</font> 类中维护一个<font face="宋体"> ThreadLocalMap</font> 类型的属性 <font face="宋体">threadLocals</font>(与线程进行绑定,避免了线程安全问题);<br>2)取值 <font face="宋体">get</font> 操作:<br>① 首先从 <font face="宋体">ThreadLocal </font>中取变量值时先获取当前线程,然后获得当前线程的 <font face="宋体">threadLocals</font> 属性(即与当前线程关联的<font face="宋体"> ThreadLocalMap</font>);<br>② 然后再从 <font face="宋体">ThreadLocalMap</font> 中根据当前<font face="宋体"> ThreadLocal</font> 获取到其中的变量值(如果 <font face="宋体">ThreadLocalMap</font> 为空,则返回初始化方法 <font face="宋体">initialValue() </font>的值,默认是<font face="宋体"> null</font>,可通过重写该方法自定义初始值);<font face="宋体"><br><font face="微软雅黑">3)</font>set</font> 操作与 <font face="宋体">get </font>类似。
注:<br>1、<font face="宋体">ThreadLocal</font> 的目的不是解决线程共享变量问题,而是隔离线程。每个线程访问的都是属于自己的变量,因此避免了线程安全问题。<br>2、<font face="宋体">ThreadLocal</font> 适用于变量需要在线程间隔离而在方法间需要共享的场景。
线程死锁
死锁产生的四个必要条件
<ul><li>互斥条件:资源不能被共享。即任一时刻一个资源只能给一个进程使用,其他进程只能等待,直到资源被占有者释放。</li><li>不可剥夺条件:已经分配的资源不能从相应的进程中被强制地剥夺,而只能由获得该资源的进程自愿释放。</li><li>请求和保持条件:已经得到资源的进程可以再次申请新的资源。</li><li>循环等待条件:系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。<br> </li></ul>
预防死锁的方法
合理对资源进行动态分配,以避免死锁
破坏死锁产生的四个必要条件
线程与进程<br>
线程是程序的最小执行单元
进程是操作系统进行资源分配和调度的一个基本单位
关联:一个程序至少有一个进程,一个进程又至少包含一个线程。进程中的多个线程共享进程的资源
引入线程的目的:充分利用 <font face="宋体">CPU </font>资源,使其可以并行处理多个任务,减少时间消耗,提高效率
线程分类
用户线程 <font face="宋体">User Thread</font><br>
一般是程序中创建的线程<br>
守护线程 <font face="宋体">Daemon Thread</font>
为用户线程服务的线程,当所有用户线程结束时才会被终止。如JVM的垃圾回收。
通过 <font face="宋体">Thread</font> 的 <font face="宋体">setDaemon(true)</font> 方法将一个用户线程变成守护线程
线程的生命周期
六种状态
新建状态 <font face="宋体">New</font><br>
<font face="宋体">Thread</font> 类
实现了<font face="宋体"> Runnable </font>接口
<font face="宋体">run</font> 方法,无返回值
<font face="宋体">Runnable</font> 接口
<font face="宋体">run</font> 方法,无返回值,通过<font face="宋体"> Thread</font> 类或线程池来使用
<font face="宋体">Callable</font> 接口
作为<font face="宋体"> FutureTask</font> 构造方法参数使用
<font face="宋体">call </font>方法,有返回值,且可以抛出异常
call方法实际是在 <font face="宋体">Runnable</font> 的 <font face="宋体">run</font> 方法中被执行的<br>
就绪状态<font face="宋体"> Runnable</font>
调用<font face="宋体"><font face="微软雅黑">新建线程的 </font>start()</font> 方法
不一定会立即运行,可能需要等待<font face="宋体"> CPU</font> 分配时间片
阻塞状态<font face="宋体"> Blocked</font>
调用 <font face="宋体">Object</font> 的<font face="宋体"> wait</font> 方法后等待同步锁的状态
等待 <font face="宋体">Waiting</font>
发生在调用以下几个方法时:<br><ul><li>不带参数的<font face="宋体"> Object.wait()</font></li><li>不带参数的<font face="宋体"> Thread.join()</font></li><li><font face="宋体">LockSupport.park()</font></li></ul>
超时等待<font face="宋体"> Timed-Waiting</font>
与 <font face="宋体">Waiting</font> 状态不同在于不会一直等待,而是等待指定的时间
发生在调用以下几个方法时:<br><ul><li><font face="宋体">Thread.sleep()</font></li><li><font face="宋体">Object.wait(long timeout)</font></li><li><font face="宋体">Thread.join(long timeout) </font></li><li><font face="宋体">LockSupport.parkNanos()</font></li><li><font face="宋体">LockSupport.parkUntil()</font></li></ul>
终结状态 <font face="宋体">Terminated</font>
当线程运行完毕,即死亡
<font face="宋体">Thread </font>类的常用方法
<font face="宋体">Thread.sleep(long millseconds) </font>方法
1、<font face="宋体">sleep </font>方法是 <font face="宋体">Thread </font>类的静态方法,是为了保证该操作只对当前线程有效,避免线程安全问题,其它几个常用静态方法类似。<br>2<code><font face="微软雅黑">、让当前正在运行的线程</font></code><font face="微软雅黑"><code></code></font>暂时停止运行,一段时间后会继续执行。
<font face="宋体">Thread.yield() </font>方法
当前处于运行状态的线程主动放弃占用的CPU资源,转变为就绪状态,让其他先线程执行(让步)
<font face="宋体">join </font>方法
在当前线程执行过程中引入另一个线程,并且当前线程需要等待另一个线程执行完毕后才能继续执行
<font face="宋体">Thread.currentThread() </font>方法
获取当前正在运行的线程
线程间通信
共享变量
等待/通知机制
<font face="宋体">Object</font> 的对象监视器方法
<span>这三个方法依赖于对象监视器,所以必须在 <font face="宋体">synchronized</font> 语句块内使用,否则会抛出 </span><span><font face="宋体">IllegalMonitorStateException<em></em></font> 异常。</span>
<font face="宋体">wait </font>方法<br>
使得当前线程必须要等待(阻塞状态),等到另外一个线程调用notify()或者notifyAll()方法。
<font face="宋体">notify</font> 方法
唤醒一个等待当前对象的锁的线程(一般是高优先级的线程)开始排队
<font face="宋体">notifyAll </font>方法
方法会唤醒其它所有等待当前对象的锁的线程
<font face="宋体">Lock、Condition</font>
<font face="宋体">Lock<font face="微软雅黑"> 的 </font>lock、tryLock、unlock、getCondition </font>等方法
<font face="宋体">Condition <font face="微软雅黑">的</font> await、signal、signalAll</font> 方法
使用 <font face="宋体">volatile</font> 关键字<br>
数据传递
<font face="宋体">PipedInputStream/PipedOutputStream </font>管道流
<font face="宋体">BlockingQueue <font face="微软雅黑">阻塞队列</font><br></font>
<font face="宋体">Exchanger</font> 交换器<br>
停止线程的方法
<font face="宋体">Thread.stop </font>方法:已废弃
使用一个标识来表示线程的状态,通过更改它的值来控制线程的运行和停止<br>
<font face="宋体">interrupt</font> 中断方法<br>
线程同步
<font face="宋体">synchronized</font><br>
在 <font face="宋体">JVM</font> 层面实现了对临界资源的同步互斥访问,锁的释放不用人工干预,由虚拟机自动完成。
1、在 <font face="宋体">volatile </font>基础上增加了互斥锁,所谓“互斥”就是同一时间只能有一个线程操作该资源;<br>2、<span>在 JDK 1.5 版本以后,为了弥补<font face="宋体"> synchronized </font>的不足,引入了<font face="宋体"> Lock </font>来代替它,将同步锁对象换成了 <font face="宋体">Condition</font> 对象,并且 <font face="宋体">Condition </font>对象可以有多个。</span>
用法
同步代码块
通常将外界资源作为锁的对象
<font face="宋体">synchronized(obj) {<br> // 同步操作代码<br>}</font><br>
用于保护外界资源不被多个线程并发修改
与同步方法比较而言,使用同步代码块的好处在于其他线程仍可以访问同步代码块以外的代码
同步方法
锁的对象是当前对象this
<font face="宋体">public synchronized void test() {<br> // 同步操作代码<br>}</font><br>
用于保护对象属性值不会被多个线程并发修改
同步静态方法
锁的对象是类的Class对象
<font face="宋体">public static synchronized void test() {<br> // 同步代码<br>}</font><br>
用于保护类的静态属性值不会被多个线程并发修改
<b>缺点:</b><br>1、无法知道是否成功获取到锁<br>2、如果是多个线程需要同时进行读操作,一个线程读操作时其它线程只有等待<br>
<b>注:为了确保所有线程都能看到共享变量的最新值,因此所有执行读操作或写操作的线程都必须在同一个锁上同步。</b>
<font face="宋体">volatile</font>
线程每次都从主内存中读取变量,改变后再写回到主内存。其作用如下:<br>1、使变量在多个线程间具有可见性(volatile 变量不会被缓存到寄存器或其他对处理器不可见的地方)<br>2、禁止 <font face="宋体">JVM</font> 对该变量的操作进行指令重排序(保证有序性)
使用条件
1、对变量的写操作不依赖于变量的当前值,或者保证仅有一个线程对变量进行写操作;<br>2、该变量不会和其他状态变量一起被纳入不变性条件中;<br>3、访问变量不需要加锁。<br>
应用场景
一写多读,保证变量可见性
开销较低的读写锁策略
将变量使用 <font face="宋体">volatile </font>关键字修饰,只在多个线程同时需要进行写操作时加锁,读操作不需要
加写锁方式
<font face="宋体">synchronized </font>同步关键字
使用 <font face="宋体">Lock</font>
<font face="宋体">volatile VS synchronized</font>
<font face="宋体"><font face="微软雅黑">访问</font> volatile <font face="微软雅黑">变量不需要加锁,</font></font><font face="宋体"><font face="微软雅黑"><font face="宋体"><font face="微软雅黑">因此不会阻塞线程,因此它</font></font>是比 </font>synchronized <font face="微软雅黑">更轻量级的同步机制。</font><br><font face="微软雅黑">但是 </font>volatile</font> 不能完全替代 <font face="宋体">synchronized</font>,因为它<b>不能保证原子性,非线程安全,</b>而加锁机制既可以确保可见性,又可以确保原子性。
<font face="宋体">CAS (Compare And Swap)<br></font>
<font face="宋体">Unsafe</font> 类<br>
该类提供了在 <font face="宋体">Java</font> 中能够像 <font face="宋体">C/C++ </font>语言使用指针直接操作内存的功能,由于直接操作内存可能会发生风险,因此Java官方不建议直接使用该类,也没有提供关于该类的文档。但是在 <font face="宋体">JDK</font> 源码中为了提高运行效率在很多地方都使用了该类。<br>
包含的功能
内存管理:包括分配内存、释放内存、获取变量内存地址偏移量、通过变量地址偏移量获取和修改变量值等
线程操作:挂起与恢复
<font face="宋体">park、unpark</font> 方法
<font face="宋体">CAS</font> 无锁技术<br>
<font face="宋体">CAS </font>即 <font face="宋体">Compare And Swap</font> 的缩写,属于乐观锁的一种实现方式。
通过 <font face="宋体">JNI</font> 调用 <font face="宋体">CPU </font>的 <font face="宋体">CAS </font>指令实现,在硬件层面实现了原子操作
实现原理:<br><font face="宋体">CAS </font>有3个操作数,内存值 <font face="宋体">V</font>,旧的预期值 <font face="宋体">A</font>,要修改的新值 <font face="宋体">B</font> 。当且仅当预期值<font face="宋体"> A </font>和内存值 <font face="宋体">V </font>相同时,将内存值 <font face="宋体">V</font> 修改为 <font face="宋体">B</font>,否则什么都不做。<br>若操作成功则返回结果,否则会进行重试直到成功为止。<br>
<font face="宋体">Java</font> 中的原子操作类如 <font face="宋体">AtomicInteger</font> 等类底层就是依赖它实现的
<font face="宋体">LockSupport</font>工具类
用于操作线程,是锁和同步类的基础。基于 <font face="宋体">Unsafe <font face="微软雅黑">中的</font> park、unpark </font>实现
用来代替 <font face="宋体">wait、notity、notifyAll</font> 。<br>因为 <font face="宋体">LockSupport </font>对<font face="宋体"> park </font>方法和 <font face="宋体">unpark </font>方法的调用没有先后的限制,而<font face="宋体"> wait <font face="微软雅黑">方法则</font></font>必须保证在 <font face="宋体">notify/notifyAll </font>之前被调用。
<font face="宋体">park()、parkNanos(long timeout)、parkUntil(long deadLine) </font>方法表示将当前线程挂起,后两个表示挂起一段时间
<font face="宋体">unpark(Thread thread) </font>方法取消指定线程 <font face="宋体">thread </font>挂起
JDK 1.6 后增加了一个方法参数 <font face="宋体">blocker</font>,表示锁的对象,在通过线程监控和分析工具来定位问题时很有用
<font face="宋体">AQS</font> 同步器
<font face="宋体">AbstractQueuedSynchronizer</font> 类的简称
<b>实现了线程同步的语义,是 <font face="宋体">Java </font>中<font face="宋体"> Lock</font> 锁机制的基础。<br></b>通过它可以很方便的实现同步器。<b><br></b>
核心思想:<br>1、基于<font face="宋体"> volatile</font> 类型的<font face="宋体"> state </font>变量,配合 <font face="宋体">Unsafe</font> 类的 <font face="宋体">CAS </font>操作来实现对当前锁状态 <font face="宋体">state</font> 进行修改;<br>2、内部依赖一个双向队列来完成资源获取线程的排队工作。
其中定义了两种获取锁的方式
共享方式 <font face="宋体">SHARED</font>
同一时间可以有多个线程获得锁,多个线程共享一把锁
采用共享方式得到的锁即为<b>共享锁</b>
适用于多个线程同时进行读操作场景
排他方式 <font face="宋体">EXCLUSIVE</font>
同一时间只能有一个线程获取到锁<br>
采用排他方式得到的锁即为<b>排他锁,也称为独占锁</b>
适用于多个线程同时进行写操作时同步
<font face="宋体">Lock</font> 接口
<font face="宋体">JDK</font> 层面实现,比起 <font face="宋体">synchronized</font> 可控性更强,弥补了 <font face="宋体">synchronized</font> 的不足。<b>使用后必须手动释放锁,否则可能会导致死锁</b><br>
<font face="宋体">lock</font>:如果没获取到,会一直阻塞直到成功获取到锁;<br><font face="宋体">tryLock:</font>尝试获取锁,获取到则返回true;如果没获取到则返回false,不会一直阻塞,可以指定等待时间;<br><font face="宋体">unlock</font>:释放锁。<br><b>都需要显示调用</b>。<br>
典型用法:<br>lock.lock();<br>try {<br> // do something<br>} finally {<br> lock.unlock();<br>}
<font face="宋体">ReentrantLock</font> 可重入锁<br>
包括公平锁 <font face="宋体">FairSync</font>、非公平锁<font face="宋体"> NonfairSync </font>两种实现,默认是非公平
公平锁是指线程按请求获取锁的先后顺序获取锁,而非公平锁则允许在线程发出请求后立即尝试获取锁,如果可用则可直接获取锁,尝试失败才进行排队等待
<font face="宋体">ReadWriteLock </font>读写锁<br>
允许多个线程对资源同时进行读操作,或一个线程写操作,但二者不能同时发生<br>
<font face="宋体">ReadLock</font> 读锁<br>
属于共享锁实现
<font face="宋体">WriteLock </font>写锁<br>
属于排他锁实现<br>
<font face="宋体">Condition</font> 接口
提供了类似 <font face="宋体">Object</font> 类中<font face="宋体"> wait、notify、notifyAll</font> 的方法,主要包括 <font face="宋体">await、signal、signalAll</font> 方法<br>
与 <font face="宋体">synchronized</font> 和 <font face="宋体">wait、notify、notifyAll </font>的搭配类似,这些方法与<font face="宋体"> Lock</font> 锁配合使用也可以实现等待/通知机制
0 条评论
下一页