乐趣区

关于java:我所知道并发编程之线程的初始化中断以及其源码讲解

一、理解多线程的创立


从这里开始,咱们来理解线程的创立,个别有以下多种形式等创立形式

  • 继承 Thread 类
  • 实现 Runnable 接口

继承 Thread 类和实现 Runnable 接口,这两种形式能够说是中规中矩的,也是咱们用的比拟多的创立线程的形式。

  • 匿名外部类的形式

咱们也能够应用匿名外部类的形式继承 Thread 类和实现 Runnable 接口的实现另外一种形式创立

  • 带返回值的线程

在 run()办法中没有方法去抛出更多的异样,而且没有返回值。那么咱们就心愿有一个可能抛出异样并且带返回值的这么一种线程,这就提供了第四种创立线程的形式

  • 定时器(quartz)

其实定时器也相当于是其中的一个线程,也是属于创立线程的一种形式

  • 线程池的实现

还有就是咱们都晓得的能够通过线程池的形式来创立线程

  • Lambda 表达式实现

在 JDK8 中,新增了一个十分弱小的一个 Lambda 表达式,那么,就能够通过这个表达式能够实现多线程这种对咱们汇合的操作、对咱们的数据流的操作等等

二、从源码上了解线程的创立


第一种形式就是继承 Thread 类的形式,咱们说万物皆对象,那么线程也能够是一个对象

// 继承 Thread
class Demo1 extends Thread{}

这时咱们的 Demo1 就是线程类的子类,那么调用实例的线程的启动办法就能够启动线程了

那么线程执行什么活呢?其实就是在线程外面定义过的一个 run()办法,咱们来看一下

public class Thread implements Runnable {

    @Override
    public void run() {if (target != null) {target.run();
        }
    }
    
    // 省略其余关键性代码......
}   

这个 run()办法的代码体十分的简略,这个办法是须要咱们去重写的。

咱们先重写 run()办法,等会在提一提源码中的这个 run()办法中的代码的意思

class Demo1 extends  Thread{

    @Override
    public void run() {System.out.println("线程执行起来了.....");
    }
}

咱们刚刚说到线程也能够是一个对象,那么 Thread 都有哪些属性和行为呢?咱们能够通过源码来简略的看一下

这时咱们在运行线程,就能够打印出线程的名称来看看是哪一个线程了

class Demo1 extends  Thread{

    @Override
    public void run() {System.out.println(getName()+"线程执行起来了.....");
    }

    public static void main(String[] args) {Demo1 demo1 = new Demo1();
        demo1.start();}
}

// 运行后果如下:Thread-0 线程执行起来了.....

咱们并没有给它指定名字,它是如何叫做 Thread- 0 呢?咱们来看一下源码

public class Thread implements Runnable {public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);}
    public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);}
           Thread(Runnable target, AccessControlContext acc) {init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);}
    public Thread(ThreadGroup group, Runnable target) {init(group, target, "Thread-" + nextThreadNum(), 0);}
    public Thread(String name) {init(null, null, name, 0);}
    public Thread(ThreadGroup group, String name) {init(group, null, name, 0);}   
    public Thread(Runnable target, String name) {init(null, target, name, 0);} 
    public Thread(ThreadGroup group, Runnable target, String name) {init(group, target, name, 0);}  
    public Thread(ThreadGroup group, Runnable target, String name,long stackSize) {init(group, target, name, stackSize);}
    // 省略其余关键性代码......
}   

通过观察 Thread 的构造方法,咱们发现能够给定指定的线程名字进行初始化

class Demo1 extends  Thread{public Demo1(String name) {super(name);
    }

    @Override
    public void run() {System.out.println(getName()+"线程执行起来了.....");
    }

    public static void main(String[] args) {Demo1 demo1 = new Demo1("first-thrade");
        demo1.start();}
}

// 运行后果如下:first-thrade 线程执行起来了.....

咱们不论应用哪种构造方法,在创立 Thread 类的时候构造方法外面都有一个进行初始化的过程。

public class Thread implements Runnable {public Thread(String name) {init(null, null, name, 0);
    }
    // 省略其余关键性代码......
}

