经典干货《Java 多线程编程核心技术》学习笔记及总结(中)
Posted Java我最强
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了经典干货《Java 多线程编程核心技术》学习笔记及总结(中)相关的知识,希望对你有一定的参考价值。
作者 | zhisheng_tian
编辑 | Sandra
原文 | http://www.jianshu.com/p/bcb939d6b56c
第三章
线程间通信
技术点:
1、使用 wait/notify 实现线程间的通信
2、生产者/消费者模式的实现
3、方法 join 的使用
4、ThreadLocal 类的使用
等待/通知机制
wait 使线程停止运行,notify 使停止的线程继续运行。
关键字 synchronized 可以将任何一个 Object 对象作为同步对象看待,而 Java 为每个 Object 都实现了 wait() 和 notify() 方法,他们必须用在被 synchronized 同步的 Object 的临界区内。通过调用 wait 方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而 notify 操作可以唤醒一个因调用了 wait 方法而处于阻塞状态的线程,使其进入就绪状态。被重新唤醒的线程会试图重新获得临界区的控制权,继续执行临界区内 wait 之后的代码。
wait 方法可以使调用该方法的线程释放共享资源的锁,从运行状态退出,进入等待状态,直到再次被唤醒。
notify() 方法可以随机唤醒等待对列中等待同一共享资源的一个线程,并使该线程退出等待状态,进入可运行状态。
notifyAll() 方法可以随机唤醒等待对列中等待同一共享资源的所有线程,并使这些线程退出等待状态,进入可运行状态。
线程状态示意图:
◾ 新创建一个线程对象后,在调用它的 start() 方法,系统会为此线程分配 CPU 资源,使其处于 Runnable(可运行)状态,如果线程抢占到 CPU 资源,此线程就会处于 Running (运行)状态 。
◾ Runnable 和 Running 状态之间可以相互切换,因为线程有可能运行一段时间后,有其他优先级高的线程抢占了 CPU 资源,此时线程就从 Running 状态变成了 Runnable 状态。
线程进入 Runnable 状态有如下几种情况:
1、调用 sleep() 方法后经过的时间超过了指定的休眠时间 ;
2、线程调用的阻塞 IO 已经返回,阻塞方法执行完毕;
3、线程成功的获得了试图同步的监视器;
4、线程正在等待某个通知,其他线程发出了通知;
5、处于挂状态的线程调用了 resume 恢复方法。
◾ Blocked 是阻塞的意思,例如线程遇到一个 IO 操作,此时 CPU 处于空闲状态,可能会转而把 CPU 时间片分配给其他线程,这时也可以称为 “暂停”状态。Blocked 状态结束之后,进入 Runnable 状态,等待系统重新分配资源。
出现阻塞状态的有如下几种情况:
1、线程调用 sleep 方法,主动放弃占用的处理器资源;
2、线程调用了阻塞式 IO 方法,在该方法返回之前,该线程被阻塞;
3、 线程试图获得一个同步监视器,但该同步监视器正在被其他线程所持有;
4、线程等待某个通知;
5、程序调用了 suspend 方法将该线程挂起。
◾ run 方法运行结束后进入销毁阶段,整个线程执行完毕。
生产者/消费者模式实现
一个生产者,一个消费者
存储值对象:
生产者:
消费者:
生产者线程:
消费者线程:
主函数:
题目:创建20个线程,其中10个线程是将数据备份到数据库A,另外10个线程将数据备份到数据库B中去,并且备份数据库A和备份数据库B是交叉进行的。
工具类:
备份A先线程:
备份B线程:
测试:
Join 方法的使用
作用:等待线程对象销毁
join 方法具有使线程排队运行的作用,有些类似同步的运行效果。join 与 synchronized 的区别是:join 在内部使用 wait() 方法进行等待,而 synchronized 关键字使用的是 “对象监视器” 原理做为同步。
在 join 过程中,如果当前线程对象被中断,则当前线程出现异常。
方法 join(long) 中的参数是设定等待的时间。
类 ThreadLocal 的使用
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
get() 方法
返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值。
InheritableThreadLocal 类的使用
该类扩展了 ThreadLocal,为子线程提供从父线程那里继承的值:在创建子线程时,子线程会接收所有可继承的线程局部变量的初始值,以获得父线程所具有的值。通常,子线程的值与父线程的值是一致的;但是,通过重写这个类中的 childValue 方法,子线程的值可以作为父线程值的一个任意函数。
当必须将变量(如用户 ID 和 事务 ID)中维护的每线程属性(per-thread-attribute)自动传送给创建的所有子线程时,应尽可能地采用可继承的线程局部变量,而不是采用普通的线程局部变量。
第四章
Lock 的使用
使用 ReentrantLock 类
一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。可以使用
isHeldByCurrentThread()和 getHoldCount()方法来检查此情况是否发生。
此类的构造方法接受一个可选的公平 参数。当设置为 true 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。与采用默认设置(使用不公平锁)相比,使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁和保证锁分配的均衡性时差异较小。不过要注意的是,公平锁不能保证线程调度的公平性。因此,使用公平锁的众多线程中的一员可能获得多倍的成功机会,这种情况发生在其他活动线程没有被处理并且目前并未持有锁时。还要注意的是,未定时的 tryLock方法并没有使用公平设置。因为即使其他线程正在等待,只要该锁是可用的,此方法就可以获得成功。
建议总是 立即实践,使用 lock 块来调用 try,在之前/之后的构造中,最典型的代码如下:
Condition
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。
正确使用 Condition 实现等待/通知
MyService.java
ThreadA.java
Test.java
运行结果:
Object 类中的 wait() 方法相当于 Condition 类中 await() 方法
Object 类中的 wait(long time) 方法相当于 Condition 类中 await(long time, TimeUnit unit) 方法
Object 类中的 notify() 方法相当于 Condition 类中 signal() 方法
Object 类中的 notifyAll() 方法相当于 Condition 类中 signalAll() 方法
后续见《Java 多线程编程核心技术》学习笔记及总结(下)
Java我最强
关心Java人成长的技术内容社区
快速关注
以上是关于经典干货《Java 多线程编程核心技术》学习笔记及总结(中)的主要内容,如果未能解决你的问题,请参考以下文章