3.线程之间的状态转换,interrupt中断标志位等

本文详细解析了Java线程的内部机制,包括start()与run()的区别,线程状态,优先级设定,以及中断机制的正确使用。通过代码示例,深入浅出地讲解了线程的生命周期和管理。
摘要由CSDN通过智能技术生成
线程状态

线程状态

解读
1.start() ≠ run()
/**
 * 比较start()与run()的不同
 */
public class testThread {
    public static class DemoThread extends Thread{
        @Override
        public void run() {
            System.out.println("我是"+Thread.currentThread().getName()+"线程");//打印当前调用的线程
        }
    }

    public static void main(String[] args){
        Thread t1 = new DemoThread();//创建一个线程对象
        t1.run();//调用run()方法
        t1.start();//调用start()方法
    }
}

Thread是一个线程类,但同时记住,它也是一个普通的类,很多人一开始能记住这个类的start()和run()是不一样的,但是随着学习的深入,这一点似乎已经越来越被淡忘了。

2.start()源码
public synchronized void start() {
    //线程状态,0表示该线程是一个新建的状态,非0则会抛出异常
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

	//将这个线程对象添加到线程组进行管理,我们在这里可以理解为就是一个管理线程的容器,group如果我们在初始化的时候指定了其相应的线程组的对象,则会添加到我们所设置的,如果没有,则在哪个线程进行对象.start()的,就添加到相应的线程组里面或者是安全管理器的线程组。整个的线程启动是通过线程组管理的
    group.add(this);
    boolean started = false;//是否启动
    try {
        start0();//native方法,启动
        started = true;//已经启动
    } finally {
        try {
            if (!started) {//未启动
                group.threadStartFailed(this);//启动失败
            }
        } catch (Throwable ignore) {
            
        }
    }
}
 

通过解读的1.2点,我们可以看到,在start()方法里面,并没有涉及到run()方法,我估计可能在start0()这个native方法中有所涉及,当然了,我也是估计下。

关于线程组的初始化,我们用下列代码来演示下

/**
 * 比较start()与run()的不同
 */
public class testThread {
    public static class DemoThread extends Thread{
        @Override
        public void run() {
            System.out.println("我是"+Thread.currentThread().getName()+"线程");//打印当前调用的线程
            System.out.println("我是"+Thread.currentThread().getThreadGroup()+"组");//打印当前调用的线程
        }
    }

    public static void main(String[] args){
        Thread t1 = new DemoThread();//创建一个线程对象
        t1.run();//调用run()方法
        t1.start();//调用start()方法
    }
}

结果:

我是main线程
我是java.lang.ThreadGroup[name=main,maxpri=10]组
我是Thread-0线程
我是java.lang.ThreadGroup[name=main,maxpri=10]组

凭借这两点,我们可以看到解读1.2两点所写的基本正确。

3.初始化方法

所有的Thread类的构造方法最后都调用了一个名为init的方法

//ThreadGroup g 线程组 
//Runnable target 调用run()
//String name 线程名字
//long stackSize 新线程所需的堆栈大小,或者零表示此参数将被忽略。简单来说,分配的资源的大小,内存的大小
//AccessControlContext acc 安全管理
//boolean inheritThreadLocals 变量的共享,在后续的笔记中会写到
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    //name不能为空,在构造方法中,如果我们没有传递name参数,会自动给我们生成
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;
    //当前调用的线程,例如,在main方法中去开启新线程,则会获得main线程
    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. */
        //parent线程组设置为当前线程的线程组
        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();
	//设置线程组,可手动亦可根据parent或者安全管理来设置,上面已经处理好了是怎么设置的。
    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 */
    //设置线程ID
    tid = nextThreadID();
}

在源码中

 		//parent线程组设置为当前线程的线程组
        if (g == null) {
            g = parent.getThreadGroup();
        }

可以解释我们在演示中为什么会出现同一个线程组的问题。

4.优先级

在init的源码中可以看到设置了一个优先级(同parent的优先级)

this.priority = parent.getPriority();

priority是优先级,可以理解为线程的先后顺序,值为1~10(越大分配的时间片越高),缺省为5,不过一般我们都不会去设置这个,因为有的操作系统并没有优先级这个概念,所以也不能保证到底那个能先运行。所以工作时,我们尽量少使用这个优先级。

设置优先级的代码:

public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    //设置的优先级不合理
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    //暂定,不是很理解
    if((g = getThreadGroup()) != null) {
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}
状态图的方法

过时的:

  1. stop():强制线程停止执行,资源可能得不到正确的释放
  2. resumer():挂起恢复的线程,容易死锁
  3. suspend():挂起该线程,不容易释放资源

推荐:

  1. interrupt():中断一个线程,并不是强行关闭,中断标志位为true
  2. isInterrupted:判断当前线程是否中断
  3. interrupted:判断是否中断,中断标志位false
  4. true是中断,false是未中断

这个比较难以理解,我们做个代码演示下:

public class testThread {
    public static class DemoThread extends Thread{
        @Override
        public void run(){
            while (true){
                System.out.println("线程名:"+Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException{
        DemoThread t1 = new DemoThread();//创建一个线程对象
        t1.start();
        Thread.sleep(20);
        t1.interrupt();
    }
}

结果:不停的打印 线程名:Thread-0

看清楚我们在上面所说的,interrupt并不是强制关闭,所以这更像是一个协作式,在main里面interrupt了,但是在相应的线程里面,我们要处理这个中断标志位的改变,而这里并没有进行处理或者去有用到这个中断标志位。

我们再看下面的演示

public class testThread {
    public static class DemoThread extends Thread{
        @Override
        public void run(){
            while (!isInterrupted()){//有用到这个中断标志位哦
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程名:"+Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException{
        DemoThread t1 = new DemoThread();//创建一个线程对象
        t1.start();
        Thread.sleep(20);
        t1.interrupt();
    }
}

结果:

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at testThread$DemoThread.run(testThread.java:7)线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
线程名:Thread-0
......

但是如果你仔细去分析这个代码,你会发现,为什么这个中断标志位改变了以后,仍然会不停的打印呢?

这是因为当抛出了异常以后,中断标志位会被自动复位位false,所以我们该如何进行修改呢?

很简单,在catch里面加上一句**interrupt();**就可以了。

public class testThread {
    public static class DemoThread extends Thread{
        @Override
        public void run(){
            while (!isInterrupted()){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    interrupt();
                    e.printStackTrace();
                }
                System.out.println("线程名:"+Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException{
        DemoThread t1 = new DemoThread();//创建一个线程对象
        t1.start();
        Thread.sleep(20);
        t1.interrupt();
    }
}

结果:

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at testThread$DemoThread.run(testThread.java:7)
线程名:Thread-0

在思考的时候,可以简单的理解为设置一个boolean类型的变量,去设置这个变量来协作式的操作线程的状态,但是要注意异常会复位这一点,另外,如果你深入的推敲这个代码,你会发现只要用到这个这个中断标志位的时候,就会抛出中断异常InterruptedException,所以,当我们看见一个方法抛出这个异常的时候,也就遇到了在底层时,这个方法会去对中断标志位进行一个处理。

yield():释放资源,并可以再次获得

sleep():释放资源,不可再次获得

再次:指的是下一次

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值