方法一,直接使用Thread
public class TestThread {
public static void main(String[] args) {
Thread thread = new Thread("t1") {
@Override
public void run() {
System.out.println("test thread " + Thread.currentThread().getName());
}
};
thread.start();
System.out.println("main begin");
}
}
执行结果,可以看到主线程先执行,子线程后执行。
main begin
test thread t1
方法二,使用Runnable 配合Thread
该方法较为灵活,推荐使用Runnable的方式创建线程。
@Test
public void testRunnable() throws InterruptedException {
// 使用lambda表达生成一个Runnable对象
Runnable runnalbe = () -> {
System.out.println("test Runnalbe");
};
new Thread(runnalbe).start();
System.out.println("main exe");
Thread.sleep(100);
}
执行结果
main exe
test Runnalbe
原理之Thread与Runnable的关系
- 方法1是把线程和任务合并在一起,方法2是把线程(thread)和任务(runnable)分开。
- 用Runnable更容易与线程池等高级API配合。
- 用Runnable让任务类脱落了Thread继承体系,更灵活。
方法三,FutureTask配合Thread
FutureTask能够接收Callable类型的参数,用来处理有返回结果的情况。
FutureTask实际上就是一个Runnable,因此可以作为Thread的参数。
@Test
public void testFutureTask() throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(
// Callable接口的Lambda实现
() -> {
System.out.println("future task test");
return 100;
}
);
new Thread(futureTask, "t2").start();
// 阻塞等待返回值
System.out.println(futureTask.get());
}
执行结果可以看到,先执行子线程,在输出子线程的返回值,因为主线程要获取子线程的值,所以会等待子线程执行完毕再执行。
future task test
100
线程查看
- linux下,通过top命令可以动态的查看进程信息。
- top -H -p processId 可以动态的查看某进程下的线程信息。
- 执行top命令
- 查询某进程下的线程信息
[root@localhost ~]# jps
2527 Jps
[root@localhost ~]# top -H -p 4262
3. 通过jstack查询执行jstack时刻的某进程下的所有线程快照信息
线程上下文切换
线程上下文切换原因
因为以下一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码。
- 线程的cpu时间片用完。
- 垃圾回收。
- 有更高优先级的线程需要执行。
- 线程自己调用了sleep、yield、wait、join、park、synchronized、lock等方法。
线程上下文切换需要做什么
保存当前线程状态,并恢复另一线程状态。
线程状态包括哪些:
- 当Context Switch发生时,需要由操作系统保持当前线程的状态,并恢复另一线程的状态,Java中对应的概念就是程序计数器(program counter register),它的作用是记住下一条Jvm执行的执行地址,是线程私有的。
- 除了程序计数器,还有虚拟机栈中每个栈帧(方法)的信息(如果局部变量、操作数栈、返回地址等)。
- 频繁的上下文切换会影响性能。
比如main线程的时间片用完了,需要保存main线程的程序计数器的下一次执行地址和栈中的每个栈帧信息。
线程常见方法
方法名 | 功能说明 | 注意 |
---|---|---|
start() | 启动一个新线程,在新的线程运行run方法中的代码 | start方法只是让线程进入就绪,里面代码不一定立刻运行(CPU的时间片还没有分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException |
run() | 新线程启动后会调用的方法 | 如果在构造Thread对象时传递了Runnable参数,则线程启动后会调用Runnable中的run方法,否则默认不执行任何操作。但可以创建Thread的子类对象,来覆盖默认行为。 |
join() | 等待调用join()方法的线程运行结果 | 比如main方法中有t1.join()代码,那么mian线程需要等待t1线程执行完,才能继续执行 |
join(long n) | 等待调用join(long b)方法的线程运行结束,最多等待n毫秒 | |
getId() | 获取线程长整型的id | id唯一 |
getName() | 获取线程名称 | |
setName(String) | 修改线程名称 | |
getPriority() | 获取线程优先级 | |
setPriority(int) | 修改线程优先级 | java中规定线程优先级是1-10的整数,较大的优先级能提高该线程被CPU调度的机率 |
getState() | 获取线程状态 | java中线程状态是用6个enum表示,分别是:NEW,RUNNABLE,BLOCKED,WATING,TIMED_WAITING,TERMINATED |
isInterrupted() | 判断是否被打断 | 不会清除 打断标记 |
isAlive() | 线程是否存活(还没有运行完毕) | |
interrupt() | 打断线程 | 如果被打断线程正在sleep,wait,join会导致被打断的线程抛出InterruptedException,并清除打断标记;如果打断的正在运行的线程,则会设置 打断标记,park的线程被打断,也会设置 打断标记 |
interrupted() | 静态方法,判断当前线程是否被打断 | 会清除 打断标记 |
currentThread() | 静态方法,获取当前正在执行的线程 | |
sleep(long n) | 静态方法,让当前执行的线程休眠n毫秒,休眠时让出cpu的时间片给其他线程,但是不会是否锁 | |
yield() | 提示线程调度器让出当前线程对CPU的使用 | 主要是为了测试和调试 |
线程方法详解
join方法
以调用角度来讲,如果:
- 需要等待结果返回,才能继续运行就是同步。
- 不需要等待结果返回,就能继续运行就是异步。
interrupt方法
打断sleep,wait,join的线程
阻塞状态的线程,抛出InterruptedException异常并清除打断标记,因此打断标记为false。
示例:
@Test
public void testInterrupt() throws InterruptedException {
Thread thread = new Thread(() -> {
log.debug("t1 begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
log.debug("t1 interrupted");
e.printStackTrace();
}
}, "t1");
thread.start();
Thread.sleep(1000);
thread.interrupt();
log.debug("main thread end, t1 interrupt status:{}", thread.isInterrupted());
}
执行结果:
21:46:55 [t1] juc.test - t1 begin
21:46:56 [t1] juc.test - t1 interrupted
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.study.concurrentprogramming.ConcurrentprogrammingApplicationTests.lambda$testInterrupt$0(ConcurrentprogrammingApplicationTests.java:21)
at java.lang.Thread.run(Thread.java:748)
21:46:56 [main] juc.test - main thread end, t1 interrupt status:false
打断正常运行的线程
线程的打断标记不会被清除。
执行interrupt()方法只是告诉线程被打断了,设置打断标记,但是线程自己是否结束,需要线程自己决策。
示例:
@Test
public void testInterruptNormal() throws InterruptedException {
Thread thread = new Thread(() -> {
log.debug("t1 begin");
while (true) {
if(Thread.currentThread().isInterrupted()) {
log.debug("t1 interrupted");
break;
}
}
}, "t1");
thread.start();
Thread.sleep(1000);
thread.interrupt();
log.debug("main thread end, t1 interrupt status:{}", thread.isInterrupted());
}
执行结果:
21:54:57 [t1] juc.test - t1 begin
21:54:58 [t1] juc.test - t1 interrupted
21:54:58 [main] juc.test - main thread end, t1 interrupt status:true
两阶段终止模式
two phase termination
在一个线程T1中如何“优雅”终止线程T2? 这里的【优雅】指的是给T2一个料理后事的机会。
错误思路
- 使用线程对象的stop()方法停止线程。
- stop方法会真正的杀死线程,如果这是线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其他线程将永远没有机会获取锁。
- 使用System.exit(int)方法停止线程。
- 目的仅是停止一个线程,但这种做法会让整个程序都停止。
两阶段终止设计示例
一个后台监控线程。
代码示例:
打断park线程
park线程,是指执行了LockSupport.park()方法导致暂停的线程。
注意如果park线程的打断标记为真,则park方法无法使线程暂停,可以使用Thread.interrupted()方法读取打断状态。
代码示例:
@Test
public void testPark() throws InterruptedException {
Thread thread = new Thread(() -> {
log.debug("t1 park");
LockSupport.park();
log.debug("t1 unPark");
log.debug("t1 interrupt status:{}", Thread.interrupted());
}, "t1");
thread.start();
sleep(2000);
thread.interrupt();
}
执行结果:
22:40:39.575 [t1] juc.test - t1 park
22:40:41.583 [t1] juc.test - t1 unPark
22:40:41.583 [t1] juc.test - t1 interrupt status:true
主线程与守护线程
默认情况下,java进程需要等待所以线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其他非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
注意:
- 垃圾回收器线程就是一种守护线程。
- Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat结束到shutdown命令后,不会等待这两个守护线程处理完当前请求,就是直接结束了。
线程状态
从操作系统层面来描述有5种状态
- 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联。
- 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由CPU调度执行。
- 【运行状态】指获取了CPU时间片运行中的状态。
- 当CPU时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换。
- 【阻塞状态】
- 如果调用了阻塞API,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入【阻塞状态】。
- 等BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】。
- 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要他们一直不唤醒,调度器就一直不好考虑调度他们。
- 【终止状态】表示线程已经执行完毕,生命周期已经结束,不好再转换为其他状态。
从Java API层面来描述6种状态
根据Thread.State枚举,分为六种状态
- New线程刚被创建,但是还没有调用start()方法。
- RUNNABLE当调用了start()方法之后,注意,Java API层面的RUNNABLE状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【阻塞状态】(由于BIO导致的线程阻塞,在Java里无法区分,仍然认为是可运行的)。
- BLOCKED(阻塞),WAITING(无限等待),TIMED_WAITING(超时等待)都是Java API层面对【阻塞状态】的细分,都无法被调度分配时间片执行,后面会在状态转换已经详述;BLOCKED(获取不到锁,线程被阻塞),WAITING(比如有Join的线程加入,当前线程处于该状态),TIMED_WAITING(当前线程执行sleep时处于该状态)。
- TERMINATED 当线程代码运行结束。
线程的六种状态演示
代码示例
@Test
public void testThreadState() throws InterruptedException {
Boolean lock = false;
// 创建线程,但是没有与操作系统线程产生联系,处于NEW 状态
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("thread 1");
}
};
t1.setName("t1");
// 线程启动运行,处于RUNNABLE状态
Thread t2 = new Thread(() -> {
synchronized (lock) {
while (true) {
}
}
}, "t2");
t2.start();
// 线程获取不到锁,处于BLOCKED状态
Thread t3 = new Thread(() -> {
synchronized (lock) {
System.out.println("t3 start");
}
}, "t3");
t3.start();
// 线程执行中加入了其他线程,当前线程处于WAITING状态
Thread t4 = new Thread(() -> {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t4");
t4.start();
// 线程sleep,线程处于TIMED_WAITING状态
Thread t5 = new Thread(() -> {
try {
sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t5");
t5.start();
// 线程执行结束,线程处于TERMINATED
Thread t6 = new Thread(() -> {
System.out.println("t6 end");
}, "t6");
t6.start();
Thread.sleep(1000);
log.debug("t1 state {}", t1.getState());
log.debug("t2 state {}", t2.getState());
log.debug("t3 state {}", t3.getState());
log.debug("t4 state {}", t4.getState());
log.debug("t5 state {}", t5.getState());
log.debug("t6 state {}", t6.getState());
}
执行结果:
t6 end
10:56:03.544 [main] juc.test - t1 state NEW
10:56:03.554 [main] juc.test - t2 state RUNNABLE
10:56:03.554 [main] juc.test - t3 state BLOCKED
10:56:03.554 [main] juc.test - t4 state WAITING
10:56:03.554 [main] juc.test - t5 state TIMED_WAITING
10:56:03.554 [main] juc.test - t6 state TERMINATED
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/100304.html