
第1章 多线程
1.1 什么是线程
java.lang.Thread 方法:
Thread(Runnable target) 构造一个新的线程来调用指定target的run()方法;
void start() 启动一个新线程,将引发调用run()方法;
void run() 调用关联Runnable的run方法;
java.lang.Runnable 方法:
void run() 重载方法并且要在方法中提供要执行任务的相关的处理代码;
1.2 中断线程
布尔类型的标志,中断状态(interrupted status)。
java.lang.Thread 方法:
void interrupt() 发送一个中断请求给一个线程,这个线程的中断状态被设为True
interrupted()方法和isInterrupted方法
static boolean interrupted()方法是一个静态方法,可以检查当前线程是否已被中断,并且会清除该线程的中断状态;
boolean isInterrupted()方法是一个实例方法,可以检查是否有线程已被中断,调用该方法不会改变中断状态;
1.3 线程状态
新生(New)——>可运行(Runnable)——>[ 被阻塞(Blocked)]——>死亡(Dead)
1、新生线程
可以用new 关键字操作符创建一个线程,如new Thread(t); 此时线程处于新生状态,程序还没有开始运行线程中的代码
2、可运行线程
调用start()方法,线程就进入可运行状态。任何给定的时刻,一个可运行线程可能正在运行,也可能被中断
3、被阻塞线程
当发生以下任何一种情况时,线程就进入被阻塞状态:
(1)线程通过调用sleep()方法进入睡眠状态
(2)线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者
(3)线程视图得到一个锁,而该锁正被其他线程持有
(4)线程在等待某个触发条件
(5)调用了线程的suspend()方法
线程从被阻塞状态到可运行状态的途径:
(1)线程被置于睡眠状态,且已经经过指定的毫秒数
(2)线程正在等待I/O操作的完成,且该操作已经完成
(3)线程正在等待另一个线程所持有的锁,且另一个线程已经释放该锁的所有权
(4)线程正在等待某个触发条件,且另一个线程发出了信号表明条件已经发生了变化
(5)线程已被挂起,且调用了它的resume()方法
一个被阻塞的进程只能通过和先前阻塞它的相同过程重新进入可运行状态。
4、死亡线程
导致线程死亡的原因:
(1)因为run()方法正常退出而自然死亡
(2)因为一个未捕获的异常终止了run()方法而使线程猝死
1.4 线程属性
线程优先级:
默认情况下,一个线程继承它父线程(即启动该线程的线程)的优先级。
守护线程:
守护线程的作用就是为其他线程提供服务。
线程组
同时可以对一组线程进行操作。默认情况下,一个新的线程组是当前线程组的子线程组。
1.5 同步
对存取进行同步 解决 竞争条件(race condition)
代码块对象保护机制:synchronized和ReentrantLock
一个进程锁住了锁对象,其他任何线程调用时都会被阻塞,直到第一个线程释放锁对象。
锁是可以重入的,线程可以重复的获取已经拥有的锁。锁对象维护一个持有计数(hold count)来追踪对lock方法的嵌套调用。线程在每次调用lock()方法后都要调用unlock()来释放锁。
条件对象(condition object)管理那些已获得了锁却不能开始执行有用的工作的线程。
一个锁对象可以有一个或多个相关联的条件对象。
线程通过两种方法获得锁,调用一个同步方法 或 进入一个同步块。
在以下条件下,对一个域的并行访问是安全的:
(1)域是volatile的 (2)域是final的,并且在构造器调用完以后访问 (3)对域的访问有锁保护
死锁,每一个线程都在等待,而导致所有线程都被阻塞。
公平锁策略(fair locking policy)会优待那些等待了最长时间的线程。
读/写锁的必要步骤:
(1)创建一个ReentrantReadWriteLock对象
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
(2)抽取读锁和写锁
private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();
(3)对所有访问者加读锁
public double getTotalBalance(){
readLock.lock();
try{ ...... }
finally{ readLock.unlock(); }
}
(4)对所有修改者加写锁
public void transfer( ...... ){
writeLock.lock();
try{ ...... }
finally{ writeLock.unlock(); }
}
1.6 阻塞队列
队列以先进先出的方式管理数据。使用阻塞队列来控制线程集。属于线程安全的集合
1.7 线程安全的集合
并发散列映射表能够有效地支持多个读取器操作和固定数量的写入器操作。
java.util.concurrent.ConcurrentLinkedQueue<E>
构建一个可以被多个线程安全访问的无边界的非阻塞队列
java.util.concurrent.ConcurrentHashMap<K,V>
构建一个可以被多个线程安全访问的散列映射表
1.8 Callable and Future
Runnable 封装一个异步运行的任务,没有任何参数和返回值
java.util.concurrent.Callable<V> 封装一个有返回值的异步计算,类型参数是返回值的类型;只有一个 V call()方法
java.util.concurrent.Future<V> 保存异步计算的结果
1.9 执行器
执行器(java.util.concurrent.Executors)类含有的静态方法
ExecutorService newCachedThreadPool() 方法构建了一个线程池,对于每个任务,如果有空闲线程可用,立即让它执行任务,否则在需要时创建新线程,空闲线程会保留60秒
ExecutorService newFixedThreadPool(int threads) 方法创建一个大小固定的线程池,空闲线程会一直被保留,如果提交的线程数大于空闲线程,那么得不到服务的任务将被置于队列中,当其他任务完成后置于队列的任务就能运行
ExecutorService newSingleThreadPool() 是一个大小为1的线程池,由一个线程顺序执行每一个递交上来的任务
在使用连接池时应该做的事:
1、调用Executors类中静态的newCachedThreadPool或newFixedThreadPool方法
2、调用submit来提交一个Runnable或Callable对象
3、如果希望能够取消任务或如果提交了一个Callable对象,那么就保存好返回的Future对象
4、当不再提交任何任务时调用shutdown
ScheduledExecutorService newScheduledThreadPool(int threads) 为预定执行而构建的固定线程池,返回一个线程池,它使用给定数目的线程来调度任务
ScheduledExecutorService newSingleThreadScheduledExecutor() 为预定执行而构建的单线程池,返回一个执行器,它在一个单独线程中调度任务
1.10 同步器(Synchronizer)
障栅:java.util.concurrent.CyclicBarrier 允许一个线程集等待直至其中预定数量的线程达到一个公共障栅为止,然后可以选择执行一个处理障栅的撤销动作,线程就可以继续运行。障栅是循环的,它能在所有等待线程被释放后被重用。何时使用:当大量的线程需要在他们的结果可用之前完成时使用。
倒计时门栓:java.util.concurrent.CountDownLatch 允许一个线程集等待直至计数器减为0为止。何时使用:当一个或多个线程需要等待直至制定数量的结果可用为止时使用。
一个有用的特例是一个计算器值为1的门栓,实现了一个一次性的门。线程在门外等候直到另一个线程把计数器的值设为0
CountDownLatch与CyclicBarrier的不同点:
1、不是所有的线程都需要等待到门栓打开为止
2、门栓可以由外部事件打开
3、倒计时门栓是一次性的。一旦计数器到达0,就不能再重用。
交换器:java.util.concurrent.Exchanger<V> 允许两个线程在要交换的对象准备好时交换对象。何时使用:当两个线程在同一个数据结构的两个实例上时,一个向实例中添加数据,另一个将数据从实例中清除。
同步队列:java.util.concurrent.SynchronousQueue<V> 允许一个线程将对象交给另一个线程,只在一个方向上传递数据,当一个线程调用put方法时,它会阻塞直到另一个线程调用task方法为止。何时使用:在没有显式同步的情况下,当两个线程准备好将一个对象从一个线程传给另一个线程时使用。
信号量:java.util.concurrent.Semaphore 允许线程集等待直至被允许继续运行为止。何时使用:用来限制访问资源的线程总数。如果许可是1,则阻塞线程直至另一个线程给出许可为止。
1.11 线程和Swing
规则:
1、如果一个动作占用的时间很长,就启动一个新的线程来执行它。因为如果事件分派线程占用了大量时间,那么它没有办法响应任何事件了
2、如果一个动作在输入和输出上阻塞了,就启动一个新的线程来处理输入输出。
3、如果需要等待指定的时间,不要让事件分派线程睡眠,而应该使用定时器事件。
4、Swing程序的单一线程规则:在线程中做的事不能接触用户界面。在启动线程前,应该先阅读来自用户界面的信息,然后再启动线程,一旦这些线程启动完成,就从事件分派线程中更新用户界面。
Tags: java 核心技术卷 笔记
原创文章如转载,请注明:转载自:飞扬部落编程仓库 : http://www.busfly.net/csdn/
本文链接地址:http://www.busfly.net/csdn/post/495.html
如果你喜欢本文,请顶一下,支持我,你的支持是我继续发好文章的最大动力。谢谢。
好东西需要分享,快把本文发给你的朋友吧~!~