死锁的四个条件
死锁是指多个进程因竞争资源而造成的互相等待,若无外力作用,这些进程都将无法向前推进。
- 互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
- 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
- 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
- 循环等待条件: 若干进程间形成首尾相接循环等待资源的关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
线程之间同步
线程状态
- NEW 新建 new Thread()
- RUNNABLE 就绪 调⽤线程对象的start()⽅法
- Running 运⾏
程序将处于就绪状态的线程设置为当前线程,即获得CPU使⽤权,这个时候线程进⼊运⾏状态,开始运⾏run⾥ ⾯的逻辑
- BLOCKED 阻塞
- 等待阻塞:进⼊该状态的线程需要等待其他线程作出⼀定动作(通知或中断),这种状态的话CPU不会分配过来,需要被唤醒,可能也会⽆限等待下去。
⽐如调⽤wait(状态就会变成WAITING状态),也可能通过调⽤sleep()(状态就会变成TIMED_WAITING), join或者发出IO请 求,阻塞结束后线程重新进⼊就绪状态
- 同步阻塞:线程在获取synchronized同步锁失败,即锁被其他线程占⽤,它就会进⼊同步阻塞状态
备注:相关资料会⽤细分下⾯的状态
- 等待(WAITING):进⼊该状态的线程需要等待其他线程做出⼀些特定动作(通知或中断)。
- 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后⾃⾏返回 Thread.sleep();
- TEMINATED 终结 ⼀个线程run⽅法执⾏结束
线程安全问题
多个线程同时共享同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突,也就是线程安全问题。
public class UnsafeSequence {
private int value;
public int getNext(){ // Unsafe:不能返回递增的值
return value++;
}
}
原子性: 同一时间只有一个线程对变量进行操作 Atomic(CAS.Unsafe)
可见性
有序性
Java内存模型 jmm
线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。
(共享内存模型JMM)定义了一个线程对另一个线程可见。共享变量存放在主内存中
如何解决多线程之间线程安全问题?
让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
- 使用多线程之间同步synchronized
- 使用锁(lock)。
多线程之间同步:当多个线程共享同一个资源,不会受到其他线程的干扰。
加锁,⽐如synchronize/ReentrantLock
使⽤volatile声明变量,轻量级同步,不能保证原⼦性(需要解释)
使⽤线程安全类(原⼦类AtomicXXX,并发容器,同步容器
CopyOnWriteArrayList/ConcurrentHashMap等
ThreadLocal本地私有变量/信号量Semaphore等
守护线程与非守护线程
Java中有两种线程,一种是用户线程,另一种是守护线程。
守护线程(即daemon thread),是个服务线程,准确地来说就是服务其他的线程
用户线程是指用户自定义创建的线程,主线程停止,用户线程不会停止(另一条执行路径)main线程是用户线程;
守护线程当进程不存在或主线程停止,守护线程也会被停止。JVM中的垃圾回收线程就是一个守护线程
线程优先级
优先级越高, 执行几率越大
并发模拟工具
- postman
- apache bench
创建线程方式
- 继承Thread,重写⾥⾯run⽅法,创建实例,执⾏start
优点:代码编写最简单直接操作
缺点:没返回值,继承⼀个类后,没法继承其他的类,拓展性差
- (常用)⾃定义类实现Runnable,实现⾥⾯run⽅法,创建Thread类,使⽤Runnable接⼝的实现对象
作为参数传递给Thread对象,调⽤Strat⽅法
优点:线程类可以实现多个⼏接⼝,可以再继承⼀个类
缺点:没返回值,不能直接启动,需要通过构造⼀个Thread实例传递进去启动
- 创建callable接⼝的实现类,并实现call⽅法,结合FutureTask类包装Callable对象,实现多线程
优点:有返回值,拓展性也⾼
缺点:jdk5以后才⽀持,需要重写call⽅法,结合多个类⽐如FutureTask和Thread类
- (常用)通过线程池创建线程
wait与sleep区别?
sleep()方法方法是属于Thread类中的静态方法。
wait()方法属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但依然是监控状态保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
sleep(1000)和wait(1000)的区别:
Thread.Sleep(1000) 意思是在未来的1000毫秒内本线程不参与CPU竞争,1000毫秒过去之后,这时候也许另外一个线程正在使用CPU,那么这时候操作系统是不会重新分配CPU的,直到那个线程挂起或结束,即使这个时候恰巧轮到操作系统进行CPU 分配,那么当前线程也不一定就是总优先级最高的那个,CPU还是可能被其他线程抢占去。
wait(1000)表示将锁释放1000毫秒,到时间后如果锁没有被其他线程占用,则再次得到锁,然后wait方法结束,执行后面的代码,如果锁被其他线程占用,则等待其他线程释放锁。注意,设置了**超时时间的wait方法一旦过了超时时间,并不需要其他线程执行notify也能自动解除阻塞**,但是如果没设置超时时间的wait方法必须等待其他线程执行notify。
Thread.sleep(0),是否有用?
Thread.Sleep(0)的作用,就是“触发操作系统立刻重新进行一次CPU竞争,重新计算优先级”。竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。
应用: 这也是我们在大循环里面经常会写一句Thread.sleep(0) ,因为这样就给了其他线程比如Paint线程获得CPU控制权的权力,这样界面就不会假死在那里
Thread.yield( )
Java线程中的Thread.yield( )方法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,
让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。
https://www.cnblogs.com/java-spring/p/8309931.html
synchronized join
a线程中 b.join, 等b执行完再执行
wait, notifyAll实现, 所以这个线程不要用wait和notifyAll方法