java线程详解
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了java线程详解,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含7679字,纯文字阅读大概需要11分钟。
内容图文
线程状态
在java.lang.Thread.State这个枚举类中定义了6种线程,如下图所示
- New:尚未启动的线程的线程状态。
- Runnable:可运行线程的线程状态,等待CPU调度(分两种情况,正在运行,另一种等待cpu来执行)。
- Blocked:线程阻塞等待监视器锁定的线程状态。处于synchronized同步代码块或方法中被阻塞。
- Waiting:等待线程的线程状态。下列不带超时的方式:Object.wait、Thread.join、LockSupport.park
- Timed Waiting:具有指定等待时间的等待线程的线程状态。下列带超时的方式:Thread.sleep、Object.wait、Thread.join、LockSupport.parkNanos、LockSupport.parkUntil
- Terminated:终止线程的线程状态。线程正常完成执行或者出现异常。
各状态之间的转化如下图所示:
线程终止
在Thread类中,提供了两个方法进行,对线程进行终止操作,分别是stop()和interrupt()。
- 不正确的线程中止stop:stop中止线程,并且清除监控器锁的信息,但是可能导致线程安全问题,JDK不建议用,已被标记位@Deprecated。
- 正确的线程中止interrupt:如果目标线程在调用Object class的wait()、wait(long)或wait(long,int)方法、join()、join(long,int)或sleep(long,int)方法时被阻塞,那么Interrupt会生效,该线程的中断状态将被清除,抛出InterruptedException异常;如果目标线程是被i/o或者NIO中的Channel所阻塞,同样,I/O操作会被中断或者返回特殊异常值。达到终止线程的目的;如果以上条件都不满足,则会设置此线程的中断状态。
- 正确的线程中止标志位:通过自己代码逻辑中,增加一个判断,用来控制线程执行的中止。
public class Demo1 extends Thread {
public volatile static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
while (flag) { // 判断是否运行
System.out.println("运行中");
Thread.sleep(1000L);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 3秒之后,将状态标志改为False,代表不继续运行
Thread.sleep(3000L);
flag = false;
System.out.println("程序运行结束");
}
}
线程通信
在java中,提供了3对线程通信的API分别是:suspend/resume,wait/notify和park/unpark。
suspend/resume已被标记为弃用,为什么会被弃用呢,就是因为当线程被suspend的时候,如果获得锁资源,不会被释放,容易造成死锁。并且suspend一定要比resume先执行,如果resume先执行,也会导致死锁。
/** 包子店 */
public static Object baozidian = null;
/** 正常的suspend/resume */
public void suspendResumeTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
Thread.currentThread().suspend();
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
consumerThread.resume();
System.out.println("3、通知消费者");
}
/** 死锁的suspend/resume。 suspend并不会像wait一样释放锁,故此容易写出死锁代码 */
public void suspendResumeDeadLockTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
Thread.currentThread().suspend();
}
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
consumerThread.resume();
}
System.out.println("3、通知消费者");
}
/** 导致程序永久挂起的suspend/resume */
public void suspendResumeDeadLockTest2() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) {
System.out.println("1、没包子,进入等待");
try { // 为这个线程加上一点延时
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里的挂起执行在resume后面
Thread.currentThread().suspend();
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
consumerThread.resume();
System.out.println("3、通知消费者");
consumerThread.join();
}
wait/notify机制:wait/notify这些方法只能由同一对象锁的持有者线程调用,也就是写在同步块里面,否则会抛出IllegalMonitorStateException异常;虽然wait方法可以释放锁资源,但是对执行顺序还是有要求,如果notify被调用之后,才开始wait方法的调用,线程会永远处于WAITING状态。
- wait:方法导致当前线程等待,加入到对象的等待集合中,并且放弃当前持有额对象锁。
- notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。
/** 正常的wait/notify */
public void waitNotifyTest() throws Exception {
// 启动线程
new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
synchronized (this) {
try {
System.out.println("1、进入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、买到包子,回家");
}).start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消费者");
}
}
/** 会导致程序永久等待的wait/notify */
public void waitNotifyDeadLockTest() throws Exception {
// 启动线程
new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
try {
Thread.sleep(5000L);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (this) {
try {
System.out.println("1、进入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、买到包子,回家");
}).start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消费者");
}
}
park/unpark机制:线程调用park则等待“许可”,unpark方法为指定线程提供“许可(permit)”。不要求park和unpark方法的调用顺序,即使先调用unpark,在调用park方法,线程也可以直接运行,并且这种效果不会叠加。例如先多次调用park方法,第一次会拿到“许可”直接运行,后续调用会进入等待。但是park后,不会释放锁资源,会造成死锁。
/** 正常的park/unpark */
public void parkUnparkTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
LockSupport.park();
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
LockSupport.unpark(consumerThread);
System.out.println("3、通知消费者");
}
/** 死锁的park/unpark */
public void parkUnparkDeadLockTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
LockSupport.park();
}
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println("3、通知消费者");
}
警告!在代码中不应该使用if语句来判断,是否进入等待状态,这种做法是错误的!官方建议应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误报警和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。
伪唤醒:是指线程并非因为notify、notifyall、unpark等api调用唤醒,是更底层原因导致的。
线程封闭
因为多线程在访问共享可变数据时,涉及到线程间数据同步的问题。但并不是所有时候,都要用到共享数据,所以线程封闭概念就提出来了;数据都被封闭在各自的线程之中,就不需要同步了,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭。线程封闭的具体体现有:ThreadLocal、局部变量。
ThreadLoacl:ThreadLocal是java里一种特殊的变量。它是一个线程级别变量,每一个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,在并发模式下是绝对安全的变量。
- 用法:ThreadLocal var = new ThreadLocal();会自动在每一个线程上创建一个T的副本,副本之间彼此独立,互不影响。可以用ThreadLocal存储一些参数,以便在线程中多个方法中使用,用来代替方法传参的做法。
- 理解:可以将ThreadLocal理解为JVM维护了一个map<Thread,T>,每个线程要用这个T的时候,用当前的线程去Map里面取。仅作为一个理解概念。
栈封闭:局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。
内容总结
以上是互联网集市为您收集整理的java线程详解全部内容,希望文章能够帮你解决java线程详解所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。