1 java并发基础线程
2 线程简介
3 什么是多线程?
4 操作系统调度的最小单元,可以使用 JMX (ThreadMXBean)来查看一个普通的Java程序包含哪些线程
5 可以使用 jstack 查看运行时的线程信息
6 为什么要使用多线程?
7 1. 更多的处理器核心
8 2. 更快的响应时间
9 特别是多业务操作更加快速
10 3. 更好的编程模型
11 Java提供了良好、考究并且一致的多线程编程模型
12 线程优先级
13 背景
14 CPU分配时间片给线程,时间片用完就会发生线程调度,分配到时间片的多少决定了线程使用处理器资源的多少
15 作用
16 线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。
17 Java
18 优先级的范围是1~10,默认优先级是5,优先级搞得线程分配时间片的数量要多于优先级低的线程
19 设置线程优先级时,针对频繁阻塞(休眠/IO操作)的线程需要设置设计高优先级,而偏重计算(需要较多CPU时间/偏运算)的线程需要设置较低的优先级,确保处理器不会被独占。
20 ps:线程优先级不能作为程序正确性的依赖,因为有些操作系统会忽略Java线程对优先级的设定。
21 线程的状态
22 6大状态
23 NEW
24 初始状态,线程被构建,但是还没有调用start()方法
25 RUNNABLE
26 运行状态,Java线程将操作系统中的就绪和运行两种状态统称为“运行状态”
27 BLOCK
28 阻塞状态,表示线程阻塞于锁
29 WAITING
30 等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或者等待)
31 TIME_WAITING
32 超时等待状态,可以在等待的时间自行返回的
33 TERMINATED
34 终止状态,表示当前线程已经执行完毕
35 Java状态转移
36 WAITING-->RUNNABLE
37 Object.notify()
38 Object.notifyAll()
39 LockSupport.unpark(Thread)
40 TIME_WAITING-->RUNNABLE
41 Object.notify()
42 Object.notifyAll()
43 LockSupport.unpark(Thread)
44 BLOCK-->RUNNABLE
45 获取到锁
46 1. 实例化后还未start()方法时的状态 New
47 2. New-->RUNNABLE
48 系统调度
49 Thread.start()
50 running-->ready
51 Thread.yield
52 ready-->running
53 3. RUNNABLE-->WAITING
54 Object.wait()
55 Thread.join()
56 LockSupport.park()
57 4. RUNNABLE-->TIME_WAITING
58 Object.wait(long)
59 Thread.sleep(long)
60 Thread.join(long)
61 LockSupport.parkNanos()
62 LockSupport.parkUntil()
63 5. RUNNABLE-->BLOCKED
64 等待进入synchronized方法
65 等待进入synchronized块
66 6. RUNNABLE-->TERMINATED
67 run方法结束
68 Java 线程状态变迁
69 yield
70 暂停当前正在执行的线程对象。
71 yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
72 yield()只能使同优先级或更高优先级的线程有执行的机会。
73 注意:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
74 注意
75 等待进入synchronized方法/块 为阻塞状态
76 java.concurrent.Lock为 等待状态,因为Lock接口对于阻塞的实现使用了LockSupport类中的相关方法
77 Daemon线程
78 守护线程
79 特殊的线程,陪伴的含义,当进程中不存在非守护线程了,则守护线程自动销毁。
80 典型的守护线程是垃圾回收线程
81 作用是为其他线程提供便利服务
82 是一种支持性线程,主要是用在后台程序做一些后台调度与支持性工作。这意味着当JVM中没有非Daemon线程时,JVM将自动退出。
83 可以通过调用Thread.setDaemon(true)方法将线程设为Daemon线程。(注:该方法必须在start()或者run()方法前执行,也就是说必须在线程启动前执行)
84 注:Daemon线程被用作完成支持性工作,但是在java虚拟机退出时,Daemon线程中的finally块并不一定会执行。在构建Daemon时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。
85 程序 VS 进程 VS 线程
86 程序
87 一组指令的有序结合,是静态的指令,是永久存在的
88 进程
89 具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源(打开的文件,创建的socket)分配和调度的一个独立单元。进程的存在是暂时的,是一个动态的概念。
90 线程
91 线程是CPU调度和分配的基本单位,是比进程更小的能独立运行的基本单元。本身基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈)。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
92 进程 VS 线程
93 定义
94 进程是资源分配和调度的基本单位;线程是CPU/任务调度和执行的基本单位
95 包含关系
96 1个进程包含有多(大于等于1)个线程; 线程是进程的一部分(轻量级进程)
97 地址空间
98 进程之间地址空间独立; 同一进程内的线程共享本进程的地址空间
99 切换开销
100 进程之间切换开销大; 线程之间切换开销小(创建和销毁)
101 创建
102 进程fork/vfork; 线程pthread_create
103 销毁
104 进程结束,它拥有的所有线程都将销毁; 线程结束,不会影响同个进程中的其他线程
105 私有属性
106 进程:PCB(进程控制块); 线程:TCB(线程控制块),线程Id,寄存器,上下文
107 一个程序至少只有一个进程,一个进程至少有一个线程
108 启动和终止线程
109 构造线程
110 init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc )
111 父线程就是当前线程(开启多线程的线程),子线程对象是由其父线程来进行空间分配的,继承了parent线程是否为Daemon、优先级、加载资源的contextClassLoader以及可继承的ThreadLocal。还会分配给一个唯一的ID来标识这个child线程。
112 init()运行完毕,线程对象就初始化好了,在堆内存中等待运行
113 构造函数
114 Thread(Runnable target)
115 Thread(Runnable target, String name)
116 因为Thread实现类Runnable,所以可以传递Thread
117 如果多个new Thread()里面的Runnable是同一个线程对象,那么那个对象的变量时被这几个新的线程共享的变量。
118 启动线程
119 start() 含义:当前线程(即parent线程)同步告知java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程
120 start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用run()方法,也就是使得线程得到运行,启动线程,具有异步执行的效果。而直接调用run方法不是异步而是同步
121 注意:执行start()方法的顺序不代表线程启动的顺序
122 注意:启动一个线程之前,最好起个名字
123 活动状态就是线程已经启动且尚未终止(isAlive为true),start()调用了就是活动状态了
124 理解中断
125 被其他线程中断: interrupt()
126 判断是否被中断:isInterrupted()
127 测试线程Thread对象是否已经被中断,但是不清除状态标志
128 中断表示复位:Thread.interrupted()
129 测试当前线程是否已经被中断,执行后具有将状态标志清除为false的功能
130 每次抛出InterruptedException之前,JVM会先将中断标识位清除
131 注意:不论是TERMINATED还是InterruptedException, isInterrupted都返回false
132 安全地终止线程
133 中断方式 Interrupt
134 标识位boolean true false
135 当run方法完成后线程终止
136 抛出异常 throw new InterruptedException
137 过期的
138 suspend() 暂停
139 调用后,线程不会释放已经占有的资源(比如锁),而是占有资源进入睡眠状态,容易引发死锁。
140 缺点是独占、数据不同步
141 resume() 恢复
142 缺点是独占、不同步
143 stop() 终止
144 在终结一个线程时不会保证线程的资源正常释放
145 使用stop释放锁将会给数据造成不一致的结果
146 会抛出java.lang.ThreadDeath异常
147 暴力停止线程
148 其他
149 sleep()
150 在指定的毫秒内让当前"正在执行的线程"休眠(暂停执行),即this.currentThread()返回的线程
151 currentThread()
152 构造方法
153 Thread.currentThread().getName()
154 main
155 this.getName()
156 Thread-0
157 直接调用run()方法
158 Thread.currentThread().getName()
159 main
160 this.getName()
161 Thread-0
162 调用start()方法
163 Thread.currentThread().getName()
164 Thread-1 || setName("A")--> A 。
165 this.getName()
166 Thread-0
167 isAlive()
168 线程处于正在运行或准备开始运行的状态就认为线程是存活的, start()之后就是活跃状态了
169 Thread.sleep() false
170 构造方法||直接调用run()方法||调用start()方法
171 Thread.currentThread().isAlive()
172 true
173 this.isAlive()
174 false
175 yield()
176 放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间,但放弃的时间不确定,有可能刚刚放弃,马上就又获得CPU时间片。
177 将CPU让给其他资源导致速度变慢
178 优先级
179 优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A线程是一样的
180 优先级具有规则性,让CPU尽量将执行资源让给优先级比较高的线程。线程的优先级与代码的执行顺序(start()的先后顺序)无关
181 优先级具有随机性,优先级高的线程不一定每一次都先执行完
182 有副作用
183 用等待/通知机制代替
184 对象及变量的并发访问
185 synchronized
186 同步方法
187 方法内的变量为线程安全
188 实例变量非线程安全
189 多个对象多个锁
190 synchronized取得的锁都是对象锁
191 synchronized方法和锁对象
192 A线程先持有object对象的Lock锁,B线程可以异步的方式调用object对象中的非synchronized类型的方法。
193 A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的其他synchronized类型的方法则需要等待,也就是同步。
194 解决了脏读问题
195 可重入锁
196 自己可以再次获得自己的内部锁
197 当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。如果不可锁重入的话,就会造成死锁。
198 在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的
199 可重入锁也支持在父子类继承的环境中
200 子类完全可以通过可重入锁调用父类的同步方法
201 出现异常,线程持有的锁会自动释放
202 同步不具有继承性,还得在子类的方法中添加synchronized关键字
203 有弊端的,假如A线程调用同步方法执行一个长时间任务,B线程则必须等待比较长的时间,这样的情况可以用synchronized语句块来解决,虽然能实现同步,但会受到阻塞,所以影响运行效率。
204 同步语句块
205 当两个并发线程访问同一个对象object的synchronized(this)同步代码块时,一段时间内只有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行改代码块
206 当一个线程访问一个对象object的synchronized(this)同步代码块时,另一个线程仍然可以访问该object对象中的 非synchronized(this)同步代码块
207 一半同步一半异步
208 当一个线程访问一个对象object的synchronized(this)同步代码块时,另一个线程对同一个object对象中所有其他synchronized(this)同步代码块的访问将被阻塞,说明synchronized使用的对象监视器是一个
209 synchronized方法和synchronized(this)同步代码块都是锁定当前对象的
210 将任意对象作为对象监视器,synchronized(非this对象anyobject)
211 优点
212 synchronized(非this对象anyobject)代码块中的程序与同步方法是异步的
213 不与其他锁this同步方法争抢this锁,可以大大提高运行效率
214 结论
215 当多个线程同时执行synchronized(x)同步代码块时呈现同步效果
216 同步原因,使用了同一个对象监视器
217 当其他线程执行x对象中synchronized同步方法时呈现同步效果
218 当其他线程执行x对象方法里面的synchronized(this)同步代码块时呈现同步效果
219 针对X对象内部的同步方法和同步代码块
220 多个线程调用同一个方法是随机的
221 多个线程之间没有固定的顺序,随机的
222 静态同步synchronized方法和synchronized(class)代码块
223 应用在静态方法上
224 对当前的 *.java文件对应的Class类进行持锁
225 注意,一个是对象锁,一个是Class锁,会出现异步的情况,Class锁可以对类的所有对象实例起作用,
226 synchronized(class)对class上锁后,其他线程只能以同步的方式调用class2的静态同步方法
227 两个不同的对象,但是静态同步方法还是同步运行
228 只要对象不变,即使对象的属性被改变,运行的结果还是同步
229 synchronized(string)
230 String常量池特性,两个线程拥有相同的锁,造成另一个线程不能运行
231 注意:给string赋值另一个值,锁对象就变了
232 大多数情况下,同步synchronized代码块都不使用String作为锁对象,而改用new Object()实例化对象
233 多线程死锁
234 synchronized(lock1){
235 synchronized(lock2){}
236 }
237 synchronized(lock2){
238 synchronized(lock1){}
239 }
240 监测方法
241 1. jps查看运行的线程Run的id
242 2. jstack -l id
243 3. DeadThread
244 volatile
245 非原子:i++
246 解决就是用synchronized或者AtomicInteger原子类
247 volatile v.s. synchronized
248 修饰
249 volatile只能用于修饰变量
250 synchronized可以修饰方法以及代码块
251 阻塞
252 多线程访问volatile不会发生阻塞
253 多线程访问synchronized会阻塞
254 原子性
255 volatile不能保证原子性,能保证可见性
256 synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步
257 作用
258 volatile解决的是变量在多个线程之间的可见性
259 synchronized解决的是多个线程之间访问资源的同步性
260 线程安全包含原子性和可见性两个方面,java的同步机制都是围绕这两个方面来保证线程安全的
261 Atomic 原子方法和原则方法虽然都是原子的,但是原子方法和原子方法之间的调用不是原子的,解决这样的问题必须用同步,synchronized
262 线程间通信
263 不使用等待/通知机制实现多个线程之间的通信
264 传统的使用sleep()+while(true)死循环来实现多个线程间的通信
265 浪费CPU资源
266 volatile & synchronized
267 共享内存和本地内存拷贝的同步更新问题,使得变量不一定能是最新的
268 volatile: 保证所有线程对变量的可见性
269 synchronized:保证线程对变量访问的可见性和排他性,获取monitor; 主要确保多个线程在同一时刻,智能有一个线程处于方法或者同步块中。
270 synchronized
271 Monitor.Enter--->get监视器Monitor
272 Enter成功--->锁定对象Object----->Monitor.Exit----->通知同步队列中的线程出队列
273 Enter失败---->线程进入同步队列SynchronizedQueue----->Monitor.Exit后通知,出队列
274 对象、监视器、同步队列和执行线程之间的关系
275 等待/通知机制
276 做什么和怎么做解耦,生产者/消费者模式
277 任意java对象所具备的
278 依托于同步机制,目的就是确保等待线程从wait()方法返回时能够感知到通知线程对变量作出的修改
279 等待通知机制:线程A调用了对象O的wait()方法进入了等待状态,而线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象的wait()与notify()或notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
280 方法
281 notify(): 通知在一个对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁。
282 notyfyAll(): 通知所有等待在该对象上的线程
283 wait(): 调用该方法的线程进入waiting状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用wait()方法后,会释放对象的锁。在从wait方法返回前,线程与其他线程竞争重新获得锁。
284 wait(long): 超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回(没有线程对锁进行唤醒就自动唤醒)。
285 wait(long, n): 对于超时时间更细粒度的控制,可以达到纳秒。
286 注意
287 1)使用wait()、notify()、notifyAll()方法都需要先对调用对象加锁。如果没有持有适当的锁,也会抛出IllegalMonitorStateException
288 在调用condition.await()方法之前必须调用lock.lock()代码获得同步监视器,否则会报错IllegalMonitorStateException异常
289 2)调用wait()方法后,线程状态由RUNNING变为WAITTING,将锁释放,并将当前线程放到对象的等待队列。WaitingQueue
290 3)notify()或notifyAll()方法调用后,不会立刻释放锁,等待线程依旧不会从wait()返回,需要等待调用notify()、notifyAll()的线程释放锁之后,等待线程才可能会拿到锁,等待线程才有机会从wait()返回。
291 4)notify()方法将等待队列WaitingQueue中的一个等待线程从等待队列中移到同步队列SychronizedQueue中,而notifyAll()方法则是将等待队列WaitingQueue中的所有线程全部移动到同步队列SynchronizedQueue中,被移动的线程状态由WAITING变为BLOCKED。
292 5)从wait()返回的前提是获取调用对象的锁。
293 6) 执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。
294 7) wait()方法锁释放,notify()锁不释放,sleep()锁不释放
295 8) 当线程呈wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常,导致线程终止,锁也会被释放
296 实现
297 synchronized+wait/notify实现等待通知模式
298 被通知的线程是JVM随机选择的
299 所有的线程都会注册在一个对象上
300 condition + lock 实现等待通知模式
301 支持选择性通知,调度线程上更加灵活
302 Lock对象里面可以创建多个Condition实例(对象监视器)
303 等待/通知的经典范式
304 等待方(消费者)
305 原则
306 加锁:获取对象的锁
307 循环:如果条件不满足,那么调用对象的wait()方法,被通知后仍然要检查条件。
308 处理逻辑:条件满足则执行对应的逻辑
309 伪代码
310 synchronized(对象) {
311 while(条件不满足) {
312 对象.wait();
313 }
314 对应的处理逻辑
315 }
316 通知方(生产者)
317 原则
318 获取对象的锁
319 改变条件
320 通知所有等待在对象上的线程notifyAll()
321 伪代码
322 synchronized(对象) {
323 改变条件
324 对象.notifyAll();
325 }
326 一个生产者一个消费者
327 条件不同,一个满足条件,一个不满足条件
328 伪代码
329 synchronized(对象) {
330 if(条件满足) {
331 对象.wait();
332 }
333 跟上面条件一致
334 对应的处理逻辑
335 对象.notify();
336 }
337 操作数
338 synchronized代码块
339 操作栈
340 synchronized方法
341 多生产者多消费者
342 容易出现假死情况
343 假死状态的线程都呈WAITING状态
344 原因:notify唤醒的可能是异类,也可能是同类;比如生产者唤醒生产者
345 解决:将notify()改成notifyAll(); 不光通知同类,也通知异类
346 synchronized(对象) {
347 while(条件满足) {
348 对象.wait();
349 }
350 对应的处理逻辑
351 跟上面条件一致
352 对象.notifyAll();
353 }
354 操作数
355 synchronized代码块
356 操作栈
357 synchronized方法
358 等待超时模式
359 等待超时模式就是在等待/通知范式基础上增加了超时控制,避免执行时间过长,也不会“永久”阻塞调用者,而是按照调用者的要求返回。
360 超时等待:调用一个方法时,等待一段时间(一般给定一个时间段),如果该方法能够在给定的时间段内得到结果,那么将结果立刻返回,反之,超时返回默认结果。
361 伪代码
362 //对当前对象加锁
363 public synchronized Object get(lon mills) throws InterruptedException{
364 long future = System.currentTimeMillis() + mills;//超时时间
365 long remaining = mills;//等待持续时间
366 //当超时大于0并且result返回值不满足要求则继续等待
367 //当时间到了或者返回结果满足要求则不再等待
368 while((result == null) && remaining > 0){
369 wait(remaining);
370 remaining = future - System.currentTimeMillis();
371 }
372 return result;
373 }
374 应用场景:针对昂贵资源(比如数据库的连接)的获取都应该加以超时限制,是系统的一种自我保护机制
375 管道输入/输出流
376 管道输入输出流与普通的文件输入输出流或者网络输入输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介是内存
377 一个线程发送数据到输出管道,另一个线程从输入管道中读取数据
378 4种具体的实现
379 PipedOutputStream
380 write(string.getBytes())
381 PipedInputStream
382 read(byte[] byteArray)
383 PipedReader
384 read(char[] xx)
385 PipedWriter
386 write(string)
387 面向字节
388 面向字符
389 注意:out.connect(in); 对于Piped类型的流,必须先要进行绑定,也就是调用connect方法,如果没有将输入/输出流绑定起来,对于该流的访问将会抛出异常
390 读取线程启动后如果没有数据被写入,线程会阻塞在in.read()代码中,直到有数据被写入,才继续往下运行。
391 Thread.join()的使用
392 本质
393 涉及了等待/通知机制,等待前驱线程结束,接收前驱线程结束通知,源码本质也是wait()和notifyAll()
394 join方法的作用是等待线程对象销毁
395 while(isAlive()){
396 wait(0);//表示永远等下去
397 }
398 指导join线程终止后,线程的this.notifyAll()方法会被调用,调用notifyAll()方法是在JVM里实现的,所以在JDK中看不到
399 CountDownLatch可以实现join的功能,并且比join的功能更多
400 方法
401 join(): 当前线程A等待 thread线程终止 之后才从 thread.join 返回
402 join(long millis): 如果在给定的时间内没有终止,那么将会从该超时方法中返回,毫秒
403 join(long millis,int nanos): 如果在给定的时间内没有终止,那么将会从该超时方法中返回,纳秒
404 join过程中,如果当前线程对象被中断,则当前线程出现InterruptedException异常
405 join v.s. synchronized
406 共同点
407 具有使线程排队运行的作用,有点类似同步的运行效果
408 区别
409 join在内部使用wait()方法进行等待,具有释放锁的特点
410 synchronized关键字使用的是 对象监视器原理作为同步
411 join(long) v.s. sleep(long)
412 join(long)在内部使用wait(long)方法进行等待,具有释放锁的特点
413 sleep(long)不释放锁
414 注意:join后面的代码提前运行可能会出现陷阱意外,原因在于join方法先运行抢到锁,然后释放锁;之后再次争抢锁,发现时间已经过去,就会释放锁执行后面的代码,导致出现意外。
415 ThreadLocal的使用
416 背景
417 变量值的共享可以使用public static变量的形式,所有的线程都使用同一个public static变量
418 threadLocal实现每一个线程都有自己的共享变量
419 ThreadLocal,即线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。
420 ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用。作用:提供一个线程内公共变量,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度,或者为线程提供一个私有的变量副本,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
421 方法
422 initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法。如果set()方法没有调用,第一次get()方法调用时会进行初始化 initialValue(),每个线程会调用一次。
423 get()方法是用来获取ThreadLocal在当前线程中保存的变量副本
424 set(T value)用来设置当前线程中变量的副本
425 remove()用来移除当前线程中变量的副本
426 工作原理
427 Thread类中有一个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的内部类),它是一个Map,他的key是ThreadLocal实例对象。
428 当为ThreadLocal类的对象set值时,首先获得当前线程的ThreadLocalMap类属性,然后以ThreadLocal类的对象为key,设定value,值时则类似。
429 ThreadLocal变量的活动范围为某线程,是该线程“专有的,独自霸占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。
430 由ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非是共享的,
431 存储结构的好处
432 1、线程死去的时候,线程共享变量ThreadLocalMap则销毁。
433 2、ThreadLocalMap<ThreadLocal,Object>键值对数量为ThreadLocal的数量,一般来说ThreadLocal数量很少,相比在ThreadLocal中用Map<Thread, Object>键值对存储线程共享变量(Thread数量一般来说比ThreadLocal数量多),性能提高很多。
434 弱引用GC
435 1、使用完线程共享变量后,显示调用remove方法清除线程共享变量可以及时清除
436 2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。
437 3、对于ThreadLocal变量,我们可以手动的将其置为Null,比如tl =null。那么这个ThreadLocal对应的所有线程的局部变量都有可能被回收。
438 解决Hash冲突方法
439 线性探测
440 应用场景: 用来解决 数据库连接、Session管理、AOP耗时统计等。
441 注意
442 不过有点遗憾的是只能放一个值,再次调用set设置值,会覆盖前一次set的值。如果要多个变量,新建多个ThreadLocal对象
443 是单个线程内函数和组件的共享变量,不是多线程的共享变量,线程隔离
444 覆盖initialValue()方法可以设置默认初始值,使得get()不会返回null
445 使用InheritableThreadLocal类可以让子线程从父线程中取得值,如果子线程在取得值得同时,主线程将InheritableThreadLocal中的值进行修改,那么子线程取到的值还是旧值
446 线程池技术
447 好处
448 1. 降低资源消耗
449 通过重复利用已经创建的线程消除了频繁创建和消亡线程的系统资源开销
450 2. 提高响应速度
451 当任务到达时,任务可以不需要等等待线程创建就能立即执行
452 3. 提高线程的可管理型
453 统一分配,调优和监控
454 4. 面对过量任务的提交能够平缓的劣化。
455 实现原理
456 处理流程
457 1. 提交任务
458 2. 判断核心线程池是否已满,否 创建线程执行任务,是下一步
459 3. 判断队列是否已满,否 将任务存储在队列里,是下一步
460 4. 判断线程池是否已满, 否 创建线程执行任务,是下一步
461 5. 按照饱和策略处理无法执行的任务
462 工作线程
463 线程池创建线程时,会将线程封装成工作线程worker,worker在执行完任务之后还会循环获取工作队列里的任务来执行
464 线程池的使用
465 见 ThreadPoolExecutor
466 线程池的本质就是使用了一个线程安全的工作队列连接工作者线程和客户端线程,客户端线程将任务放入工作队列后便返回,而工作者线程则不断从工作队列上取出工作并执行。当工作队列为空时,所有的工作者线程均等待在工作队列上,当有客户端提交了一个任务后会通知任意一个工作者线程,随着大量任务被提交,更多的工作者线程会被唤醒。
467 线程池的数量不是越多越好,具体的数量需要评估每个任务的处理时间,以及当前计算机的处理器能力和数量。使用的线程过少,无法发挥处理器的性能;使用的线程过多,将会增加系统的无故开销,起到相反的作用。
468 应用
469 数据库连接池
470 服务器线程池
471 更多参加java并发框架-Executor框架
472 Timer定时器
473 线程调度任务以供将来在后台线程中执行的功能。 任务可以安排一次执行,或定期重复执行。
474 定时计划任务
475 构造函数
476 Timer():创建一个新的计时器
477 Timer(boolean isDaemon):创建一个新的计时器,可以指定其关联的线程作为守护程序运行
478 Timer(String name):创建一个新的计时器,其关联的线程具有指定的名称
479 Timer(String name,boolean isDaemon):创建一个新的计时器,其关联的线程具有指定的名称,并且可以指定作为守护程序运行
480 这个类是线程安全的:多个线程可以共享一个单独的Timer对象,而不需要外部同步。内部使用多线程的方式进行处理
481 API
482 Timer.cancel()
483 终止此计时器,将任务队列里的全部任务清空
484 有时候不一定会停止执行计划任务,而是正常执行,因为有时候并没有抢到queue锁
485 TimerTask.cancel()
486 将自身从任务队列里清除,其他任务不受影响
487 purge()
488 从该计时器的任务队列中删除所有取消的任务。
489 schedule(TimerTask task, Date time)
490 在指定的日期,执行一次某任务
491 schedule(TimerTask task, Date firstTime, long period)
492 从指定的时间开始 ,按照执行的间隔周期性地无限循环地执行某一任务。
493 执行任务的时间晚于当前时间,在未来执行
494 执行任务的时间早于当前时间,立即执行
495 schedule(TimerTask task, long delay)
496 以当前时间为参考时间,延迟指定的毫秒数后执行一次制定任务
497 schedule(TimerTask task, long delay, long period)
498 以当前时间为参考时间,延迟指定的毫秒数后执行一次制定任务,再以某一个时间间隔无限次数地执行制定任务
499 scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
500 从指定的时间 开始 ,对指定的任务执行重复的 固定周期执行 。
501 scheduleAtFixedRate(TimerTask task, long delay, long period)
502 以当前时间为参考时间,在指定的延迟之后 开始 ,重新执行 固定周期的指定任务。
503 schedule V.S. scheduleAtFixedRate
504 延时
505 下一次任务的执行时间参考的是上一次任务的“结束”时的时间计算
506 不延时
507 schedule
508 下一次任务的执行时间参考的是上一次任务的“开始”时的时间计算
509 scheduleAtFixedRate
510 下一次任务的执行时间参考的是上一次任务的“结束”时的时间计算
511 追赶性
512 schedule
513 执行任务的时间早于当前时间,立即执行, 不填充
514 scheduleAtFixedRate
515 执行任务的时间早于当前时间,立即执行, 不补充
516 Timer中允许有多个TimerTask任务,以队列的形式被顺序执行,所以执行的时间和预期的时间不一致,因为前面的任务有可能消耗的时间太长,后面的任务运行的时间也会被延迟
517 Java 5.0引入了java.util.concurrent软件包,其中一个java.util.concurrent程序是ScheduledThreadPoolExecutor ,它是用于以给定速率或延迟重复执行任务的线程池。 这实际上是对一个更灵活的替代Timer / TimerTask组合,因为它允许多个服务线程,接受各种时间单位,并且不需要子类TimerTask (只实现Runnable )。 使用一个线程配置ScheduledThreadPoolExecutor使其等同于Timer 。
518 单例模式
519 立即加载/饿汉模式
520 静态实例初始化
521 私有构造方法
522 静态getInstance
523 延迟加载/懒汉模式
524 静态实例声明
525 私有构造方法
526 静态getInstance{
527 if(是null){
528 初始化
529 }
530 }
531 解决
532 声明synchronized关键字
533 尝试同步代码块
534 针对重要代码进行单独的同步
535 使用DCL双检查锁机制
536 使用静态内置类实现单例模式
537 序列化和反序列化的单例模式实现
538 readResolve()
539 使用static代码块来实现单例模式
540 使用enum枚举数据类型实现单例模式
541 SimpleDateFormat
542 负责日期的转换和格式化
543 非线程安全的
544 解决方法
545 每个线程一个SimpleDateFormat实例
546 ThreadLocal类