java线程目录
1、线程简介
1.1 什么是线程
操作系统在运行一个程序时,会为其创建一个进程。操作系统调度的最小单元是线程,在一个进程里可以创建多个线程,这些线程都各自有各自的计数器、堆栈和局部变量等属性,而且可以访问共享的内存变量。cpu在这些线程中不断高速的切换,让使用者感觉这些线程在同时执行
1.2 线程的优先级
操作系统基本上采用时分的形式调度运行的线程,操作系统的会划分一个个时间片,线程在执行的时候,就是拿到分配的时间片,当时间片用完后,就会切换到其他线程,并等待下次时间片的分配.线程分配到了多少时间片资源,就代表需要多少CPU资源,而线程优先级就是决定线程在执行时,分配时间片的优先程度,越早分配,就越早执行完成,也就代表该线程优先级越高
在java中,可以指定给线程优先级,如果不指定,则统一为默认的优先级,有CPU来决定执行顺序
@Test
public void learnThread1() throws Exception{
for (int i = 0; i < 50; i++) {
final int h =i;
Thread threadA = new Thread(() -> {
System.out.println("线程A 执行 "+h);
});
// threadA.setPriority(Thread.MAX_PRIORITY);
Thread threadB = new Thread(() -> {
System.out.println("线程B 执行 "+h);
});
threadA.start();
threadB.start();
System.out.println();
}
Thread.sleep(1000);
}
着这里执行 50次 的结果
线程B 执行 0
线程A 执行 0
线程B 执行 26
线程A 执行 26
线程B 执行 34
线程A 执行 34
线程A 执行 48
线程B 执行 48
线程A 执行 49
线程B 执行 49
可以看到A B两个线程的执行顺序是有发送顺序的差别的.
但是如果设置优先级 则就可以保证有些线程是优先执行的.
把上边的 threadA.setPriority(Thread.MAX_PRIORITY);
放开 则是执行结果都是顺序的. 在不同的JVM或者操作系统上,线程的优先级是会存在差异的,有些操作系统甚至会忽略对线程优先级的设定. 比我使用的环境是 <Mac Os 10.15.5 JDK 1.8> 这里是不生效的 大家可以看看这篇文章的内容 是对于优先级的讲解优先级讲解文章
1.3 线程的状态
线程在运行时会发生以下几种状态变化.
状态名称 | 状态说明 |
---|---|
NEW | 初始状态,线程被创建,但是还没有调用start()方法 |
RUNNABLE | 运行状态,JAVA线程将操作系统中的就绪和运行两种状态都称为 运行中 状态 |
BLOCKED | 阻塞状态,表示线程处于阻塞,一般在竞争资源时,等待锁的时候,会进入这个状态 |
WAITING | 等待状态,表示线程进入等待状态,比如等待其他线程的通知和中断,或者说调用await()方法时,就是在等待状态 |
TIME_WAITING | 超时等待状态,该状态不同于WAITING,这个状态可以再等待一段时间后,自行返回,也就是不是一直保持等待状态,不需要其他线程的通知或者中断 |
TERMINATED | 终止状态,表示线程已经执行完毕 |
在通过对于运行的java程序,使用Jstack 命令后可以得到这样的状态说明.
如图示例代码
@Test
public void testThread(){
new Thread(()->{
while (true){
//该线程一直在进行睡眠, 这是为了验证超时等待的状态
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"TimeWaitingThread").start();
final Object wait = new Object();
new Thread(()-> {
//加锁 只保证 有个线程能拿到锁,后续调用wait的时候是 释放锁,但是等待其他线程唤醒,此时线程就进入为 等待状态
synchronized (wait){
try {
wait.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"WaitingThread").start();
final Object block = new Object();
new Thread(()->{
synchronized (block){
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"BlockThread-1").start();
new Thread(()->{
synchronized (block){
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"BlockThread-2").start();
//防止线程关闭.
while (true){
}
}
通过JPS 和jsatck看线程运行的栈信息
以下就是线程执行状态中,状态的流转图
1.4 守护线程
Daemon 线程是一种支持性线程,因为主要用作程序中后台调度以及支持性工作,如果JVM中不存在非Daemon线程的时候,JVM将退出.Daemon被称为守护线程.通过调用Thread.setDaemon(true)将线程设置为守护线程.
2、启动和终止线程
2.1 线程的构建和启动
线程的创建方式有两种
- 实现Runable接口
- 继承Thread类
下边来看看Thread类的init方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
//当前线程为该线程的父线程
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
//设置为父线程的对应属性
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
//复制 父线程 的信息
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
线程的启动
线程的启动方式是调用start() 方法进行启动. 当前线程
同步告知JVM,只要线程规划器空闲,应立即调用start()方法的线程
2.2 线程中断
线程的中断是调用thread.interrupt(); 方法进行中断.
- 中断可以理解为线程的一个标识属性,标识一个运行中的线程是否被其他线程进行了中断操作.好比说其他线程对该线程打了一个招呼,其他线程通过调用该线程的thread.interrupt(); 来进行对其中断操作
线程通过调用threadA.isInterrupted(); 来判断是否被中断,也可以进行调用静态方法Thread.interrupted() 进行复位标识,如果线程已经终止,即使该线程被中断过,该线程的isInterrupted() 也会返回false.
javaAPI中 许多声明抛出InterruptedException 的方法,这些方法在抛出InterruptedException 之前,JVM会先将该线程的中断标识位清除,然后抛出InterruptedException 此时 isInterrupted() 方法返回false
@Test
public void testInterrupte() {
Thread thread = new Thread(() -> {
while (true) {
try {
/**
* Sleep方法的注释说明. 会判断 是否中断 如果中断则抛出异常
* * @throws InterruptedException
* * if any thread has interrupted the current thread. The
* * <i>interrupted status</i> of the current thread is
* * cleared when this exception is thrown.
* *
*public static native void sleep ( long millis) throws InterruptedException;
*/
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Thread-1");
thread.setDaemon(true);
Thread thread2 = new Thread(() -> {
while (true) {
// System.out.println("----");
}
}, "thread-2");
thread2.setDaemon(true);
thread.start();;
thread2.start();
//等待 两个线程可以都获取到时间片运行
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
thread2.interrupt();
System.out.println("thread-1 interrupt = " + thread.isInterrupted());
System.out.println("thread-2 interrupt = " + thread2.isInterrupted());
}
运行结果:
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at chenyi.demo.javaThread.LearnJavaTread.lambda$testInterrupte$6(LearnJavaTread.java:104)
at java.lang.Thread.run(Thread.java:748)
thread-1 interrupt = false
thread-2 interrupt = true
Disconnected from the target VM, address: '127.0.0.1:61190', transport: 'socket'
抛出的异常是因为,在sleep的时候会判断线程是否是interrupted
2.3 线程的暂停,恢复,和 停止
直接看代码:
@Test
public void testThreadBc() throws InterruptedException {
Thread thread = new Thread(() -> {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我在生产---");
}
}, "Thread-C");
thread.setDaemon(true);
thread.start();
Thread.sleep(5000);
System.out.println("线程暂停 start");
//方法暂停
thread.suspend();
System.out.println("线程暂停 end");
Thread.sleep(5000);
//线程方法恢复 执行
thread.resume();
System.out.println("线程恢复 end");
Thread.sleep(5000);
//线程方法结束
thread.stop();
System.out.println("线程结束 end");
}
执行结果:
我在生产---
我在生产---
我在生产---
我在生产---
线程暂停 start
线程暂停 end
我在生产---
线程恢复 end
我在生产---
我在生产---
我在生产---
我在生产---
线程结束 end
线程的暂定 恢复使用 以及结束都是可以正常的生效.
注意:
但是这些方法被标注为过时,是因为在调用这些方法的时候,线程不会释放资源,而是一直持有资源,这样容易引发死锁问题.同样,stop方法在终结一个线程时,无法保证该线程持有的资源可以正常释放.只是给线程线程资源释放工作的机会.
2.4 线程的终止
上边说,线程的stop方法只是给线程资源释放的机会,不能保证资源一定被释放,那么线程该如何安全的终止呢?
2.2提到 中断状态是线程的标识位,而中断操作是一种简便的线程间交互方式,这种凡是最适合用来取消或者停止任务.
2.5 Thread.join()方法的使用
如果一个线程A执行了thread.join() 方法,表示的是,当前线程A等待thread线程终止后,才会从thread.join() 方法返回线程Thread提供了join() join(long minlls) 和 join(long minlls,int nanos)
下边我们具体展示一下是怎么使用的
@Test
public void testJoin() throws InterruptedException {
final Thread threadA = new Thread(() -> {
System.out.println(this.getClass().getName() +"进入线程");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("I'm A");
System.out.println(this.getClass().getName() +"线程执行完毕");
}, "thread-a");
final Thread threadB = new Thread(() -> {
try {
//等待B 表示是一直等待 和 join方法是一样的
threadA.join(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getClass().getName() +"进入线程");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("I'm B");
System.out.println(this.getClass().getName() +"线程执行完毕");
}, "thread-b");
final Thread threadC = new Thread(() -> {
try {
//等待B 1000毫秒
threadB.join(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getClass().getName() +"进入线程");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("I'm C");
System.out.println(this.getClass().getName() +"线程执行完毕");
}, "thread-c");
final Thread threadD = new Thread(() -> {
try {
threadC.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getClass().getName() +"进入线程");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("I'm D");
System.out.println(this.getClass().getName() +"线程执行完毕");
}, "thread-d");
threadA.start();
threadB.start();
threadC.start();
threadD.start();
Thread.sleep(20000);
}
执行结果:
Connected to the target VM, address: '127.0.0.1:61442', transport: 'socket'
chenyi.demo.javaThread.LearnJavaTread进入线程
chenyi.demo.javaThread.LearnJavaTread进入线程
I'm A
chenyi.demo.javaThread.LearnJavaTread线程执行完毕
chenyi.demo.javaThread.LearnJavaTread进入线程
I'm C
chenyi.demo.javaThread.LearnJavaTread线程执行完毕
chenyi.demo.javaThread.LearnJavaTread进入线程
I'm B
chenyi.demo.javaThread.LearnJavaTread线程执行完毕
I'm D
chenyi.demo.javaThread.LearnJavaTread线程执行完毕
Disconnected from the target VM, address: '127.0.0.1:61442', transport: 'socket'
为什么顺序不是ABCD?
原因是 B线程一直等待A线程,也就是说需要2s后,才会到B线程执行,但是C线程等待B线程是有超时等待的,C只等待了1s,所以C在等待超时后,就先执行了. 然后C开始执行,这个时候B等A执行完毕后,开始执行. 但是D等待C执行完成后,就自动执行了.
书籍参考: <<Java并发编程的艺术>>