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

一、了解多线程的创建


从这里开始,我们来了解线程的创建,一般有以下多种方式等创建方式

  • 继承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都有哪些属性和行为呢?我们可以通过源码来简单的看一下

image.png

这时我们在运行线程,就可以打印出线程的名称来看看是哪一个线程了

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);
    }
    //省略其他关键性代码......
}

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

我们可以来看一下这个线程组都有哪些方法?

image.png

其实线程组它是一种树状结构,可以这么理解,比如说这是当前一个顶层的线程组

image.png

那么这个顶层线程组里面,下面可以接着放线程组,也可以放线程

image.png

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

image.png

接下来我们看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里看看有什么方法

image.png

我们看到这里有三个方法,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线程重置后,一样可以运行起来完成输出

参考资料


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

你可能感兴趣的:(我所知道并发编程之线程的初始化,中断以及其源码讲解)