死锁
是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
产生死锁的条件:
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
- 自己总结(人话):
- 争夺者数目大于争夺资源
- 争夺资源顺序不对
- 拿到资源不放手
- 有另外一个等待使用资源的线程
解决死锁:
只要打破四个必要条件之一就能有效预防死锁的发生。
打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。
打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。
打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。
打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。
- 自己总结(人话):
- 确定每个线程的拿锁顺序
- 采用尝试拿锁的方式
活锁
频繁申请锁两个线程在尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生同一个线程总是拿到同一把锁,在尝试拿另一把锁时因为拿不到,而将本来已经持有的锁释放的过程。
解决办法:每个线程休眠随机数,错开拿锁的时间。
线程饥饿
低优先级的线程,总是拿不到执行时间
Thread Local
Thread Local 线程本地变量 为每个线程提供一个变量副本。实现线程隔离
eg: 创建三个线程分别对变量count+线程id
-
未使用ThreadLocal:
public class NoThreadLocal {
static Integer count = new Integer(1);
/**
* 运行3个线程
*/
public void startTArray(){
Thread[] runs = new Thread[3];
for(int i=0;i
并没有每个线程按照 t0->0 、t1->1 、t2->2
-
使用ThreadLocal:
public class UseThreadLocal {
//TODO
private static ThreadLocal intThreadLocal = new ThreadLocal(){
int count = 0;
@Override
protected Integer initialValue() {
return count;
}
};
/**
* 运行3个线程
*/
public void StartThreadArray(){
Thread[] runs = new Thread[3];
for(int i=0;i
- ThreadLocal简析:
前面说到ThreadLocal是本地变量副本。那么他是怎么实现的呢。由ThreadLocal的set()入手
ThreadLocal.java
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//通过线程获得ThreadLocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
拿到 ThreadLocal.ThreadLocalMap threadLocals
可以看到ThreadLocal.ThreadLocalMap 里面有个Entity[]
Entity的具体模型:
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
//以threadLocal为key value 为值存入
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
因为可能存在多个类型的threadlocal 所以需要使用数组
volatile的使用 (最轻量的同步机制)
适合一写多读的场景
- 优点:保证可见性与快速更新
- 缺点:无法保证线程安全与操作原子性
使用 : - 可见性例子:
public class VolatileCase {
private static boolean ready;
private static int number;
private static class PrintThread extends Thread{
@Override
public void run() {
System.out.println("PrintThread is running.......");
while(!ready);
System.out.println("number = "+number);
}
}
public static void main(String[] args) {
new PrintThread().start();
SleepTools.second(1);
number = 51;
ready = true;
SleepTools.second(5);
System.out.println("main is ended!");
}
}
打印结果:
PrintThread is running.......
main is ended!
PrintThread并不知晓ready已经变化
加入 volatile关键字
private volatile static boolean ready;
打印结果:
PrintThread is running.......
number = 51
main is ended!
线程不安全例子:
public class NotSafe {
private volatile long count =0;
public long getCount() {
return count;
}
public void setCount(long count) {
this.count = count;
}
//count进行累加
public void incCount(){
count++;
}
//线程
private static class Count extends Thread{
private NotSafe simplOper;
public Count(NotSafe simplOper) {
this.simplOper = simplOper;
}
@Override
public void run() {
for(int i=0;i<10000;i++){
simplOper.incCount();
}
}
}
public static void main(String[] args) throws InterruptedException {
NotSafe simplOper = new NotSafe();
//启动两个线程
Count count1 = new Count(simplOper);
Count count2 = new Count(simplOper);
count1.start();
count2.start();
Thread.sleep(50);
System.out.println(simplOper.count);
}
}
理想结果:20000
打印结果:
20000
13671
13529
因为线程的执行是需要有cpu执行权的 所以导致了结果的不确定性
synchronized的使用
synchronized 一定是作用在某个对象上 当所在static 的方法 或者静态块 时 锁住的是 X.class的对象
注:锁只有作用在同个对象上才会起作用
public class SynTest {
private long count =0;
public long getCount() {
return count;
}
public void setCount(long count) {
this.count = count;
}
/*用在同步块上*/
public void incCount(){
count++;
}
//线程
private static class Count extends Thread{
private SynTest simplOper;
public Count(SynTest simplOper) {
this.simplOper = simplOper;
}
@Override
public void run() {
for(int i=0;i<10000;i++){
simplOper.incCount();
}
}
}
public static void main(String[] args) throws InterruptedException {
SynTest simplOper = new SynTest();
//启动两个线程
Count count1 = new Count(simplOper);
Count count2 = new Count(simplOper);
count1.start();
count2.start();
Thread.sleep(50);
System.out.println(simplOper.count);//20000
}
}
打印结果:
18748 20000 16735 18307
加锁:
public void incCount(){
synchronized (obj){
count++;
}
}
打印结果:
20000 20000 20000 20000
锁的作用对象:
public class SynTest {
private long count =0;
private Object obj = new Object();//作为一个锁
public long getCount() {
return count;
}
public void setCount(long count) {
this.count = count;
}
/*锁的是SyncTest.class的类对象*/
public void incCount(){
synchronized (SyncTest.class){
count++;
}
}
/*用在方法上 锁的是obj对象*/
public void incCount(){
synchronized (obj){
count++;
}
}
/*用在方法上 锁的也是SynTest.class对象*/
public static synchronized void incCount2(){
count++;
}
/*用在方法上 锁的也是当前对象实例*/
public synchronized void incCount2(){
count++;
}
/*用在同步块上,但是锁的是当前类的对象实例*/
public void incCount3(){
synchronized (this){
count++;
}
}
}