avatar

目录
JAVA核心技术卷I(并发)

JAVA核心技术卷I(并发)

一、创建线程

  • Thread(Runable target)

  • void start():启动这个线程从而调用run()方法

  • void run()

二、线程状态

  1. 获取线程状态

    Thread.State getState()

    取值:NEW、RUNNING、BLOCKED、WAITING、TIMED_WAITING、TERMINATED

  2. 新建线程

    new Thread(r)

  3. 可运行线程

    可能正在运行也可能没有运行。

  4. 阻塞和等待线程

    • 阻塞

    • 等待

      Thread.join()

    • 计时等待

      void join(long millis)

  5. 终止线程

    • run方法正常退出
    • 没有捕获异常终止了run方法

三、线程属性

3.1 中断线程

java.lang.Thread

  • void interrupt()

    向线程发送中断请求。线程的中断状态将被设置成true。如果当前该线程被一个sleep调用或阻塞,则抛出一个InterruptedException异常

  • static boolean interrupted()

    测试当前线程(即正在执行这个指令的线程)是否被中断。这是一个静态方法。这个调用有一个副作用——它将当前线程的中断状态重置为false

  • boolean isInterrupted()

    测试线程是否被中断。和interrupt不同,不会改变线程的中断状态

  • static Thread currentThread()

    返回当前线程对象

3.2 守护线程

守护线程的唯一用途就是为其他线程提供服务。当只剩下守护线程的时候,虚拟机就会退出。因为如果只有守护线程,就没有必要继续运行程序了。

  • void setDaemon(boolean isDaemen)

    标识该线程为守护线程或用户线程。方法必须在线程启动前调用。

3.3 线程名

java
var t = new Thread(runnable);
t.setName("Web crawler");

这在线程转储时可能很有用。

四、同步

4.1 竞态条件

当我们运行如:

Code
a[i] += x;

的时候的会转为一系类的字节码:

Code
aload_0
getfield
iload_2
...

也就是说,一个语句要执行N个指令。如果在这些指令没有全部执行完之前,被中断。其他指令运行完再回头继续执行这个剩余指令,那么就有可能数据出问题。(即没有原子性)

4.2 锁对象 和 条件对象

P568

  • 锁用来保护代码片段,一次只能由一个线程执行被保护的代码
  • 锁可以管理试图进入被保护代码段的线程

在锁中如果需要条件判断用条件对象。

  • 一个锁可以有一个或多个相关联的条件对象
  • 每个条件对象管理那些已经进入被保护代码段但还不能运行的线程

可以用 synchronized 关键字代替。

4.3 synchronized 关键字

每个对象都有一个内部锁,并且这个锁有一个内部条件。这个锁会管理试图进入synchronized方法的线程,这个条件可以管理调用了wait的线程。

notifyAll => signalAll

notify => signal

wait => await

何时使用

  • 最好既不用Lock/Condition 也不用 synchronized 关键字。再许多情况下,可以使用java.util.concurrent包中的某种机制,它会为你处理所有的锁定。
  • 如果synchronized关键字适合你的程序,那么尽可能用这个,减少代码量。
  • 如果特别需要 Lock/Condition 结构提供的格外能力,则使用Lock/Condition 。

4.4 同步块

java
synchronized(obj) {
// ...
}

4.5 监视器概念

  • 监视器是只包含私有字段的类
  • 监视器类的每个对象有一个关联的锁
  • 所有方法由这个锁锁定
  • 锁可以有任意多个相关联的条件

4.6 volatile 和 final

P581-582

假设对共享变量除了赋值之外并不做其他操作,那么可以将这些共享变量声明为volatile

五、线程安全的集合

六、任务和线程池

6.1 Callable 与 Future

Callable< T> 是一个接口,只有一个call方法。返回类型是T。

Future保存异步计算的结果。

  • V get()

  • V get(long time, TimeUnit unit)

    获取结果

  • boolean cancel(boolean mayInterrupt)

    尝试取消这个任务的运行

  • boolean isCancelled()

  • boolean isDone()

示例

java
Callable<Integer> task = ...;
var futureTask = new FutureTask<Integer>(task);
var t = new Thread(futureTask);
t.start();
...
Integer result = task.get();

6.2 执行器

执行器类(Executors)有许多静态工厂方法,用来构造线程池。

方法 描述
newCachedThreadPool 必要时创建新线程,空线程会保留60秒
newFixedThreadPool 池中包含固定数目的线程;空闲线程会一直保留
newWorkStealingPool 一种适合“fork-join”任务的线程池,其中复杂的任务会分解为简单的任务,空闲线程会“密取”较简单的任务
newSingleThreadPool 只有一个线程的“池”,会顺序执行所提交的任务
newScheduledTahreadPool 用于调度执行的固定线程
newSingleThreadScheuledExcutor 用于调度执行的单线程“池”

如果线程生存期很短,或者大量时间都在阻塞,那么可以用一个缓存线程池。不过,如果线程工作量很大而且不阻塞,你肯定不希望运行太多线程。

为了得到最优的运行速度,并发线程数等于处理器内核数。在这种情况下,就应当使用固定线程池,即并发线程总数有一个上限。

单线程执行器对于性能分析很有帮助。如果临时用一个单线程替换缓存或者固定线程池,就能测量不适用并发的情况下应用的运行速度会慢多少。

提交任务

  • Future< T> submit(Callable< T> task)
  • Future< ?> submit(Runnable task)
  • Future< T> submit(Runnable task, T result)

关闭线程池

  • shutdown:关闭线程池
  • shutdownNow:线程池会取消所有尚未开始的任务

使用连接池的工作

  1. 调用Executor类的静态方法newCachedThreadPool或newFixedThreadPool
  2. 调用submit提交Runnable或Callable对象
  3. 保存好返回的Future对象,以便得到结果或取消任务
  4. 当不想提交任何任务时,调用shutdown
文章作者: IT小王
文章链接: https://wangbowen.cn/2020/05/21/JAVA%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF%E5%8D%B7I%EF%BC%88%E5%B9%B6%E5%8F%91%EF%BC%89/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 IT小王

评论