多线程之Thread
目录
一、多线程
1、实现线程的方式
1.1、实现Runnable接口
1.2、继承Thread类
1.3、实现Callable接口
1.4、对比
2、线程的生命周期
2.1、多线程状态源码
3、为什么不能多次调用start()方法
一、多线程
1、实现线程的方式
1.1、实现Runnable接口
package com.ceam.thread;
/**
* @author CeaM
* 2022/08/30 20:12
**/
public class Task implements Runnable {
@Override
public void run() {
//TODO 在此写线程中执行的业务逻辑
}
}
1.2、继承Thread类
package com.ceam.thread;
/**
* @author CeaM
* 2022/08/30 20:00
**/
public class ThreadTest extends Thread {
@Override
public void run() {
}
}
1.3、实现Callable接口
package com.ceam.thread.callable;
import java.util.concurrent.Callable;
/**
* @author CeaM
* 2022/08/30 20:08
**/
public class CallableTest implements Callable<String> {
@Override
public String call() throws Exception {
//TODO 在此写在线程中执行的业务逻辑
return null;
}
}
1.4、对比
1、采用实现 Runnable、Callable 接口的方式创建多线程。
优势是:
线程类只是实现了 Runnable 接口或 Callable 接口, 还可以继承其他类。
在这种方式下, 多个线程可以共享同一个 target 对象, 所以非常适合多个相同线程来处理同一份资源的情况, 从而可以将 CPU、代码和数据分开, 形成清晰的模型, 较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用 Thread.currentThread() 方法。
2、使用继承 Thread 类的方式创建多线程
优势是:
编写简单, 如果需要访问当前线程, 则无需使用 Thread.currentThread()方法, 直接使用 this 即可获得当前线程。
劣势是:
线程类已经继承了 Thread 类, 所以不能再继承其他父类。
3、Runnable 和 Callable 的区别
1、Callable 规定( 重写)的方法是 call(),Runnable 规定( 重写)的方法是 run()。2、Callable 的任务执行后可返回值, 而 Runnable 的任务是不能返回值的。
3、Call 方法可以抛出异常, run 方法不可以。
4、运行 Callable 任务可以拿到一个 Future 对象, 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过 Future 对象可以了解任务执行情况, 可取消任务的执行, 还可获取执行结果。
2、线程的生命周期
- NEW:初始状态,线程被构建即new,但是还没有调用start()方法。
- RUNNABLE:可运行状态,可运行状态可以包括:运行中状态和就绪状态。处于就绪状态的线程, 只是说明此线程已经做好了准备, 随时等待 CPU 调度执行, 并不是说执行了 t.start()此线程立即就会执行,所以会看到线程一般情况下是无序执行的。当 CPU 开始调度处于就绪状态的线程时, 此时线程才得以真正执行, 即进入到运行状态。注: 就 绪状态是进入到运行状态的唯一入口, 也就是说, 线程要想进入运行状态执行, 首先必须处于就绪状态中;
- BLOCKED:阻塞状态,处于运行状态中的线程由于某种原因,暂时放弃对 CPU 的使用权, 停止执行, 此时进入阻塞状态,需要等待其他线程释放锁或者等待进入 synchronized 直到其进入到就绪状态, 才 有机会再次被 CPU 调用以进入到运行状态。
- WAITING:表示等待状态,处于该状态的线程需要等待其他线程对其进行通知或中断等操作,进 而进入下一个状态。
- TIME_WAITING:超时等待状态。可以在一定的时间自行返回。
- TERMINATED:终止状态,当前线程执行完毕。
根据阻塞产生的原因不同, 阻塞状态又可以分为三种:
1、等待阻塞:运行状态中的线程执行 wait()方法,使本线程进入到等待阻塞状态;
2、同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用), 它会进入同步阻塞状态;
3、其他阻塞:通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时, 线程重新转入就绪状态。
2.1、多线程状态源码
public enum State {
/**
* 线程还没有启动
*/
NEW,
/**
* 可运行线程
*/
RUNNABLE,
/**
* 阻塞的,可能是在等待进入同步块/方法时被阻塞的
* WAITING 不同在于, BLOCKED 是还没有进入同步块/方法时被阻塞,WAITING 是已经进去到获取同步块的过程中了,但却获取不到锁
*/
BLOCKED,
/**
* 等待,遇到 Object#wait()、Thread.join、LockSupport#park() 这些方法时,线程就会等待
* 等待另外一个线程执行特定的操作
* 一个线程 Object.wait() 后,需要等待另外一个线程执行同一个 Object 的 notify()
* 或者线程执行 thread1.join(),等待 thread1 来打断
*/
WAITING,
TIMED_WAITING,
/**
* 终止线程
*/
TERMINATED;
}
注意:
// The VM can handle all thread states
// Vm 可以处理所有的线程状态
3、为什么不能多次调用start()方法
/**
* 该方法可以创建一个新的线程出来
* 启动线程,线程状态从 NEW 进入 RUNNABLE
*
* @throws IllegalThreadStateException 若线程已经启动
* @see #run()
* @see #stop()
*/
public synchronized void start() {
// 如果已经初始化,抛异常
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 将当前线程加入到所在的线程组,记录为活跃线程
group.add(this);
// started 是个标识符,我们在初始化一些东西的时候,经常这么写
boolean started = false;
try {
// 这里会创建一个新的线程,执行完成之后,新的线程进入Runnable状态
start0();
// 这里执行的还是主线程
started = true;
} finally {
try {
// 如果失败,把线程从线程组中删除
if (!started) {
group.threadStartFailed(this);
}
// 这里的 catch 捕捉也是值得我们学习的,我们在工作中 catch 时也应该多用 Throwable,少用 Exception
// 比如对于异步线程抛出来的异常,Exception 是捕捉不住的,Throwable 却可以
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
注意:
当调用start方法时,线程的状态会从NEW改为RUNNABLE,也就是threadStatus不再为初始化状态0,故再次调用start()方法就会抛出异常。Vm 可以处理所有的线程状态!!!
4、Thread类常用的方法:
- Thread.activeCount():这个方法用于返回当前线程的线程组中活动线程的数量,返回的值只是一个估计值,因为当此方法遍历内部数据结构时,线程数可能会动态更改。)。
- Thread.checkAccess(): 检验当前正在执行的线程是否有权限修改thread的属性,这个方法我们一般不自己进行调用,Thread类的set方法在进行属性修改时都会先调用这个方法。
- Thread.currentThread():获取当前正在运行的线程。
- Thread.dumpStack():输出线程栈,一般在debug的时候调用。
- Thread.enumerate(Thread tarray[]):??使用场景。
- Thread.getAllStackTraces():获取系统中所有线程的线程栈信息。
- thread.getName():获取线程的名字。
- thread.getPriority():获取线程的优先级。
- thread.getStackTrace():获取堆栈信息。
- thread.getState():获取线程状态。
- thread.getThreadGroup():获取线程所在线程组。
- thread.interrupt():使得指定线程中断阻塞状态,并将阻塞标志位置为true。
- thread.interrupted():测试当前线程是否被中断。
- thread.isAlive():判断线程是否还存活着。
- thread.isDaemon():判断线程是否是守护线程。
- thread.join():在当前线程中加入指定线程,使得当前线程必须等待指定线程运行结束之后,才能结束。可以理解成线程插队、等待该线程终止。
- Thread.sleep(long):强制线程睡眠一段时间。
- thread.start():启动一个线程。
- thread.setName(name):设置线程的名字。
- thread.setPriority(priority):设置线程的优先级。
- thread.setDaemon(true):将指定线程设置为守护线程。
- thread.yield():使得当前线程退让出CPU资源,把CPU调度机会分配给同样线程优先级的线程。
- object.wait()、object.notify()、object.notifyAll():Object类提供的线程等待和线程唤醒方法。