Java - 并发编程实践(读书笔记)
[注] 同步机制保证:1)原子性 2)内存可见性;
Volatile变量只能保证:1)可见性; - 恰当的同步,同步的弱形式,确保对一个变量的更新以可预见的方式告知其他线程。
[注] 用锁来协调访问变量时,每次访问变量都需要用同一个锁。锁不仅仅是关于同步与互斥的,也是关于内存可见的。
为了保证所有线程都能够看到共享的、可变变量的最新值,读取和写入线程必须使用公共的锁进行同步。
[注] Java提供的内部锁是可重进入的,因为线程在试图获取它自己占有的锁时,请求会成功。重进入(Reenter)意味着锁的请求是基于"每线程",而不是基于"每调用";
[注] **本地(基于栈的)变量,不会被跨线程共享,因此不需要同步;
每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,栈内存可以理解为线程的私有内存;
[注] 线程中断是一个协作机制,每一个线程都有一个boolean类型的中断状态,在中断的时候,这个中断状态被设置为true。
阻塞库函数,比如:Thread.sleep()和Object.wait(),试图监测线程何时被中断,它们对中断的响应表现为:清除中断状态,抛出InterruptedException,这表示阻塞操作因为中断而提前结束。
[注] Volatile变量 vs 锁 :
Volatile变量与锁相比是更轻量的同步机制,它们不会引起上下文的切换和线程调度。然而,Volatile变量与锁相比有一些局限性:
尽管它们提供了相似的可见性保证,但是它们不能用于构建原子化的复合操作。这意味着当一个变量依赖其他变量时,或者当前变量的新值依赖于旧值时,是不能用Volatile变量的。
[注] 原子变量类 vs 锁 :
原子变量比锁更精巧,更轻量,因为它不会引起线程的挂起和重新调度;
CAS:compare and set; 非阻塞算法;
术语:检查并运行
[注] 条件队列:
Object中的wait、notify、notifyAll方法构成了内部条件队列的API;
一个对象的内部锁与它的内部条件队列是相关的,为了能够调用对象X的任一个条件队列方法,必须持有对象的锁;
条件谓语涉及状态变量,状态变量是由锁保护的,所以测试条件谓语之前,必须先持有锁;
* 锁、条件谓语和条件变量之间的三元关系,涉及条件谓词的变量必须由锁保护,检查条件谓词时以及调用wait和notify时,必须持有锁对象;
状态依赖方法的规范式:
1 2 3 4 5 6 7 8 9 | void stateDependentMethod() throws InterruptedException{ //条件谓语必须被锁守护 synchronized (lock){ while (!conditionPredicate()){ lock.wait(); } //现在,对象处于期望的状态中 } } |
Obejct.wait 内部流程:
1) 会自动释放锁,并请求OS挂起当前线程,让其他线程可以获得该锁进而修改对象的状态;
2) 线程被唤醒时,wait会重新请求与条件队列相关联的锁;
Object.notify:JVM从条件队列中等待的众多线程中挑选出一个并把它唤醒;
Object.notifyAll:JVM唤醒条件队列中等待的所有线程;(通常使用notifyAll)
[注] 一个公平的条件队列可以确保线程从等待集中释放的相对顺序,内部条件队列并不提供公平队列,显式的Condition可以提供公平/非公平队列的选择;
[注] Condition是显式的条件队列(await、signal、signalAll等方法),一个Condition与一个单独的Lock相关联,Lock.newCondition()可以创建一个Condition;
[注] Lock:显式锁,可中断的锁获取请求,中断将抛出InterruptedException;
1 2 3 4 5 6 7 8 9 10 11 | public boolean sendOnShareLine(String message) throws InterruptedException{ lock.lockInterruptibly(); try { return cancellableSendOnSharedLine(message); } finally { lock.unlock(); } } private boolean cancellableSendOnSharedLine(String message) throws InterruptedException{ ... } |
阻塞队列 BlockingQueue : LinkedBlockingQueue、ArrayBlockingQueue、PriorityBlockingQueue
1)阻塞方法:put()、take()
2)非阻塞方法:offer()、poll()
Executor框架 - 任务执行框架:将任务的提交与任务的执行体进行解耦,可以简单的为给定的任务制定执行策略。
类库提供了一个灵活的线程池实现:ThreadPoolExecutor,以及一些有用的预设配置,可以通过 Executors 中的静态工厂方法创建一个线程池:
newFixedThreadPool
newCachedThreadPool
newScheduledThreadPool
newSingleThreadExecutor
为了解决执行服务的生命周期问题,ExecutorService 接口扩展了Executor,添加了一些用于生命周期管理的方法,同时还有一些用于任务提交的便利方法。
想要使用Executor,还必须能够将任务描述为Runnable。Executor框架使用Runnable作为其任务的基本表达形式。
- Runnable 只是相当有限的抽象,run方法不能返回值或者抛出受检查的异常;
- Callable 可携带结果的任务,可以把其他类型的任务封装成一个Callable;
一个Executor执行的任务的生命周期有4个阶段:创建、提交、开始和完成。
Future 用以描述"任务"的生命周期,提供方法来获得任务的结果、取消任务以及检查任务是否已经完成还是被取消。
有很多种方法可以创建一个描述任务的Future:
- 将一个Runnable或一个Callable提交给executor,然后得到一个描述任务执行的Future,如:ExecutorService.submit()方法;
- 显示为给定的Runnable或Callable实例化一个FutureTask;
1 2 3 4 5 6 7 8 9 10 11 12 | public interface Callable<V>{ V call() throws Exception; } public interface Future<V>{ boolean cancel( boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException,ExecutionException,CancellationException; V get( long timeout,TimeUnit unit) throws InterruptedException,ExecutionException,CancellationException,TimeoutException; } |
可参考文章:
Java并发编程实战读后感 http://www.jianshu.com/p/c072cb0b4763
并发编程网-高质量并发译文 http://ifeve.com/doug-lea/
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步