《疯狂的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以及joinsleep:让线程睡眠一段时间,处于 阻塞状态。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<>();

本文深入探讨Java中实现多线程的三种方法:继承Thread类、实现Runnable接口、实现Callable接口;并详细解析sleep、yield、join等线程控制方法,以及线程同步机制、锁的使用、线程池的应用和ForkJoinPool并行处理技巧。

被折叠的 条评论
为什么被折叠?



