多线程并发
2018-10-13 16:20:38 136 举报
AI智能生成
多线程是一种程序执行方式。它允许单个程序的不同部分同时运行,每个部分被称为一个“线程”。线程可以并行处理任务,从而提高程序的执行效率和响应速度。例如,当一个程序需要同时进行网络请求和数据处理时,可以创建两个线程,一个用于处理网络请求,另一个用于数据处理。这样,这两个任务就可以同时进行,而不是等待一个任务完成后再开始另一个任务。多线程在许多领域都有广泛应用,如操作系统、图形用户界面、游戏开发等。然而,多线程也带来了一些复杂性,如线程同步和数据竞态等问题。因此,有效地使用和管理多线程是程序员需要掌握的一项重要技能。
作者其他创作
大纲/内容
高级部分
CompletionService
作用
是以 异步 的 方式 一边 生产 新的 任务, 一边 处理 已完成 任务 的 结果, 这样 可以 将 执行任务 与 处理 任务 分离 开来 进行 处理。 使用 submit 执行任务, 使用 take 取得 已完成 的 任务, 并按 照 完成 这些 任务 的 时间 顺序 处理 它们 的 结果。
实现类
ExecutorCompletionService
方法
方法 take() 取得 最先 完成任务 的 Future 对象, 谁 执行 时间 最短 谁 最先 返回。
方法 poll() 的 作用 是 获取 并 移 除 表示 下一个 已完成 任务 的 Future, 如果不 存在 这样 的 任务, 则 返回 null, 方法 poll() 无 阻塞 的 效果。
方法 Future< V> poll( long timeout, TimeUnit unit) 的 作用 是 等待 指定 的 timeout 时间, 在 timeout 时间 之内 获取 到 值 时 立即 向下 继续 执行, 如果 超时 也 立即 向下 执行。
总结
接口 CompletionService 完全 可以避免 FutureTask 类 阻塞 的 缺点, 可 更加 有效地 处理 Future 的 返回 值, 也就是 哪个 任务 先 执行 完, CompletionService 就 先取 得 这个 任务 的 返回 值 再处理。
CountDownLatch&CyclicBarrier
CountDownLatch
作用
门闩, 也就 是有“ 门锁” 的 功能, 所 以当 门 没有 打开 时, N 个人 是 不能 进入 屋内 的, 也就是 N 个 线程 是 不能 继续 向下 运行 的, 支持 这样 的 特性 可以 控制 线程 执行任务 的 时机, 使 线程 以“ 组团” 的 方式 一起 执行任务。 类 CountDownLatch 所 提供 的 功能 是 判断 count 计数 不为 0 时 则 当前 线程 呈 wait 状态, 也就 是在 屏障 处 等待
基本用法
定 一个 计数, 当 使用 这个 CountDownLatch 类 的 线程 判断 计数 不为 0 时, 则 呈 wait 状态, 如果 为 0 时 则 继续 运行。 实现 等待 与 继续 运行 的 效果 分别 需要 使用 await() 和 countDown() 方法 来 进行。 调用 await() 方法 时 判断 计数 是否 为 0, 如果 不为 0 则 呈 等待 状态。 其他 线程 可以 调用 count- Down() 方法 将 计数 减 1, 当 计数 减到 为 0 时, 呈 等待 的 线程 继续 运行。 而 方法 getCount() 就是 获得 当前 的 计数 个数。
代码 new CountDownLatch( 1) 的 作用 是 创建 1 个 计数 的 CountDownLatch 类 的 对象, 当 线程 执行 down. await() 代码 时 呈 等待 状态, 程序 不向 下 继续 运行。 程序 执行 down. countDown() 代码 时 计数 由 1 变成 0, 以前 呈 等待 状态 的 线程 继续 向下 运行。
其他概念
方法 await( long timeout, TimeUnit unit) 的 作用 使 线程 在 指定 的 最大 时间 单位 内 进入 WAITING 状态, 如果 超过 这个 时间 则 自动 唤醒, 程序 继续 向下 运行。 参数 timeout 是 等待 的 时间, 而 unit 参数 是 时间 的 单位。
方法 getCount() 获取 当前 计数 的 值。
我的理解
门上一共有10把锁,10个人有10把钥匙,当锁全部打开,10个人才可以进入下一步操作
调用countdownlatch.awati的线程处于阻塞状态,当countdownlatch调用countdownf方法将count计数减为0的时候,await的线程从阻塞状态变为可运行状态
CyclicBarrier
作用
类 CyclicBarrier 不 仅有 CountDownLatch 所 具有 的 功能, 还可以 实现 屏障 等待 的 功能, 也就是 阶段性 同步, 它在 使 用上 的 意义 在于 可以 循环 地 实现 线程 要 一起 做 任务 的 目标, 而 不是 像 类 CountDownLatch 一样, 仅仅 支持 一次 线程 与 同步 点 阻塞 的 特性,
它 允许 一组 线程 互相 等待,直到 到达 某个 公共 屏障这些 线程 必须 实时 地 互相 等待, 这种 情况下 就可以 使用 CyclicBarrier 类 来 方便 地 实现 这样 的 功能。 CyclicBarrier 类 的 公共 屏障 点 可以 重用, 所以 类 的 名称 中有“ cyclic 循环” 的 单词。
CountDownLatch 类 的 使用 情况 是 两个 角色 之间 互相 等待, 而 Cyclic- Barrier 的 使用 情况 是 同类 互相 等待。 和 CountDownLatch 类 不同, 类 CyclicBarrier 的 计数 是 加法 操作。
类 CyclicBarrier 具有 屏障 重置 性,这句话的意思就是如果全部线程await的个数都到达了屏障数,那屏障就会被重置为0,然后在从0开始累加到屏障数
基本用法
一组线程中每个线程调用CyclicBarrier.awati()方法等待其他线程到达一个公共屏障点
new CyclicBarrier(5,Runnable),设置 最大 为 5 个 的 parties 同 行者, 也就是 5 个 线程 都 执行 了 cbRef 对象 的 await() 方法 后 程序 才可 以 继续 向下 运行, 否则 这些 线程 彼此 互相 等待, 一直 呈 阻塞 状态。
其他概念
getNumberWaiting() , 该 方法 的 作用 是 获得 有几个 线程 已经 到达 屏障 点。
方法 isBroken() 查询 此 屏障 是否 处于 损坏 状态。
方法 isBroken() 查询 此 屏障 是否 处于 损坏 状态。由于 中断 或者 超时 提前 离开 了 屏障 点, 其他 所有 在 屏障 点 等待 的 线程 也会 抛出 BrokenBarrierException 或者 InterruptedException 异常, 并且 离开 屏障 点。
方法 await( long timeout, TimeUnit unit) 的 功能 是 如果 在 指定 的 时间 内 达到 parties 的 数量, 则 程序 继续 向下 运行, 否则 如果 出现 超时, 则 抛出 TimeoutException 异常。
方法 getParties() 的 作用 是 取得 parties 个数。
reset() 的 作用 是 重置 屏障。
Semaphore&Exchanger
Semaphore
作用
控制线程并发的数量
主要用法
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + ",begin timer=" + System.currentTimeMillis());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ",end timer=" + System.currentTimeMillis());
semaphore.release();
其他概念
有参方法 acquire(int permits) 的 功能 是 每 调用 1 次 此 方法, 就使用 x 个 许可。说白了就是一共有10把锁,没进来一个人就消耗掉2把锁,所以一共只能进来5次
构造 参数 new Semaphore(5);中的 5 并不是 最终 的 许可 数量, 仅仅是初始的状态 值。比如初始是5,如果release(5),那avaliablePermits=10
方法 acquireUninterruptibly() 的 作用 是 使 等待 进入 acquire() 方法 的 线程, 不允许 被 中断。也就是正常执行不抛出异常
availablePermits() 返回 此 Semaphore 对象 中 当前 可用 的 许可 数, 此 方法 通常用于 调试, 因为 许可 的 数量 有可能 实时 在 改变, 并不是 固定 的 数量。
drainPermits() 可 获取 并 返回 立即 可用 的 所有 许可 个数, 并且 将 可用 许可 置 0。
方法 getQueueLength() 的 作用 是 取得 等待 许可 的 线程 个数。
方法 hasQueuedThreads() 的 作用 是 判断 有没有 线程 在等 待 这个 许可。
Semaphore semaphore = new Semaphore( 1, isFair);
获得 许可 的 顺序 与 线程 启动 的 顺序 有关, 这时 信号 量 就要 分为 公平 与 非 公平 的。 所谓 的 公平 信号 量 是 获得 锁 的 顺序 与 线程 启动 的 顺序 有关, 但不 代表 100% 地 获得 信号 量, 仅仅 是在 概率 上 能 得到 保证。 而非 公平 信号 量 就是 无关 的 了。
无 参 方法 tryAcquire() 的 作用 是 尝试 地 获得 1 个 许可, 如果 获取 不到 则 返回 false, 此 方法 通 常与 if 语句 结合 使用, 其 具有 无 阻塞 的 特点。 无 阻塞 的 特点 可以 使 线程 不至于 在 同步 处 一直 持续 等待 的 状态, 如果 if 语句 判断 不 成立 则 线程 会 继续走 else 语句,
有 参 方法 tryAcquire( int permits) 的 作用 是 尝试 地 获得 x 个 许可, 如果 获取 不到 则 返回 false。
有 参 方法 tryAcquire( int long timeout, TimeUnit unit) 的 作用 是在 指定 的 时间 内 尝试 地 获得 1 个 许可, 如果 获取 不到 则 返回 false。
有 参 方法 tryAcquire( int permits, long timeout, TimeUnit unit) 的 作用 是在 指定 的 时间 内 尝试 地 获得 x 个 许可, 如果 获取 不到 则 返回 false。
Exchanger
作用
类 Exchanger 中的 exchange() 方法 具有 阻塞 的 特色, 也就是 此 方法 被 调用 后 等待 其他 线程 来 取得 数据, 如果 没有 其他 线程 取得 数据, 则 一直 阻塞 等待。
基本用法
System.out.println("ex1=" + ex.exchange("hello world"));两个线程分别调用ex.exchange(Object obj)方法,互相阻塞,互相得到exchange方法的返回值
其他概念
调用 exchange( V x, long timeout, TimeUnit unit) 方法 后 在 指定 的 时间 内 没有 其他 线程 获取 数据, 则 出现 超时 异常。
并发集合框架
继承关系
Iterable
Collection
List
ArrayList
非 线程 安全
Vector
线程安全
当 多个 线程 分别 调用 该类 的 iterator() 方法 返回 Iterator 对象 后, 再 调用 remove() 时会出现 ConcurrentModificationException 异常, 也就是 并不 支持 Iterator 并发 的 删除
子类Stack
后进 先出( LIFO) 的 对象 堆栈
Queue
可以 方便 地 操作 列 头,poll,add,remove等方法
PriorityQueue
它是 一个 基于 优先级 的 无 界 优先级 队列
Deque(接口)
双端 队列,表头表尾都可以操作
ArrayDeque
队列 两端 获取 数据
LinkedList
队列 两端 获取 数据 ,还可以根据 索引 的 位置 操作 数据
Set
具有 的 默认 特点 是 内容 不允许 重复, 排序 方式 为 自然 排序, 防止 元素 重复 的 原理 是 元素 需要 重写 hashCode() 和 equals() 方法
HashSet
非线程安全,无序
LinkedHashSet
非线程安全,有序
TreeSet
它不 仅 实现 了 Set 接口, 而且还 实现 了 SortedSet 和 NavigableSet 接口
SortedSet 和 NavigableSet 接口 在 功能上 得到 了 扩展, 比如 可以 获取 Set 中 内容 的 子集, 以比 较 范围 进行 获得 子集, 支持 对 表头 与 表尾 的 数据 进行 获取 等,
Map
AbstractMap
HashMap
非线程安全
HashTable
线程安全
不支持并发操作
LinkedHashMap
非阻塞队列
Map相关
ConcurrentHashMap
线程安全
支持并发操作
ConcurrentSkipListMap
线程安全
支持排序
支持并发操作
Set相关
ConcurrentSkipListSet
支持并发操作
支持排序
支持并发操作
不允许重复
CopyOnWriteArraySet
Queue相关
ConcurrentLinkedQueue
方法 poll() 当 没有 获得 数据 时 返回 为 null, 如果 有 数据 时 则 移 除 表头, 并将 表头 进行 返回。
方法 element() 当 没有 获得 数据 时 出现 NoSuchElementException 异常, 如果 有 数据 时 则 返回 表头 项。
方法 peek() 当 没有 获得 数据 时 返回 为 null, 如果 有 数据 时 则 不移 除 表头, 并将 表头 进行 返回。
ConcurrentLinkedDeque
双端队列
pollFirst()
pollLast()
List相关
CopyOnWriteArrayList
阻塞队列
概念
阻塞 队列 BlockingQueue, 其实 就是 如果 BlockQueue 是 空的, 从 BlockingQueue 取 东西 的 操作 将会 被 阻塞 进入 等待 状态, 直到 BlockingQueue 添加 进了 元素 才会 被 唤醒。 同样, 如果 BlockingQueue 是 满的, 也就是 没有 空余 空间 时, 试图 往 队列 中 存放 元素 的 操作 也会 被 阻塞 进入 等待 状态, 直到 BlockingQueue 里 有 剩余 空间 才会 被 唤醒 继续 操作。
分类
List相关
Set相关
Queue相关
ArrayBlockingQueue
有界
PriorityBlockingQueue
LinkedBlockingQueue
无界
类似ArrayBlockingQueue
LinkedBlockingDeque
双端操作
SynchronousQueue
一种 阻塞 队列, 其中 每个 插入 操作 必须 等待 另一个 线程 的 对应 移 除 操作, 反之亦然。 同步 队列 没有 任何 内部 容量, 甚至 连 一个 队列 的 容量 都没 有。 不能 在 同步 队列 上 进行 peek, 因为 仅在 试图 要 移 除 元素 时, 该 元素 才 存在; 除非 另一个 线程 试图 移 除 某个 元素, 否则 也不能( 使用 任何 方法) 插入 元素; 也不能 迭代 队列, 因为 其中 没有 元素 可用 于 迭代。 类 SynchronousQueue 经常 在 多个 线程 之间 传输 数据 时 使用。
DelayQueue
LinkedTransferQueue
作用和SynchronousQueue一样
Future&Callable
作用
在 默认 情况下, 线程 Thread 对象 不具 有 返回 值 的 功能, 如果 在 需要 取得 返回 值 的 情况下 是 极为 不方便 的, 但在 Java1. 5 的 并 发包 中 可以 使用 Future 和 Callable 来 使 线程 具有 返回 值 的 功能。
与Runnable接口的区别
1) Callable 接口 的 call() 方法 可以 有 返回 值, 而 Runnable 接口 的 run() 方法 没有 返回 值。
2) Callable 接口 的 call() 方法 可以 声明 抛出 异常, 而 Runnable 接口 的 run() 方法 不可以 声明 抛出 异常。
执行 完 Callable 接口 中的 任务 后, 返回 值 是 通过 Future 接口 进行 获得 的。
用法
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> feture = executorService.submit(new MyThread());
System.out.println(feture.get());
ExecutorService 接口 中的 方法 submit( Runnable,T result),第 2 个 参数 result 可以 作为 执行 结果 的 返回 值, 而 不需要 使用 get() 方法 来 进行 获得。
接口 Future 的 实现 类 是 FutureTask. java
Future.get()方法是阻塞的
isDone() 方法 无阻 塞 特性。
方法 cancel( boolean mayInterruptIfRunning) 的 参数 mayInterruptIfRunning 的 作用 是: 如果 线程 正在 运行 则是 否 中断 正在 运行 的 线程, 在 代码 中 需要 使用 if( Thread. currentThread(). isInterrupted()) 进行 配合。
任务 在 没有 运行 完成 之前 执行 了 cancel() 方法 返回 为 true, 代表 成功 发送 取消 的 命令。
方法 get( long timeout, TimeUnit unit) 的 作用 是在 指定 的 最大 时间 内 等待 获得 返回 值。
RejectedExecutionHandler
ExecutorService的execute()与submit()区别
返回值
方法 execute() 没有 返回 值
而 submit() 方法 可以 有 返回 值。
异常
方法 execute() 在 默认 的 情况下 异常 直接 抛出, 不能 捕获, 但可以 通过 自定义 Thread- Factory 的 方式 进行 捕获
submit() 方法 在 默认 的 情况下, 可以 catch Execution- Exception 捕获 异常。
Executor
继承关系
Executor(接口)
ExecutorService(接口)
AbstractExecutorService(抽象类)
ThreadPoolExecutor(类)
ForkJoinPool(类)
ScheduledExcutorService(抽象类)
ScheduledThreadPoolExcutor
线程池
Executors工具类
newCachedThreadPool()
使用 Executors 类 的 newCachedThreadPool() 方法 创建 的 是 无 界 线程 池, 可以 进行 线程 自动 回收。 所谓 的“ 无 界 线程 池” 就是 池 中 存放 线程 个数 是 理论上 的 Integer. MAX_ VALUE 最大值。
newCachedThreadPool(ThreadFactory)
定制 线程 工厂
MyThreadFactory implements ThreadFactory,复写newThread方法
newFixedThreadPool(int)
newFixedThreadPool( int) 创建 的 是有 界 线程 池, 也就是 池 中的 线程 个数 可以 指定 最大 数量。
newFixedThreadPool( int, ThreadFactory)
newSingleThreadExecutor()
创建 单一 线程 池
newSingleThreadExecutor(ThreadFactory)
ThreadPoolExecutor
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue)
·corePoolSize: 池 中 所 保存 的 线程 数, 包括 空闲 线程, 也就是 核心 池 的 大小。
maximumPoolSize: 池 中 允许 的 最大 线程 数。
keepAliveTime: 当 线程 数量 大于 corePoolSize 值 时, 在 没有 超过 指定 的 时 间内 是 不 从 线程 池 中将 空闲 线程 删除 的, 如果 超过 此时 间 单位, 则 删除。
unit: keepAliveTime 参数 的 时间 单位。
·workQueue: 执行 前 用于 保持 任务 的 队列。 此 队列 仅 保持 由 execute 方法 提交 的 Runnable 任务。
shutdown()
方法 shutdown() 的 作用 是 使 当前 未 执行 完 的 线程 继续 执行, 而 不再 添加 新的 任务 Task, 还有 shutdown() 方法 不会 阻塞, 调用 shutdown() 方法 后, 主 线程 main 就 马上 结束 了, 而 线程 池 会 继续 运行 直到 所有 任务 执行 完 才会 停止。 如果不 调用 shutdown() 方法, 那么 线程 池 会 一直 保持 下去, 以便 随时 执行 被 添加 的 新 Task 任务。
以上的文字一句话,已经接下的客人继续服务完,但是停止接客
当 线程 池 调用 shutdown() 方法 时, 线程 池 的 状态 则 立刻 变成 SHUTDOWN 状态, 此时 不 能再 往 线程 池 中 添加 任何 任务, 否则 将会 抛出 RejectedExecutionException 异常。 但是, 此时 线程 池 不会 立刻 退出, 直到 线程 池 中的 任务 都已 经处 理 完成, 才会 退出。
shutdownNow()
方法 shutdownNow() 的 作用 是 中断 所有 的 任务 Task, 并且 抛出 InterruptedException 异常, 前提 是在 Runnable 中 使用 if(if( Thread. currentThread(). isInterrupted()== true) 语句 来 判断 当前 线程 的 中断 状态, 而未 执行 的 线程 不再 执行, 也就是 从 执行 队列 中 清除。 如果 没有 if( Thread. currentThread(). isInterrupted()== true) 语句 及 抛出 异常 的 代码, 则 池 中正 在 运行 的 线程 直到 执行 完毕, 而未 执行 的 线程 不再 执行, 也从 执行 队列 中 清除。
以上文字一句话,接下的客,判断是否服务完,如果告知人家服务完,则全部清除,否则等待服务完已结客人,但不接待未接的客人
而 shutdownNow() 方法 是 使 线程 池 的 状态 立刻 变成 STOP 状态, 并 试图 停止 所有 正在 执行 的 线程( 如果 有 if 判断 则 人为 地 抛出 异常), 不再 处理 还在 池 队列 中 等待 的 任务, 当然, 它 会 返回 那些 未 执行 的 任务。
isShutdown()
方法 isShutdown() 的 作用 是 判断 线程 池 是否 已经 关闭。
只要 调用 了 shutdown() 方法, isShutdown() 方法 的 返回 值 就是 true。
Phaser
基础部分
实现方式
继承自Thread,重写run函数
实现runnable接口,实现run方法
实现Callable接口,实现call方法
三种实现方式的区别
实现Runnable接口可以避免Java单继承的局限;代码可以被多个线程共享,代码与数据是独立的;适合多个相同代码的线程处理同一资源的情况
继承Thread类和实现Runnable接口启动线程都是使用start方法,然后JVM将线程放到就绪队列中,如果有处理机可用,则调用run方法
实现callable接口,方法可以有返回值,并且可以抛出异常,需要FutureTask实现类的支持,用于接收执行结果
启动
调用start方法
线程的启动具有随机性
常用API
currentThread()
获得当前线程,如果在main中调用,当前线程是main而不是被start的线程
isAlive()
判断当前线程是否处于活动状态
线程 处于 正在 运行 或 准备 开始 运行 的 状态, 就 认为 线程 是“ 存活” 的。
sleep()
方法 sleep() 的 作用 是在 指定 的 毫秒 数 内 让 当前“ 正在 执行 的 线程” 休眠( 暂停 执行)。 这个“ 正在 执行 的 线程” 是指 this. currentThread() 返回 的 线程。
但是,不释放对象锁;需要抛出中断异常。
yield()
放弃当前的CPU资源,进入就绪状态;将它 让给 其他相同优先级的任务 去 占用 CPU 执行 时间。 和sleep相比,同样不释放锁,但不能控制具体交出CPU的时间,放弃当前的CPU资源,进入就绪状态,所以有可能 刚刚放弃, 马上又获得 CPU 时间 片。
wait
进入阻塞状态,释放锁,交出cpu执行权限;object方法
join()
当调用thread1.join()方法后,main线程(当前父线程)会进入等待,然后等待thread1执行完之后再继续执行。
join方法会让线程进入阻塞状态,(父线程)释放对一个对象持有的锁,并交出CPU执行权限
抛出中断异常
getId()
getId() 方法 的 作用 是 取得 线程 的 唯一 标识。
停止线程
使用 退出 标志, 使 线程 正常 退出, 也就是 当 run 方法 完成 后 线程 终止。
stop()
这个 方法 是 不安全 的( unsafe), 而且 是 已被 弃 用 作废 的( deprecated)
调用 stop() 方法 时会 抛出 java. lang. ThreadDeath 异常, 但在 通常 的 情况下, 此 异常 不需要 显 式 地 捕捉。
destroy()
废弃的方法
使用 interrupt() 方法 中断sleep、wait状态的线程。
interrupt()
单独调用interrupt方法可以使处于阻塞的线程抛出中断异常,中断处于阻塞状态的线程
调用 interrupt() 方法 仅仅是 在当 前 线程 中 打了 一个 停止 的 标记, 并不是 真的 停止 线程。
测试 当前 线程 是否 已经 中断。 线程 的 中断 状态 由该 方法 清除。 换句话说, 如果 连续 两次 调用 该 方法, 则 第二次 调用 将 返回 false( 在 第一次 调用 已 清除 了 其中 断 状态 之后, 且 第二次 调用 检验 完 中断 状态 前, 当前 线程 再次 中断 的 情况 除外)。
isInterrupt()
测试 线程 Thread 对象 是否 已经 是 中断 状态, 但不 清除 状态 标志。
通过interrupt方法和isInterrupted()方法来停止正在运行的线程,类似标志位的用法
捕获IO异常,中断I/O阻塞状态
抛出InterruptedException
if (this. interrupted()) { System. out. println(" 已经 是 停止 状态 了! 我要 退出 了!"); throw new InterruptedException(); }
使用 return 停止 线程
建议 使用“ 抛 异常” 的 方法 来 实现 线程 的 停止, 因为 在 catch 块 中 还可以 将 异常 向上抛, 使 线程 停止 的 事件 得以 传播。
暂停/恢复线程
suspend()
废弃的方法
缺点
此方法是独占的,如果拿到锁并且调用了线程的suspend()方法,那其他线程就再也拿不到锁,无法执行了
容易 出现 因为 线程 的 暂停 而 导致 数据 不 同步 的 情况。
resume()
线程间通信
等待/通知
wait()
方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch语句进行捕捉异常。
wait()方法被执行后,锁立即释放
线程呈wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常。
wait(1000),等待1s,超过1s自动唤醒
当多个执行相同任务的线程,条件判断wait()的时候要用while而不能用if
当线程wait之后,又被唤醒的时候,是从wait后面开始执行,而不是又从头开始执行的,所以如果用if的话,被唤醒之后就不会在判断if中的条件,而是继续往下执行了
notify()
notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程对其发出通知notify,并使它等待获取该对象的对象锁。
需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。
notify()方法执行后,不自动释放,必须执行完notify()方法所在的同步synchronized代码块后才释放锁。
notifyAll()唤醒所有线程
线程状态切换图
两个队列
就绪队列
存储将要获得锁的线程
被notify()并且拿到锁的线程
阻塞队列
存储被阻塞的线程
调用wait()的线程
不同线程间直接传送数据
字节流
PipedInputStream
PipedOutputStream
字符流
PipedReader
PipedWriter
join
作用:创建子线程的主线程,必须等待子线程执行完毕,或销毁后后,才能够提前执行完成
thread.join()
join(long)中的参数是设定等待的时间
方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。
ThreadLocal
作用:解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据。
ThreadLocal threadLocal = new ThreadLocal(); threadLocal.set("tes value"); threadLocal.get();
解决threadLocal初始化get()为null的问题
继承自ThreadLocal类,复写initialValue()方法
InheritableThreadLocal
以在子线程中取得父线程继承下来的值。
线程的优先级
setPriority()
线程 的 优先级 分为 1~ 10 这 10 个 等级, 如果 小于 1 或 大于 10, 则 JDK 抛出 异常 throw new IllegalArgumentException()。
JDK 中 使用 3 个 常量 来 预置 定义 优先级 的 值,
public final static int MIN_ PRIORITY = 1;
public final static int NORM_ PRIORITY = 5;
public final static int MAX_ PRIORITY = 10;
特性
继承 特性
规律性:高优先级的线程总是先执行完
随机性:高优先级的线程不一定每一次都先执行完
线程的分类
用户线程
守护线程
守护线程是一种特殊的线程,它的特性有“ 陪伴”的含义,当创建他的进程中不存在其他非守护线程了,则守护线程自动销毁。
典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程也就没有存在的必要了,自动销毁。
当JVM中没有用户线程了,就算守护线程没有执行完毕也会被关闭
但是在主线程中调用守护线程.join(),那就守护不了了
对象及变量的并发访问
synchronized
synchronized同步方法
其方法内的变量为线程安全
实例变量非线程安全
两个线程访问同一个对象中的同步方法时一定是线程安全的
java中的锁是对象锁:如果拿到锁,则其他线程是不能调用对象中的所有synchronized方法的
重入锁
也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。
这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。
这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。
出现异常,锁自动释放
同步不具有继承性,需要重写synchronized关键字,synchronized关键字不是方法的一部分
synchronized同步语句块
同步方法的弊端,方法中不需要同步的代码也做了同步,增加了方法的执行时间,效率低,不够灵活
并发线程访问同一个对象object中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
不在synchronized块中就是异步执行,在synchronized块中就是同步执行。
当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的“对象监视器”是一个。
采用“序列化访问临界资源”的方案,调用按顺序执行,也就是同步的,阻塞的。
synchronized同步方法
对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
非synchronized方法或者静态方法可以被调用
同一时间只有一个线程可以执行 synchronized同步方法中的代码。
synchronized(this)同步代码块
对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。
锁非this对象
如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可大大提高运行效率。
使用“synchronized(非this对象x)同步代码块”格式进行同步操作时,对象监视器必须是同一个对象。如果不是同一个对象监视器,运行的结果就是异步调用了,就会交叉运行。
同步代码块放在非同步synchronized方法中进行声明,并不能保证调用方法的线程的执行同步/顺序性,也就是线程调用方法的顺序是无序的,虽然在同步块中执行的顺序是同步的,这样极易出现“脏读”问题。
锁得几种类型
🔒对象锁
添加在方法上和代码块上的锁都是对象锁
Class锁
synchronized还可以应用在static静态方法上,如果这样写,那是对当前的*.java文件对应的Class类进行持锁
Class锁可以对类的所有对象实例起作用
在静态方法上加Synchronized 等同于 Synchoronized(this.getClass())
死锁
JAVA_HOME下执行jps列出java进程
执行jstack 进程id
出现异常,JVM自动释放锁,不会由于异常导致死锁现象
Lock的使用
ReentrantLock
调用ReentrantLock对象的lock()方法获取锁,调用unlock()方法释放锁。
基本用法
Lock lock = new ReentrantLock();
lock.lock();
//do something else.
lock.unlock();应将锁的释放卸载finally中
Condition
Condition类是在JDK5中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。
Condition condition = lock.newCondition();
condition.wait()
Object类中的wait()方法相当于Condition类中的await()方法。
Object类中的wait(longtimeout)方法相当于Condition类中的await(longtime,TimeUnitunit)方法。
condition.signal()
Object类中的notify()方法相当于Condition类中的signal()方法。
Object类中的notifyAll()方法相当于Condition类中的signalAll()方法。
公平锁和非公平锁
公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。
非公平锁就是一种获取锁的抢占机制,是随机获得锁的,先到不一定先得到锁,这个方式可能造成某些线程一直拿不到锁
new ReentrantLock(isFair)
ReentrantReadWriteLock
共享锁
关系
读操作相关的锁
读锁之间不互斥
读锁与写锁互斥
使用
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
// do something else.
lock.readLock().unlock();
排它锁
关系
写操作相关的锁
写锁与写锁互斥
使用
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.writeLock().lock();
// do something else.
lock.writeLock().unlock();
相关API
lock.getHoldCount()
查询当前线程报错此锁定的个数,也就是调用lock()的次数
lock.getQueueLength()
返回正等待获取此锁定的线程估计数,比如有5个线程,1个线程首先执行await()方法,那么在调用getQueueLength()方法后返回值是4,说明有4个线程同时在等待lock的释放。
lock.getWaitQueueLength(condition)
返回等待与此锁定相关的给定条件Condition的线程估计数,比如有5个线程,每个线程都执行了同一个condition对象的await()方法,则调用getWaitQueueLength(Conditioncondition)方法时返回的int值是5。
lock.hasQueuedThread(thread)
查询指定的线程是否正在等待获取此锁定
lock.hasQueuedThreads()
查询是否有线程正在等待此锁定
lock.hasWaiters(condition)
是查询是否有线程正在等待与此锁定有关的condition条件
lock.isFair()
lock.isHeldByCurrentThread()
查询当前线程是否保持此锁定
lock.isLocked()
查询此锁定是否由任意线程保持
lock.lockInterruptibly()
如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常
lock.tryLock()
仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定
lock.tryLock(long timeout, TimeUnit unit)
方法booleantryLock(longtimeout,TimeUnitunit)的作用是,如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定
volatile
关键字volatile的主要作用是使变量在多个线程间可见。
volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
类的成员变量存在于公共堆栈及线程的私有堆栈中。在JVM被设置为-server模式时为了线程运行的效率,线程一直在私有堆栈中取值
线程的私有堆栈
读取公共内存
关键字volatile虽然增加了实例变量在多个线程之间的可见性,但它却不具备同步性,那么也就不具备原子性。
变量在内存中的工作过程
使用原子类进行i++操作
AtomicInteger
synchronized代码块有volatile同步的功能
定时器
Timer
TimerTask是以队列的方式一个一个被顺序执行的,所以执行的时间有可能和预期的时间不一致,因为前面的任务有可能消耗的时间较长,则后面的任务运行的时间也会被延迟。
如果执行任务的时间早于当前时间,则立即执行task任务。
cancel
作用是将任务队列中的全部任务清空。
Timer类中的cancel()方法有时并不一定会停止执行计划任务,而是正常执行。
有时并没有争抢到queue锁,所以TimerTask类中的任务继续正常执行。
TimerTask
几种用法
schedule( TimerTask task, Date time)
在指定的日期执行一次某一任务。
schedule(TimerTask task,Date firstTime,long period)
是在指定的日期之后,按指定的间隔周期性地无限循环地执行某一任务。
schedule( TimerTask task, long delay)
以当前的时间为参考时间,在此时间基础上延迟指定的毫秒数后执行一次TimerTask任务。
schedule( TimerTask task, long delay, long period)
当前的时间为参考时间,在此时间基础上延迟指定的毫秒数,再以某一间隔时间无限次数地执行某一任务。
scheduleAtFixedRate( TimerTask task, Date firstTime, long period)
cancel
将自身从任务队列中清除。其他任务不受影响。
凡是使用方法中带有period参数的,都是无限循环执行TimerTask中的任务。
单例模式&多线程
饱汉模式
不存在线程安全问题
饿汉模式
存在线程安全问题
解决:加锁,加同步块
但是效率低
应该只针对需要同步的代码部分加锁
所以引入DLC双重锁定机制
0 条评论
下一页