Java学习笔记: 线程
目录:
1. 线程的建立
2. 线程的生命周期与控制
3. 线程的同步与通信
4. 未处理的异常
5. 线程安全的集合
6. 同步器
7. 线程池
主要参考: Thinking in Java, Core Java, 疯狂的Java讲义, Java doc, 源码.
Tips: Thinking in Java和Core Java都推荐了Java Concurrency in Practice, 相信是一本关于线程以及Concurrency的不错的进阶数目.
1. 线程的建立
想让线程执行一段代码, 有两种方法将需要执行的代码绑定到线程中:
(1) 继承Thread类, 重写run()方法;
(2) 实现一个runnable对象, 调用Thread类的相关构造函数, 如Thread(Runnable).
疯狂的Java讲义中提到, 这两种方式的区别在于: 第一种方法中, run()方法中所有的局部变量是独立的, 而第二种方法中Runnable r里面的局部变量是共享的. 但我觉得这个说法不够全面, 例如我用第二种方法创建线程的时候采用new Thread(new Runnable{...})的话, runnable里面的局部变量也是独立的. 共享局部变量的本质在于多个Thread实例的Runnbale target成员是否指向同一个对象. 采用第二种方法并不一定都会共享局部变量.
另一点区别是, 在Thread.run()里面可以直接用this引用线程实例, 而在Runnable.run()里则需要使用Thread.currentThread()引用线程实例.
Thread.run()方法也好, Runnable.run()也好, 并没有返回值. 如果需要线程有返回值, 可以采用FutureTask(Android里大量使用的AsyncTask类中就是使用这个方法创立线程), 创建的具体流程如下:
首先创立一个Callable对象 -> 用Callable对象创立一个FutureTask对象 -> 因为FutureTask对象实现了Runnable接口, 所以可以用Thread(Runnable)方法建立线程. 线程启动后, 就可以调用FutureTask中Future接口里的方法获得线程的执行状态或者返回值.
注: 已经结束的线程不能再次调用start(), 调用过run()方法的线程不能调用start()方法(其实包含了第一句话), 会抛出异常.
2. 线程的生命周期与控制
2.1 线程的生命周期
源码和Core Java中Thread类是一致的, 六种状态: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED.
疯狂的Java讲义里面将RUNNABLE状态细分为"就绪"和"运行"两种, 以CPU是否实际执行线程作为区分点. 但是没有对BLOCKED, WAITING和TIMED_WAITING做区分, 统一称为阻塞.
简单流程如下:
(1) new方法执行后, 线程进入NEW状态;
(2) 调用start()后, 线程进入RUNNABLE状态;
(3) 如果试图获得一个内部对象锁(synchronized关键字所用的锁), 而该锁却被别的对象占有, 线程进入BLOCKED状态;
(4) 如果调用Object.wait, Thread.join或者等待java.util.concurrent中Lock或Condition时(Lock.tryLock或Condition.await), 线程进入WAITING状态;
(5) (4)中方法如果带有超时参数, 以及Thread.sleep方法, 线程会进入TIMED_WAITING状态;
(6) run方法执行结束正常退出, 或因为一个没有被捕获的异常(Runnable.run不能抛出异常, Callable.call可以), 或者调用stop方法(慎用), 线程进入TERMINATED状态.
注: Thread.yield()只会让Thread让出CPU, 但不会改变线程RUNNABLE的状态.
2.2 线程的控制
join(), sleep(), yield(), setDaemon(), setPriority()等方法.
注: setDaemon必须在start()前.
注: 用lock等待获得内部锁的过程中, lock方法是不能响应中断的, 但是可以可以用lockInterruptibly()方法解决, 但是synchronized则不可以中断.
3. 线程的同步与通信
3.1 线程的同步
总的来说有两类:
(1) 第一类使用synchronized关键字同步代码块或方法, 它带有一个隐式的condition, 可用wait(), signal()和signalAll()方法.
(2) 第二类使用java.util.concurrent package中的Lock, 结合Condition实现.
3.1.1 synchronized
同步代码块:
synchronized(obj) { };
相当于
obj.intrinsicLock.lock();
try {
...
} finally {
obj.intrinsicLock.unlock()
}
Object内置wait, notify和notifyAll()方法. 三个方法均是native方法. 调用后将调用intrinsicCondition的await, signal和signalAll方法.
同步方法:
public synchronized void method() { };
相当于
public void method() {
this.intrinsicLock.lock();
try {
...
} finally {
this.intrinsicLock.unlock();
}
}
可以使用wait, notify和notifyAll.
注: synchronized可以修饰static方法, 用其Class对象的内部锁. 但是不可以修饰构造器.
3.1.2 Lock
Lock和Condition(Lock.newCondition)可以起到河synchronized相同的作用.主要函数有:
Lock.lock(), Lock.unlock()以及Condition.await, Condition.signal, Condition.signalAll方法.
Lock中可以生成多个Condition, 而synchronized中只有一个, 所以Lock要更加灵活. 另外Lock还有tryLock方法和lockInterruptibly方法.
注: 如果处于阻塞或者等待状态, 线程被interrupt, 那么会抛出异常. 除非是调用了Condition.awaitUninterruptibly方或是不带超时参数的lock().
3.1.3 ReadWriteLock
读锁: ReadWriteLock.readLock();
写锁: ReadWriteLock.writeLock();
可以有多个读同时发生, 但是写和读以及写和写不能同时发生.
3.1.4 StampedLock
StampedLock是ReadWrite的一种改进版本, 有Reading, Writing和Optimistic Reading三种模式.
Reading和Writing模式对应ReadWriteLock的读锁和写锁, 使用语法稍有不同.
StampedLock.readLock()和StampedLock.writeLock()返回的是一个Stamp(long类型), 在StampedLock的unlock, unlockRead和unlockWrite中使用.
对于Optimistic Reading, 使用StampedLock.tryOptimisticRead. 判断读取后是否还有效(中间可能插入别的写操作)可以用StampedLock.validate(long)方法.
此外, StampedLock读写锁还可以转换, 具体可以查阅Java doc.
3.2 BlockingQueue
除了使用synchronized和Lock进行线程的同步与通信外, 还可以使用BlockingQueue. 按照队尾插入, 队头获取, 取出后是否删除, 将方法分三类:
队尾插入:
(1) add: 向队尾添加一个元素, 如果队列满则抛出IllegalStateException异常. 这个方法是没有加锁的.
(2) offer: 使用lock, 尝试向队尾添加一个元素, 返回Boolean标志成功和失败.
(3) 有延时参数的offer: 使用Lock.lockInterruptibly和有延时参数的Condition.awaitNanos. 返回Boolean标志成功和失败.
(4) put: 使用lockInterruptibly和不带延时参数的Condition.await.
队头获取并删除:
(1) remove(Object): 使用lock(), 和add还是不太一样, 是否抛出异常也是optional的, 而且也不是从队头删除, 而是从队头开始找第一个equeals参数的, 返回Boolean.
(2) poll(): 使用lock(), 若空返回null.
(3) 有延时参数的poll(): lockInterruptibly加带参数的waitNanos, 若等待结束还为空则返回null.
(4) take(): 相当于延时参数无限的poll, 无返回值.
队头获取:
(1) peek(): 使用lock().
(2) element(): 已删除.
BlockingQueue接口的实现类:
(1) ArrayBlockingQueue
(2) LinkedBlockingQueue
(3) PriorityBlockingQueue: 不是标准的BlockingQueue, 取最小元素.
(4) SynchronousQueue: 存取必须交替进行.
(5) DelayQueue: 基于PriorityBlockingQueue, 使用Delay接口的getDelay方法进行比较.
4. 未处理的异常
略
5. 线程安全的集合
以Concurrent开头的集合类, 以及CopyOnWrite开头的集合类, 或者使用Collection的静态方法进行包装.
6. 同步器
6.1 CyclicBarrier
先建立一个CyclicBarrier, 然后调用Barrier.await方法实现多个线程的并行运行. CyclicBarrier的构造器中还可以传入一个Runnable, 它会在最后一个Thread到达时触发.
6.2 DelayQueue
略
6.3 CountDownLatch
调用await将阻塞, 直到调用CountDownLatch.countDown.
6.4 Semaphore
控制访问数量的锁, 用Semaphore.acquire和Semaphore.release控制获取和释放锁.
6.5 Exchanger
交换两个线程中一对Object(用模板参数传入), 使用Exchanger.exchange.
7. 线程池
(1) 提交任务:
用Executors类的静态方法生成ExecutorService或ScheduleExecutorService接口的具体类. 然后调用submit或sechdule等执行.
(2) 控制任务组:
可以使用shutdown和shutdownNow停止任务.
可以使用invokeAny或invokeAll提交一个Collection的Callable<T>(invokeAny不是随机提交一个任务, 而是提交所有任务). invokeAny将随机返回一个任务的结果, 而invokeAll将返回一个List<Future<T>>. 可以用ExecutorCompletiomService包装一个ExecutorService, 这样可以将任务按是否完成排序. 可以调用take, poll等方法获取完成任务的返回值.
(3) ForkJoinPool(RecursiveTask<T>)
RecursiveTask<T>可以把任务划分再把返回值合并起来, 使用其fork和join的方法. 使用ForkJoinPool.commonPool方法, 然后调用submit, execute, invoke等方法执行.
后记: 试着写了一篇当作复习, 发现越写越少. 一些已经懂的东西写起来没有兴致, 也没有从零开始全面讲解用法. 就当作一个纲提醒自己有哪些知识点吧, 以后遇到具体的需要细致分析的问题可以尝试详细写点.
- 学习java多线程的笔记3-使用BlockingQueue阻塞队列来模拟两个线程之间的通信
- 学习笔记 java多线程(四)线程间协作
- Java学习笔记之线程间的通信
- Java线程学习笔记之并发集合类
- Java学习笔记----------------守护线程
- Java多线程学习笔记——从Java JVM对多线程数据同步的一些理解
- Android(java)学习笔记63:线程的调度
- java学习笔记1018---线程的控制
- java学习笔记之线程并发库
- 黑马程序员 Java基础学习笔记 线程间同信
- Java 当中的线程(一)-- MarsChen Java4Android 教程学习笔记
- Java多线程学习笔记——从Java JVM对多线程数据同步的一些理解
- JAVA学习笔记(三十九)- 线程优先级
- Android(java)学习笔记72:线程的状态转换图以及常见执行情况
- java线程学习笔记
- java基础学习笔记之九--线程(1)
- Java学习笔记之线程的生命周期
- Java多线程编程学习笔记之一:线程中断(含代码)
- 21天学通Java学习笔记-Day07(异常-断言-线程)
- Java并发学习笔记(4)线程的取消,关闭和异常终止