01 线程的创建与销毁
- 创建运行
一般情况,线程的创建有两种方式
1.实现Runnable接口,放入Thread类中执行
2.继承Thread类,覆写run方法。
启动:start();
注意点:thread对象虽然可以直接调用run方法,但是,直接调用run方法不会启动新线程,只是在当前线程执行run方法而已,只有使用start方法,才会启动新线程!
- 停止和销毁
1.自然停止,当run方法执行完成后,线程会自动停止。
2.stop方法强制停止。
3.interrupt中断线程。
自然停止的线程无须关系,因为执行结束就自动停止了。
然而对可以无限时间执行的线程,需要注意:
stop方法不能手动直接调用。
stop方法会直接将线程置空,会抛出ThreadDeath
错误,无法预料线程对数据的影响。
interrupt方法对处于阻塞中的线程,无法处理
线程执行sleep或wait方法时,或是读取文件,执行interrupt会抛出异常,因为中断线程导致线程无法完成正在等待的任务。
提倡方法,run方法中使用isInterrupted()作为终止条件
public void run() {
while(!isInterrupted()){
正常逻辑
}
}
public synchronized void stopTheThread(){
isInterrupted = false;
}
这样调用interrupt方法时,逻辑会在执行完成后停止线程。
ps: interrupt不能中断正在进行的数据写入。
02 JVM中线程的内存分布工作方式
线程中的异常处理
所有线程都不允许抛出未捕获的checked exception,如果线程任务中有checked exception,需要自己catch住,并处理掉,如果是unchecked exception出现的话,这个线程就挂了,当然,不会直接影响其他线程,导致其他线程或宿主宕掉。
如果想处理unchecked exception,可以使用public void uncaughtException(Thread t, Throwable e) 方法处理,但是这个方法依旧在Thread内部。而且若是在线程池中,这个方法是不会被调用的。
╮(╯╰)╭
03 死锁与解决办法
什么是线程死锁,看一段代码
String lock1 = "l1";
String lock2 = "l2";
Thread t1 = new Thread(()->{
synchronized (lock1){
System.out.println("t1:"+lock1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println("t1:"+lock2);
}
}
});
Thread t2 = new Thread(()->{
synchronized (lock2){
System.out.println("t2:"+lock1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1){
System.out.println("t2:"+lock1);
}
}
});
t1.start();
t2.start();
这段代码运行后,无法退出,因为t1进入sleep后,t2占用了lock2,然后t2 没有释放lock2 就进入了sleep,切换回t1,这时候lock2被占用,无法进一步执行,等待线程时长结束后,t1没有释放lock1 导致t2也不能进入下一步执行,这就形成了死锁。
避免死锁可以从下面几个角度去修改代码,
- 破坏互斥条件:不要出现一个资源只能被一个进程占用,直到该进程释放资源
- 取消请求和保持条件:当一个线程等不到请求的资源时,不要阻塞。
- 剥夺条件:尽量不要让一个变量只能被一个线程独占,
- 打破循环等待:当发生死锁时,所等待的进程必定会形成一个环路,尽量不要让线程相互占用对方的独立资源,从而导致循环阻塞。
这四个满足一个就可以避免死锁的发生。
转载请注明出处:https://micorochio.github.io/2017/12/10/learning-thread-note-01/
网友评论