多线程笔记
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程笔记相关的知识,希望对你有一定的参考价值。
1.线程的6种状态(Thread.State)
(1)New(新创建):
new新线程,还未运行。
(2)Runnable(可运行)
调用start方法后。
(3)Blocked(被阻塞)
当前线程试图获取内部的对象锁但该锁被其他线程持有时,该线程进入阻塞状态;当其他线程释放该锁,且线程调度器允许本线程持有它的时候变成非阻塞状态。
(4)Waiting(等待)
当线程等待另一个线程通知线程调度器某个条件时,它自己进入等待状态。如执行(Object)wait、(Thread)join方法 ,或是等待java.util.concurrent库中的Lock或Condition时。
(5)Timed Waiting(计时等待)lock
执行带有超时参数的方法时,如(Thread)sleep、(Object)wait、(Thread)join、(Lock)tryLock、(Condition)await。
(6)Terminated(终止)
a.run方法正常退出而自然死亡;
b.由于未捕获的异常导致run方法异常退出而意外死亡。
2.线程属性
2.1线程优先级
MIN_PRIORITY=1,MAX_PRORITY=10,NORM_PRIORITY=5
当线程调度器有机会选择新线程时,优先选择优先级高的。
线程优先级是高度依赖于宿主系统的,Java线程优先级映射到宿主系统时可能更多也可能更少。
2.2守护线程
setDaemon(true);//为其他线程提供服务;在线程启动之前调用
注:守护线程应该永远不去访问固有资源,如文件、数据库等,因为它可能会在任何时候甚至一个操作的中间发生中断。
2.3未捕获异常处理器
线程的run方法不能抛出任何被检测的异常,而未检测异常又会导致线程终止。
不需要用catch处理可以被传播的异常,在线程死亡之前,异常被传递到一个用于未捕获异常的处理器。
3.同步
3.1原因:多线程共享同一数据
3.2实现方式
方式(一)synchronizd关键字
方式(二)java.util.concurrent.locks.Lock接口,实现类有:
- ReentrantLock
- ReentrantReadWriteLock.ReadLock
- ReentrantReadWriteLock.WriteLock
注:解锁(unlock)语句应在finally块中。
3.3锁对象
Reentrantlock();
锁是可重入的,即线程可以重复获得已持有的锁,锁保持一个持有计数来跟踪lock方法的嵌套调用。
3.4条件对象(条件变量)
原因:要用一个变量来管理那些已进入临界区,即获得了锁却不能做有用工作的线程。
Condition c=bankLock.newCondition();
当条件不满足时,调用c.await()方法,线程进入该条件的等待集。
注意与等待锁的线程区分:当锁可用时,该线程不能马上解除阻塞,相反,它要阻塞直到其他线程调用同一条件的signalAll()方法。
signal()方法随机解除等待集中某个线程的阻塞状态。
小结:
提供高度的锁定控制。
- 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码;
- 锁可以管理试图进入被保护代码段的线程;
- 锁可以拥有一个或多个相关的条件对象;
- 每个条件对象管理那些已进入被保护代码段但还不能运行的线程。
3.5 synchronized关键字
Java中的每个对象都有一个内部锁,并且该锁有一个内部条件。
wait()/notifyAll()方法相当于condition.await()/signalAll()方法。
静态方法也可以声明为synchronized(类对象的内部锁)。
所以,synchronized静态方法有什么意义吗?
内部锁和条件的局限性:
- 不能中断一个正在试图获得锁的线程;
- 试图获得锁时不能设定超时;
- 每个锁仅有单一条件,可能是不够的。
建议:既不使用Lock/Condition,也不实用synchronized;推荐使用java.util.concurrent 包中的一种机制。
3.6 同步阻塞
synchronized(obj){
...
obj.method1();
obj.method2();
}
又叫客户端锁定,必须保证(obj)类的所有可修改方法都使用内部锁。
因此,该方法很脆弱,不推荐使用。
3.7 监视器
在程序员不需要考虑如何加锁的情况下保证多线程安全。
监视器的特性:
- 监视器是只包含私有域的类;
- 每个监视器类的对象有一个相关的锁;
- 使用该锁对所有的方法进行加锁;
- 该锁可以有任意多个相关条件。
Java对象的synchronized类似监视器,但不同(安全性下降):
- 域不要求必须是private;
- 方法不要求必须是synchronized;
- 内部锁对客户是可用的。
3.8 volatile域
提供一种免锁机制;编译器和虚拟机知道该域可能被另一个线程并发更新。
不提供原子性。
3.9 原子性
如果对共享对象除了赋值之外没有别的操作,可以将其声明为volatile。
3.10 死锁
两个或两个以上的线程在执行过程中,因争夺资源而产生的一种互相等待的现象。
产生死锁的4个必要条件:
- 互斥条件:一个资源同时只能有一个线程访问;
- 请求与保持条件:一个线程请求阻塞时,对于已获得的资源保持不放;
- 不可剥夺条件:一个线程获得的资源在只用完之前不能被剥夺,只能在使用完毕后释放;
- 循环等待条件:若干线程形成头尾相接的循环等待资源关系。
解决死锁的方法:
- 预防死锁:设置限制条件,破坏产生死锁的必要条件(之一);效率降低;
- 死锁避免:允许前三个必要条件,但通过明智的选择确保永远不会到达死锁点;比死锁预防允许更多的并发;
- 死锁检测:不须采取任何限制措施,允许发生死锁。但通过系统设置的检测机构及时检测死锁的发生,并精确地确定死锁相关的线程和资源,采取相关措施将死锁清除;
- 死锁解除:与死锁检测相配套,常用方法:撤销或挂起一些线程,以便回收资源分配给已阻塞的线程;死锁检测和解除会获得较好的资源利用率和吞吐量,但实现难。
3.11 读写锁
- java.util.concurrent.locks.ReentrantReadWriteLock
对所有的获取方法加读锁——readLock()
对所有的修改方法加写锁——writeLock()
4. 阻塞队列
生产者、消费者
多线程程序中,使用不会抛出异常的poll、peek和offer方法。
- java.util.concurrent.LinkedBlockingQueue<E> 容量无上界,但也可以指定最大容量。
- java.util.concurrent.LinkedBlockingDeque<E> 双端版本
- java.util.concurrent.ArrayBlockingQueue<E> 构造时需要指定容量,并且有一个可选参数用来指定是否需要公平性。
- java.util.concurrent.PriorityBlockingQueue<E> 是优先级队列,而非先进先出。
5.线程安全的集合
java.util.concurrent包提供了映射表、有序集和队列的高效实现,如:
- java.util.concurrent.ConcurrentHashMap<K,V>
- java.util.concurrent.ConcurrentSkipListMap<K,V>
- java.util.concurrent.ConcurrentSkipListSet<E>
- java.util.concurrent.ConcurrentLinkedQueue<E>
并发的散列表可以高效的支持大量读者和一定数量的写者;保证原子性。
6.执行器
线程池(Thread pool):程序中创建了大量生命期很短的线程时使用;减少并发线程的数目。
将一个Runnable对象交给线程池,就会有一个线程调用run方法。
执行器(Executor)类有许多静态工厂方法用来构建线程池。
以上是关于多线程笔记的主要内容,如果未能解决你的问题,请参考以下文章