咱们来剖析看看这个 init()办法外面有什么?做了些什么事件?

public class Thread implements Runnable {
    
    
    //ThreadGroup 线程组 作用:用于对线程进行分组的
    private void init(ThreadGroup g, Runnable target, String name,long stackSize) {init(g, target, name, stackSize, null, true);
    }
    // 省略其余关键性代码......
}

这个办法外面有很多的参数,第一个参数是线程组,这个线程组是干什么的呢?其实就是用于对线程进行分组的。

咱们能够来看一下这个线程组都有哪些办法?

其实线程组它是一种树状构造,能够这么了解,比如说这是以后一个顶层的线程组

那么这个顶层线程组外面,上面能够接着放线程组,也能够放线程

总之它就是一层一层的往下走,依照面向对象的思维形式,它会有它的名字,以及获取它的上一级下一级等性能

接下来咱们看 init 办法 () 第二个参数是 Runnable,也就是能够指定一个线程工作。

public class Thread implements Runnable {
    
    
    //target 线程 作用:能够指定一个线程工作
    private void init(ThreadGroup g, Runnable target, String name,long stackSize) {init(g, target, name, stackSize, null, true);
    }
    // 省略其余关键性代码......
}

第三个参数就是线程的名字,当咱们的初始化时没有给予名字时

public class Thread implements Runnable {public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    // 省略其余关键性代码......
}

发现会调用的是 Thread- nextThreadNum(),对线程进行编号

public class Thread implements Runnable {
    
    // 用于主动编号匿名线程
    private static int threadInitNumber;
    
    private static synchronized int nextThreadNum() {return threadInitNumber++;}
    // 省略其余关键性代码......
}

这也就是为什么咱们没有给予线程名字,它为什么叫做 Thread- 0 的起因

第四个参数是 stackSize,那么,这个参数又是什么呢?咱们看一下到底是什么?

public class Thread implements Runnable {
    
    
    /*
     * The requested stack size for this thread, or 0 if the creator did
     * not specify a stack size.  It is up to the VM to do whatever it
     * likes with this number; some VMs will ignore it.
     */
    private long stackSize;
    
    // 省略其余关键性代码......
}

就是开发 Thread 类的这个人,他也不晓得 stackSize 到底是干什么用的,它是为虚拟机做一些事件,做一些什么事件呢?

做一些虚拟机想干的事件,就是说 stackSize 指定了之后,虚拟机能够通过 stackSize 做一些虚拟机想做的事件。

当然了,很多的虚拟机是疏忽 stackSize 的。所以,咱们也不须要关 stackSize 这个参数了

三、对于线程的 Daemon


Daemon 是用来干什么的呢?咱们称它叫做守护线程,或者说它是一个反对型线程,它是干什么的呢?

它次要是作用在程序中后盾调用做一些支持性工作,就比如说垃圾回收线程吧,它就扔在后盾在后盾本人默默的去做一些事,当程序执行结束的时候,它即便执行不结束那么它仍然会跟着退出

class Demo1 extends  Thread{public Demo1(String name) {super(name);
    }

    @Override
    public void run() {while (true){System.out.println(getName()+"线程执行起来了.....");
        }
    }

    public static void main(String[] args) {Demo1 demo1 = new Demo1("first-thrade");
        demo1.setDaemon(true);
        demo1.start();}
}

咱们当初把线程的 Daemon 设置为 True,使以后线程是反对型线程,这样即便这个线程的线程工作没有执行结束,当主线程执行结束之后,那么这个线程也仍然会被退出

四、从源码上了解线程的中断


比如说咱们想让一个线程执行一段时间之后,咱们不想让它再执行了,咱们让它中断掉怎么办呢?咱们在 Thread 里看看有什么办法

咱们看到这里有三个办法,interrupt()、interrupted()、isInterrupted()。

public class Thread implements Runnable {
    
    // 给以后线程标记中断标记
    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();}
    
    // 省略其余关键性代码......
}
public class Thread implements Runnable {
    
