线程的新启方式有几种?
①继承Thread类
②实现Runnable接口
没有其他了,就算是实现callable接口,也是需要再包装成FutureTask,而FutureTask其实也只是实现了Runnable接口的而已。
线程状态
image.png 1、中间的运行态,分2种情况,分别是running和ready,其实就是CPU调度,如果CPU正在执行该线程,就是running,被剥夺或者等待就是ready。2、在并发编程的时候,我们可能会调用线程的wait、join、notify、notifyAll等一系列方法,进行线程间的暂停和唤醒继续执行,达到并发的效果。
3、如果在wait、sleep等方法参数中填写时间,那就是超时的限制,避免线程无限等待。
4、阻塞状态,其实就是锁的获取。
注意:进入阻塞状态的情况,只有使用的是synchronize修饰符才会进入,如果使用的是显示锁,就不会了,只会进行等待。
死锁
达成死锁的条件:
1、有多个线程(T>=2),同时有多个资源(S>=2),同时资源要小于线程的数量(S<=T)
2、各个线程获取资源的顺序不一样
3、得到某个资源后,不再释放
举个例子就是:有2个小朋友A和B,同时又有一台游戏机和一包零食,A和B都想一边玩游戏一边吃零食;但是A优先选游戏,B优先选零食;各自得到一样之后,都想得到对方的,同时还不肯把自己已获得的给对方。这样的结果就是谁也无法既吃零食又玩游戏了。
那如何打破死锁?
其实主要是针对第2、3点,因为第1点基本都是跟业务有关,很多时候我们都不能主动改变。打破第2点很简单,调整获取资源的顺序就可以了,保证全部线程申请资源的顺序都一致。
要打破第3点,就需要用Lock接口来自己实现锁了,因为Lock提供了很多方法,使得我们可以对锁进行尝试获取、释放等操作。
/**
*类说明:演示尝试拿锁解决死锁
*/
public class TryLock {
private static Lock No13 = new ReentrantLock();//第一个锁
private static Lock No14 = new ReentrantLock();//第二个锁
//先尝试拿No13 锁,再尝试拿No14锁,No14锁没拿到,连同No13 锁一起释放掉
private static void fisrtToSecond() throws InterruptedException {
String threadName = Thread.currentThread().getName();
Random r = new Random();
while(true){
if(No13.tryLock()){
System.out.println(threadName
+" get 13");
try{
if(No14.tryLock()){
try{
System.out.println(threadName
+" get 14");
System.out.println("fisrtToSecond do work------------");
break;
}finally{
No14.unlock();
}
}
}finally {
No13.unlock();
}
}
Thread.sleep(r.nextInt(3));
}
}
//先尝试拿No14锁,再尝试拿No13锁,No13锁没拿到,连同No14锁一起释放掉
private static void SecondToFisrt() throws InterruptedException {
String threadName = Thread.currentThread().getName();
Random r = new Random();
while(true){
if(No14.tryLock()){
System.out.println(threadName
+" get 14");
try{
if(No13.tryLock()){
try{
System.out.println(threadName
+" get 13");
System.out.println("SecondToFisrt do work------------");
break;
}finally{
No13.unlock();
}
}
}finally {
No14.unlock();
}
}
Thread.sleep(r.nextInt(3));
}
}
private static class TestThread extends Thread{
private String name;
public TestThread(String name) {
this.name = name;
}
public void run(){
Thread.currentThread().setName(name);
try {
SecondToFisrt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread.currentThread().setName("TestDeadLock");
TestThread testThread = new TestThread("SubTestThread");
testThread.start();
try {
fisrtToSecond();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
活锁
活锁是啥?其实就是解决了上面的2或者3的死锁问题,但是也有几率资源被锁住,或者最终获取到资源的时间被拉长。例如:
线程A、B 资源锁1、2
A得1等2->全释放->A得1等2->全释放->A得1等2
B得2等1->全释放->B得2等1->全释放->B得2等1
这样下去就会没完没了了,解决方法就是,可以等一等,所以上面的代码中,稍微加入了Thread.sleep(r.nextInt(3));使得线程获得锁的时间点错开,提高资源锁的获得率,也就避免了活锁了。
ThreadLocal
线程本地变量,让每个线程都拥有一份自己的变量副本,实现线程间的数据隔离。
image.png
结构就是上图那样:
①每个线程,都会有一个ThreadLocalMap类型的变量,threadLocals
②在ThreadLocalMap类里面,有一个Entry[]类型的变量,table
③Entry类里面,存放的是键值对,key就是ThreadLocal类型,value就是object类型。
所以,每当在线程里面调用threadLocal的set方法时,首先会创建一个threadLocalMap的对象实例和一个Entry[]的空数组对象;然后以线程为key,把我们需要存储的值放到value里面。
网友评论