大家好,我是傻明蚕豆,今天为大家带来java线程的基础知识。
我在CSDN也发布了,编辑比这里美观:https://blog.csdn.net/weixin_44668634/article/details/109147514
一、线程的概念;
进程:进程就是一个在内存中运行的应用程序,比如你电脑在运行的一个QQ,如果你再打开个哭狗,那就是另一个进程,每个进程都有自己的独立内存空间,一个进程中可以有多个线程。
线程:线程是进程里面的一个执行流程,是CPU调度和分派的基本单位,一个进程中可以有多个线程,线程与进程内的其他线程一起共享所有该进程的资源,每个线程有自己的堆栈和局部变量。java程序中最少有两个线程,一个main线程,一个是垃圾回收线程。
二、线程的生命周期;
NEW状态:
一个已创建而未启动(还没调用Thread.start()方法)的Java线程,就是处于NEW状态。
RUNNABLE状态:
当线程调用了start()之后,就会进入RUNNABLE状态,RUNNABLE状态包含两个子状态:READY和RUNNING。
READY:准备状态,当被线程调度器进行调度,会变为RUNNING状态。
RUNNING:运行状态,线程正在运行,run()方法中的代码在执行。
当Java线程调用yield()方法时或者由于线程调度器的调度,线程的状态有可能由RUNNING转变为READY,Thread.getState()可以获取到状态。
WAITING状态:
无限期等待状态,需要唤醒,进入该状态是由于调用了下面方法之一:不带超时的Object.wait(),不带超时的Thread.join(),LockSupport.park() 。
TIMED WAITING状态:
时间等待状态,无需唤醒,进入该状态是由于调用了下面方法之一:Thread.sleep(time),Object.wait(time),Thread.join(time),LockSupport.parkNanos(time),LockSupport.parkUntil(time)
BLOCKED状态:
阻塞状态,遇到阻塞的I0操作或者在等待某个锁资源都会进入阻塞状态。
TERMINATED状态:
死亡状态,线程的最终状态,在该状态的线程不会再切换到其它任何状态,意味着线程的整个生命周期都结束了。
三、线程的创建方式;
1、继承Thread类
public class Thread1 extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread t=new Thread1();
t.start();
//t.start();
}
}
2、实现Runnable接口
public class Thread2 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
new Thread(new Thread2()).start();
}
}
3、实现Callable接口
public class Thread3 implements Callable{
public static void main(String[] args) {
ExecutorService executorService=Executors.newFixedThreadPool(1);
Thread3 t=new Thread3();
Future future=executorService.submit(t);
System.out.println("result:"+future);
}
@Override
public Object call() throws Exception {
String threadName=Thread.currentThread().getName();
System.out.println("name:"+threadName);
return threadName;
}
}
四、线程的启动和停止;
怎么启动线程?
Thread类实现了Runnable接口,所以创建线程继承Thread类或者实现Runnable接口都是一样的,因为java是单继承,所以如果要继承其他类,那就只有实现Runnable接口了。
线程启动必须调用Thread.start方法,run()方法只是一个类中的普通方法,调用run方法跟调用其他普通方法一样,而start()是会创建新线程,然后新线程调用run方法。
我们来看看Thread类的源码(JDK1.8):
private volatile int threadStatus = 0;
public synchronized void start() {
if (threadStatus != 0){
throw new IllegalThreadStateException();
}
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then it will be passed up the call stack */
}
}
}
private native void start0();
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
threadStatus和枚举类State就是用来记录Thread的状态,Thread类中并没有对threadStatus进行过赋值,那么threadStatus应该是在start0方法里面改变了。
start0方法是native修饰的,所以是调用了C++实现的方法,而threadStatus是volatile修饰,保证可见性,
因此native方法进行变量threadStatus修改的同时Thread类中就能同时获取到该变量的最新值。
同时start方法是synchronized修饰的,所以即使多线程同时调用start方法,也能保证原子性。
线程初始化后状态为0,所以start方法只可以调用一次,因为线程调用过一次start方法后,状态就会改变,那么threadStatus就不等于0了,
由于threadStatus是在c++里面修改,所以看不到ThreadStatus等于多少,但肯定不等于0,所以再调用start方法就会抛出IllegalThreadStateException。
如何中断线程?
不要问我为什么不用stop方法。
在Thread线程类里面有个isInterrupted方法,
private native boolean isInterrupted(boolean ClearInterrupted);
是一个native方法,Thread类有两个public方法调用了该native方法,
public boolean isInterrupted() {
return isInterrupted(false);
}
这个方法只返回线程的中断状态,并没有去改变线程的中断状态。
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
这个方法是个静态方法,所以只作用于当前正在执行的线程,方法里面调用了currentThread()获取当前线程,该方法返回当前线程的中断状态,并且会重置线程的中断状态。
这两个方法都只是查看线程的中断状态,并没有中断线程,还有一个方法interrupt():
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
这个就是中断线程的方法,首先判断是否是当前线程,如果不是调用checkAccess()方法,大概就是查询权限吧,也就是说一个线程只允许自己中断。
我们再看看这个方法的注释,调用interrupt方法时,如果线程因为以下方法的调用而处于阻塞中,线程的中断标志会被清除,并抛出一个InterruptedException。
以下方法包括:Object类的wait()、wait(long)、wait(long, int)和Thread类的join()、join(long)、join(long, int)、sleep(long)、sleep(long, int)。
如果线程没有因为以上方法调用而进入阻塞状态的话,那么中断这个线程仅仅会设置它的中断标志位,而不会抛出InterruptedException。
总结:调用interrupt方法,并不会中断一个正在运行的线程,而是将线程的中断标志设为true,在某些情况下抛出一个InterruptedException罢了。
也就是说,无论是设置中断状态,还是抛出InterruptedException,那都是给当前线程的建议,至于在收到这些中断的建议后,当前线程要怎么处理,完全取决于当前线程。
那么到底怎么中断线程呢?
设置中断标记法
public static void test1(){
Thread thread=new Thread(()->{
System.out.println("线程启动了");
while(!Thread.currentThread().isInterrupted()){ //默认情况下isInterrupted() 返回 false
System.out.println(Thread.currentThread().isInterrupted());
}
System.out.println("线程结束了");
},"test1");
thread.start();
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//通过调用interrupt(),把while里面的isInterrupted()返回变为true
thread.interrupt();
}
当main线程运行到最后一行代码thread.interrupt();,那么Thread.currentThread().isInterrupted()就等于true,
然后循环不成立,接着执行输出"线程结束了",那岂不是没法中断?所以逻辑代码都写在循环里面才能使用中断标记法。
捕获InterruptedException法
public static void test2(){
Thread thread=new Thread(()->{
System.out.println("线程启动了");
while(!Thread.currentThread().isInterrupted()){
try {
Thread.sleep(1100);
System.out.println(Thread.currentThread().isInterrupted());//逻辑处理代码
} catch (InterruptedException e) {
System.out.println("出异常了");
//Thread.currentThread().interrupt();//调用中断方法
//break;//跳出循环
//return ;//跳出方法,不再执行输出"线程结束了"的逻辑
}
}
System.out.println("线程结束了");
},"test2");
thread.start();
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程执行了一段时间(上面睡眠5秒),我突然想要线程停止,于是调用interrupt方法
thread.interrupt();
}
这里如果线程test2正在sleep,main线程这时候执行thread.interrupt(),那么会抛出异常,并且线程的中断标志会被清除,所以要做什么操作,就必须在catch里面搞了。
或者这样写:
public static void test3(){
Thread thread=new Thread(()->{
System.out.println("线程启动了");
try {
while(!Thread.currentThread().isInterrupted()){
Thread.sleep(1100);
System.out.println(Thread.currentThread().isInterrupted());//逻辑处理代码
}
} catch (InterruptedException e) {
System.out.println("出异常了");
//return ;//跳出方法,不再执行输出"线程结束了"的逻辑
}
System.out.println("线程结束了");
},"test3");
thread.start();
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
五、线程的其他方法;
sleep方法:
public static native void sleep(long millis) throws InterruptedException;
sleep是一个静态的native方法,它作用于当前线程,当调用sleep方法后,当前线程会让出CPU资源,但不会释放锁。
Thread.sleep() 与 Thread.currentThread().sleep() 是一样的,只不过一个是用类直接调用静态方法, 一个是用类的实例调用静态方法。
yield方法:
public static native void yield();
yield是一个静态的native方法,当调用yield方法后,当前线程表示会让出CPU,但到底让不让是随机的,或者是CPU决定的。
join方法:
调用join方法后,会让当前线程先执行,如果指定时间,则先执行指定时间,如果时间为0,则是让当前线程执行完为止。
比如有线程T1和线程T2,线程T1里面有这样代码:T2.jion(0);
那么T1会立刻停止让线程T2执行,一直到T2执行完,T1才会继续执行,即使T2里面调用了sleep方法让出CPU资源,T1也会等T2执行完才能获得CPU资源。
我们来看一下源码:
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//当参数为0,while循环会一直检查T2是否存活,存活的话T1就一直wait(0)。
//注意:如果T1在执行T2.jion(0)时,T2还没start,那么T2就不存活,那么T2.jion(0)相当于白写了。
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
好了,java线程就介绍到这里,如有问题请留言一起探讨。
谢谢观看!
网友评论