版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
《疯狂的JAVA讲义》笔记-第16章多线程
- 三种方法实现多线程
实现方法 | 特点 | 例子 |
---|---|---|
继承 Thread |
无法共享变量,以run 为线程执行体 |
new Thread().start |
实现 Runnable 接口,并作为 Thread 的target 运行 |
共享实现类变量,以run 为线程执行体 |
new Thread(new MyRun()).start |
实现Callable 接口,并使用FutureTask 包装,把包装对象作为Thread 的target运行 |
可以抛异常且有返回值(task.get() ),共享变量,以call 为线程执行体 |
task = new FutureTask(new MyCall()); Thread thread = new Thread(task); |
-
sleep
、yield
以及join
sleep
:让线程睡眠一段时间,处于 阻塞状态。join
:让线程等待另一个线程执行结束,可用于流程性的控制yield
: 线程让步,不阻塞线程,只是将线程设为 就绪状态,将CPU让给优先级更高的线程。可能会出现没有其他优先级更高的线程,从而刚成为就绪状态后又被操作系统调度成为 运行 状态的线程
-
后台线程/守护线程
- 为其他线程服务的,一直在后台运行的线程,可以通过
thread.setDaemon(true)
设置,但是须在运行前进行设置,且中途不能修改。当所有的前台程序结束,后台线程自动结束。例子:JVM的GC
- 为其他线程服务的,一直在后台运行的线程,可以通过
-
线程同步
- 同步代码块:同步监视器(互斥量)为
obj
,只有获取到该监视器才可以进入代码块
synchronized(obj){ .... }
- 同步方法:同步监视器为
this
,只有获取到account
实例才可以进入代码块
//Account.java public synchronized int method(){ .... }
- 灵活的加锁同步Lock :较为常用的是可重入锁
ReetrantLock
,synchronized
也是可重入锁
private final lock = new ReentrantLock(); public int method(){ lock.lock(); try{ ... }finally{ lock.unlock(); } }
- 同步代码块:同步监视器(互斥量)为
-
可重入锁与不可重入锁
- 可重入锁:当一个进程获取到 A 锁,如果在递归或者其他需要 A 锁的地方时,无需再次获取 A 锁。
- 不可重入锁:需要再次获取 A 锁,容易导致死锁
public synchronized int method1(){ method2(); } public synchronized int method2(){ ..... } //这种情况下 可重入不会死锁,但 不可重入则会死锁
-
锁的释放
- 释放锁的情景
- 方法执行结束
- 同步代码块或同步方法执行了 break 或 return 语句
- 出现异常,方法结束
- 程序执行了
wait()
- 不会释放锁的情景
- 调用
sleep()
或yield()
方法 - 其他线程调用其他线程的
suspend()
方法
-
进程通信:当使用 同步方法时,同步监视器是
this
,即对象本身,可以使用Object
类的wait``notify``notifyAll
方法进行通信wait()
:让当前进程暂停,直到其他线程调用 该同步监视器 的notify
或notifyAll
方法才被唤醒,也可以 等指定秒数后自行唤醒notify()
:唤醒 在同一个同步监视器上等待的单个线程。如果有多个,则选择是任意性的notifyAll()
:唤醒所有在同一个同步监视器上等待的线程
如果不使用 同步方法,则不存在
wait()
等方法,可以使用Condition
来进行类似的操作await()
:跟wait()
相似,让当前线程暂停,并释放已经获得的Lock
对象signal()/signalAll()
:与notify()/notifyAll()
一样
-
使用 阻塞队列 控制通信:消费者/生产者模型
- 使用
take()
put()
可以使进程阻塞
- 使用
-
线程池
- 由于频繁创建销毁非常影响性能,所以在
JAVA 5
后有了线程池,可使用工厂类Executors
的以下方法产生线程池 newCachedThreadPool()
:根据需要创建线程,并将线程缓存在线程池里newFixedThreadPool()
:创建固定长度,可复用的线程池newSingleThreadPool()
: 创建只有一个线程的线程池newScheduledThreadPool(int corePoolSize)
:创建指定线程线程数的线程池,可以在指定延迟后执行线程任务
以上方法返回一个
ExecutorService
对象,有以下方法Future<?> submit(Runnable task)
:由于run
方法没有返回值,所以返回值为null
,可以调用Future
对象的isDone
/isCancelled
方法获取执行状态<T> Future<T> submit(Runnable task,T result)
:线程池在空闲时会执行 task 任务,在结束后返回 result<T> Future<T> submit(Callable<T> task)
:Future
对象是call()
方法的返回值
当不想提交任务后,请使用
shutdown()
方法关闭线程池,但是线程池中线程是在执行完当前任务后才会死亡,如果需要立即死亡,使用shutdownNow()
方法,可以视图停止所有正在执行的任务 - 由于频繁创建销毁非常影响性能,所以在
-
ForkJoinPool
- 单核上多线程只能算 并发,但是多核上就可以实现 并行(parallelism),
ForkJoinPool
线程就是实现这个的,他是ExecutorService
的实现类 ForkJoinPool()
:根据Runtime.availableProcessors()
方法指定 并行线程数,当然也可以传入int parallelism
指定并行线程数- 没有返回值的任务需要实现
RecursiveAction
,有返回值的任务则实现RecursiveTask<T>
,最后使用submit
方法提交即可
- 单核上多线程只能算 并发,但是多核上就可以实现 并行(parallelism),
-
TreadLocal
类可以实现每个线程都使用自己的变量副本,即每个线程的该变量都是独立的,不会发生死锁、等待锁的情况。使用方法private ThreadLocal<String> name = new ThreadLocal<>();