1.死锁
线程同步的时候会对对象监视器所监视的操作上锁,也就是只有当前线程能够进来,其他线程是进不来的,除非你拿到了监视器。
死锁:当线程A进入到了X对象的监视器内,线程B进入到了Y对象的监视器内,X对象的监视器内部调用了Y对象监视器内部的操作,所以线程A想正常终结的话,必须等线程B交出监视器(终结或挂起(挂起不考虑)),然后以可重入锁的方式进入Y对象监视器的内部(可重入锁的概念,后续博文会解释),执行完相关操作,然后终结;而巧的是Y对象的监视器调用了X对象监视器内部的操作,B线程若是想正常终结的话,必须等A线程交出监视器,这样互相等待的状态称为死锁。
死锁案例:
public class DeadLockTest {
public static void main(String[] args) {
/*
* 所谓死锁,就是线程A进入到X对象的监视器内部,线程B进入到了Y对象的监视器内部,而A在X监视器内部需要调用B的监视器内部操作才能结束,所以就调用了,然而,得等B交出监视器(B终结或者挂起(挂起除外))
* 而巧的是B要想终结的话,得执行A监视器内部的操作才能执行,这样就造成了互相等待,而且不会有解,除非有一个能够不以终结的方式交出监视器
*/
Task1 t1 = new Task1();
Task2 t2 = new Task2();
new Thread(()->{
t1.task1(t2);
}).start();
new Thread(()->{
t2.task1(t1);
}).start();
// 运行结果:
// Task1...task1
// Task2...task1
// 正在运行..(必须强行终止否则没有头啊)
}
}
class Task1{
public synchronized void task1(Task2 task2){
System.out.println("Task1...task1");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
task2.task2();
}
public synchronized void task2(){
System.out.println("Task1...task2");
}
}
class Task2{
public synchronized void task1(Task1 task1){
System.out.println("Task2...task1");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
task1.task2();
}
public synchronized void task2(){
System.out.println("Task2...task2");
}
}
2.线程状态间转化
线程总共有5个状态:被创建后还没有被执行状态、执行状态、阻塞状态(等待,不交出锁)、等待状态、终结态
相互之间的转化图如下:
调用sleep方法后处于等待状态,但是不交出监视器持有;调用wait方法后也是处于等待状态,但是交出监视器持有。
3.线程组
线程组为管理一组线程提供了一种便利的方式,可以把一组线程当成一个单位进行管理。对于希望对一组线程进行操作(挂起、恢复等)是很便利的。
线程组类:ThreadGroup(String groupName) ThreadGroup(ThreadGroup parentGp,String groupName)
对于如何设置线程所属的线程组,以及如何获取线程组中的线程由下文的代码实例说明。
对于线程组的理解,可以从Java中线程的组织关系下手:
1.Java中线程是以树的结构进行管理的,树的根是主线程,由主线程创建的线程是根的孩子,由此递归
2.这颗树上的线程都是活着的线程:没有终结也没有挂起
3.线程组在这个树中是什么呢?线程组是非叶子节点。如果没有加入线程组的话,所有的子线程都是根(主线程)的孩子,加了线程组,那所有的线程(除主线程)都是叶子节点,从树的不同非叶子节点(线程组)总是能获取这个节点的所有子孙节点(子线程),说明代码如下:
实例代码如下:
public class ThreadGroupTest {
public static void main(String[] args) {
/*
* 对于任何一个程序,首先有一个主线程,这个主线程是程序主函数(入口函数)启动的时候启动的
* 由主线程M开的线程A之间的关系是:M和A位于同一个线程组(即使在开新线程的时候不指明线程组的话),M是A的父线程
* 同样,如果A线程再开线程,那开的线程就是A线程的子线程
* 可用的三个构造器分别为:
* Thread(ThreadGroup groupOb,Runnable threadOb):指明线程组和Runnable实现
* Thread(ThreadGroup groupOb,Runnable threadOb,String threadName):指明线程租和Runnable实现和线程名称
* Thread(ThreadGroup groupOb,String threadName):指明线程组和线程名称,通常在Thread子类的构造器中调用super方法时使用这个
* 引入线程组的目的是为了批管理线程
*/
//t1和主线程位于同一线程组
Thread t1 = new Thread(()->{
//t1_1和主线程位于同一线程组
Thread t1_1 = new Thread(()->{
//加上死循环是为了保证这个线程一直在这个线程组中活下去,已经终结的线程是不能通过线程组获取的
while(true);
});
t1_1.start();
while(true);
});
t1.start();
//t2属于线程组X
Thread t2 = new Thread(new ThreadGroup("X"),()->{
Thread t2_2 = new Thread(new ThreadGroup("Y"),()->{
while(true);
});
t2_2.start();
while(true);
});
t2.start();
//获取这两个线程租
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
//获取展示这两个线程组中都有哪些线程
tg1.list();
tg2.list();
//分别获取到每个线程组中的每个线程
Thread[] threadarray1 = new Thread[tg1.activeCount()];
Thread[] threadarray2 = new Thread[tg2.activeCount()];
tg1.enumerate(threadarray1);
tg2.enumerate(threadarray2);
//操作已经get到的线程组中的线程,这里仅仅是打印出来,实际就是这样进行批处理的哦
System.out.println("Threads in the main Thread Group");
for(Thread t:threadarray1){
System.out.println(t);
}
System.out.println("Threads in the X Thread Group");
for(Thread t:threadarray2){
System.out.println(t);
}
// 运行结果(带#表示不是运行结果,是后加的注释):
// #树结构:
// java.lang.ThreadGroup[name=main,maxpri=10] #从根节点获取子线程
// Thread[main,5,main]
// Thread[Thread-0,5,main]
// java.lang.ThreadGroup[name=X,maxpri=10]
// Thread[Thread-1,5,X]
// java.lang.ThreadGroup[name=Y,maxpri=10]
// Thread[Thread-3,5,Y]
// java.lang.ThreadGroup[name=X,maxpri=10] #从X线程组获取子线程
// Thread[Thread-1,5,X]
// java.lang.ThreadGroup[name=Y,maxpri=10]
// Thread[Thread-3,5,Y]
// #数组表示:
// Threads in the main Thread Group
// Thread[main,5,main]
// Thread[Thread-0,5,main]
// Thread[Thread-2,5,main]
// Thread[Thread-1,5,X]
// Thread[Thread-3,5,Y]
// Threads in the X Thread Group
// Thread[Thread-1,5,X]
// Thread[Thread-3,5,Y]
}
}