    // 返回以后线程中断标记并将它重置
    public static boolean interrupted() {return currentThread().isInterrupted(true);
    }
    
    // 省略其余关键性代码......
}
public class Thread implements Runnable {
    
    // 返回以后线程中断标记但不重置
    public boolean isInterrupted() {return isInterrupted(false);
    }
    
    // 省略其余关键性代码......
}

那么接下来咱们创立两个线程,同时让一个线程中断,看看是什么成果

class Demo1 extends  Thread{public Demo1(String name) {super(name);
    }

    @Override
    public void run() {while (true){System.out.println(getName()+"线程执行起来了.....");
            try {Thread.sleep(2000);
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }
    }
}
public static void main(String[] args) {Demo1 demo1 = new Demo1("xiaoming-thrade");
        Demo1 demo2 = new Demo1("xiaohong-thrade");
        // 启动两个线程
        demo1.start();
        demo2.start();

        // 让 xiaoming 线程中断
        demo1.interrupt();}
运行后果如下:xiaohong-thrade 线程执行起来了.....
xiaoming-thrade 线程执行起来了.....
xiaoming-thrade 线程执行起来了.....
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at Demo1.run(thread.java:57)
xiaohong-thrade 线程执行起来了.....

咱们发现执行出错了,然而前面仍然在执行。那么这是怎么回事呢?

这就须要咱们本人去解决,咱们写代码须要解决中断这种状态

class Demo1 extends  Thread{public Demo1(String name) {super(name);
    }

    @Override
    public void run() {while (!interrupted()){System.out.println(getName()+"线程执行起来了.....");
            try {Thread.sleep(2000);
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }
    }
}
public static void main(String[] args) {Demo1 demo1 = new Demo1("xiaoming-thrade");
        Demo1 demo2 = new Demo1("xiaohong-thrade");
        // 启动两个线程
        demo1.start();
        demo2.start();

        // 让 xiaoming 线程标记中断标记
        demo1.interrupt();}
运行后果如下:xiaohong-thrade 线程执行起来了.....
xiaohong-thrade 线程执行起来了.....
xiaohong-thrade 线程执行起来了.....
xiaohong-thrade 线程执行起来了.....

这时咱们的 run 办法里的 while 条件是调用 interrupted(),它会返回以后线程中断标记并且重置它,这样以后线程不是中断标记的话就去执行

那么咱们之前调用了 demo1.interrupt()之后,会将 demo1 的中断标记批改为 ” 是中断 ”

咱们能够调用 isInterrupted()办法去查看 demo1 的中断标记,你会发现会返回为 true

class Demo1 extends  Thread{public Demo1(String name) {super(name);
    }

    @Override
    public void run() {System.out.println(getName() +"线程的中断标记为"+isInterrupted());
        while (!interrupted()){System.out.println(getName()+"线程执行起来了.....");
            try {Thread.sleep(2000);
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }
    }
}
运行后果如下:xiaoming-thrade 线程的中断标记为 true
xiaohong-thrade 线程的中断标记为 false
xiaohong-thrade 线程执行起来了.....
xiaohong-thrade 线程执行起来了.....
xiaohong-thrade 线程执行起来了.....

若咱们调用 interrupted 办法来查看以后线程中断标记,那么就会重置这就会满足 while 条件

class Demo1 extends  Thread{public Demo1(String name) {super(name);
    }

    @Override
    public void run() {System.out.println(getName() +"线程的中断标记为"+interrupted());
        while (!interrupted()){System.out.println(getName() +"线程的中断标记为"+interrupted());
            System.out.println(getName()+"线程执行起来了.....");
            try {Thread.sleep(2000);
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }
    }
}

运行后果如下:xiaoming-thrade 线程的中断标记为 true
xiaoming-thrade 线程的中断标记为 false
xiaoming-thrade 线程执行起来了.....
xiaohong-thrade 线程的中断标记为 false
xiaohong-thrade 线程的中断标记为 false
xiaohong-thrade 线程执行起来了.....

这时就会造成 demo1 线程重置后,一样能够运行起来实现输入

参考资料


龙果学院:并发编程原理与实战(叶子猿老师)

退出移动版