【多线程编程】创建线程的几种方式 面试题

创建线程的几种方法

  1. 继承Thread类,重写run方法。
  2. 实现Runnable接口,重写run方法 。
  3. Thread+匿名内部类,重写run方法。
  4. Runnable+匿名内部类,重写run方法。
  5. Thread+lambda表达式,不用重写。

 

1.继承Thread类,重写run方法。

//创建一个线程  并发编程   (默认)并发 = 并行 + 并发
class MyThread extends Thread {
    @Override
    public void run() {      //方法重写  线程的入口方法
        while(true) {
            System.out.println("========");
            try {
                //使用静态方法sleep 受查异常 需要抛异常 抛异常有两种 throws 和 try..catch
                //由于这里是重写方法 重写的方法方法名等都要相同 被重写的方法没有throws 故不能用throws  只能用 try..catch
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Demo1 {
    //main 也是一个线程 由JVM自动创建
    public static void main1(String[] args) {
        Thread thread = new MyThread();
        //start 和 run 都是 Thread 的成员
        //run 只是描述了线程的入口 线程要做什么
        //start 是调用了系统API,在系统中创建线程,让线程再调用 run
        thread.start();
        //thread.run();  //这样调用会先执行调用run方法的线程,此线程执行完后,再执行其他线程
    }


    public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();
        while(true) {
            System.out.println("********");
            Thread.sleep(1000);
        }

    }

}

 

  2. 实现Runnable接口,重写run方法。

class MtThread implements Runnable {  //实现Runnable接口

    @Override
    public void run() {
        while (true) {
            System.out.println("&&&&&&&&&&&&&&");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

public class Demo02 {
    public static void main(String[] args) throws InterruptedException {
        //使用 Runnable 的写法 和 直接继承 Thread 之间的区别主要就是【解耦合】
        Runnable runnable = new MtThread();     
        Thread thread = new Thread(runnable);
        thread.start();
        while (true) {
            System.out.println("=========");
            thread.sleep(1000);
        }

    }

}

使用实现 Runnable 的写法 和 直接继承 Thread 之间的区别?

      主要区别就是解耦合,解耦合,即就是 实现Runnable 会把任务存在runnable的run方法中,如果要执行此任务,再创建线程进行调用,这样,任务就不会单独属于某个进程,而是创建的进程都可以去调用。实现了任务与线程的分离,即实现Runnable的写法会解耦合。

3.Thread+匿名内部类,重写run方法。

//利用匿名内部类
public class Demo03 {
    public static void main(String[] args) {
        //匿名内部类
        Thread thread = new Thread() {
            @Override
            public void run() {

                while (true) {
                    System.out.println("=======");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        };
        

        //启动线程
        thread.start();
        while (true) {
            System.out.println("++++++++++++");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

4.Runnable+匿名内部类,重写run方法。

//利用匿名内部类
public class Demo04 {
    public static void main1(String[] args) {
        //第一种
        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                while (true) {
                    System.out.println("======");
                }
            }

        };
        Thread thread = new Thread();
        thread.start();

        while (true) {
            System.out.println("*********");
        }
    }

    public static void main2(String[] args) {
        //第二种
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("???????");
                }
            }
         });
        thread.start();
    }
}

5.Thread+lambda表达式,不用重写。

 public static void main(String[] args) {
        Thread thread = new Thread( () -> {

            while (true) {
                System.out.println("=========");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"这是新线程");    //给线程起名字

        //设置 thread 为后台进程 改为后台进程后 主线程飞快的执行完了,thread 还没来得及执行整个 
          进程就完了
        //前台进程 会影响进程结束  而后台进程则不会即当前台进程若是早于后台进程结束,无论后台结 
          束与否,整个进程都得结束 
        //thread.setDaemon(true); //设置 thread 为后台进程
 
        thread.start();   //启动线程
        System.out.println(thread.isAlive());  //线程是否存活
    }

前台进程 后台进程概念

前台进程 会影响进程结束  而后台进程则不会即当前台进程若是早于后台进程结束,无论后台结 
束与否,整个进程都得结束。

在java中可通过调用方法 setDaemon(true) 来将线程设置为后台进程。

关于后台进程的要点:JVM会在一个进程的所有非后台进程都执行结束完后,才会结束运行。即后台进程完不完并不关心。只关心所有非后台进程完没完。

Thread的几个常见属性

属性 获取方法 说明
ID

getId()

线程的唯一标识 是Java分配给线程的,不是系统 API 提供的。

名称

getName() 线程名
状态 getState()
优先级 getPriority()
是否后台线程 isDaemon()
是否存活 isAlive() 简单理解为run方法是否运行结束
是否被中断 isInterrupted() 标志位

面试题:在JAVA中创建线程都有哪些方式?

  1. 继承Thread类,重写run方法。
  2. 实现Runnable接口,重写run方法 。
  3. Thread+匿名内部类,重写run方法。
  4. Runnable+匿名内部类,重写run方法。
  5. Thread+lambda表达式,不用重写。

lamda表达式不用重写的原因及什么是变量捕获?

lambda本质上是一个匿名函数,并且使用lambda表达式的前提是接口为函数式接口,何为函数式接口,即接口中只包含唯一一个抽象方法

而Runnable接口就是函数式接口,只包含一个抽象方法run(),圆括号对应run()方法的参数列表,为空则就为空。箭头后面的一对花括号对应run()方法的方法体。

这就是与匿名内部类的区别:匿名内部类可用于接口中有多个抽象方法的情况

【多线程编程】创建线程的几种方式 面试题_第1张图片

面试题:start方法和run方法的区别?

  •  start方法是启动线程的,在方法内部,会调用系统API,并在系统内核中创建出线程。
  • run方法是线程的入口方法,即告诉线程你要干啥,要执行啥内容。
  • 本质区别是是否在系统内部创建出新的线程。

 

标志位

  • 额外用一个成员变量设置
private  static boolean isQuit = false;   //标志位
    public static void main1(String[] args) throws InterruptedException {
        Thread thread = new Thread( () -> {   //lambda 表达式是不需要写 run 方法的 , 自身本就是 run
            while(!isQuit) {
                System.out.println("线程正在工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程运行结束");

        });
        thread.start();
        Thread.sleep(5000);
        isQuit = true;

    }

若将成员变量设置为局部变量时的问题 

 【多线程编程】创建线程的几种方式 面试题_第2张图片

 其实将红色方框的代码去掉就好了,但是我们设置标志位的目的就是让 isQuit 在线程执行一段时间后从 false 改为 true 。从而使线程结束,但如果去掉变量就没用了,达不到目的了。返回到问题,即为什么用局部变量就不行呢?原因是与lambda表达式的变量捕获有关

所谓变量捕获,匿名内部类有,lambda也有,即就是访问所在方法或代码块中的局部变量,这个过程被称为"变量捕获"。并且这个局部变量得是有效的最终变量。有效的最终变量不一定是非得用 final 修饰,而是指一个在生命周期中没有被修改过的值。而在上面代码中局部变量 isQuit 在红框框中被修改的话就不符合有效的最终变量。

匿名内部类的变量捕获与lambda表达式的变量捕获的区别?

lambda表达式可以捕获外面的this,而匿名内部类无法直接捕获外面的this。

上述标志位不够好。
因为需要手动创建变量。
并且当线程内部在sleep的时候,主线程修改变量,新线程内部不能及时响应。

下面用另一种方法:Thread类内部,有一个现成的标志位,可以用来判定当前的循环是否要结束。

  • 用Thread的属性方法interrupt()进行标志
public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread( () -> {

            //Thread类内部,有一个现成的标志位,可以用来判定当前的循环是否要结束
            //isInterrupted() 判断标志位
            while (!Thread.currentThread().isInterrupted()) {   
                System.out.println("==========");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    
                    e.printStackTrace();
                    // 1. 若不加break 程序抛出异常后 线程仍然继续正常执行
                    // 2. 加上break ,程序抛出异常后线程立即结束
                    // 3. 做一些其他工作收尾工作 完成之后再break
                    // 其他工作的代码放到这里
                    break;
                }
            }

        });

        //启动线程
        thread.start();

        //5s后线程往下执行
        Thread.sleep(5000);

        //设置标志位 即让线程中断停止
        thread.interrupt(); 
 }

代码中thread.interrupt();这个操作,就是把Thread对象内部的标志位设置为true了。即使线程内部还在sleep,也是可以通过这个方法立即唤醒的。这就是使用这个方法比额外使用一个成员变量充当标志位的优点,及时。并且会抛出InterruptedException异常。

有个注意点是; 一定要在 try... catch 的 catch 里加 break;

如果不加break,程序抛出异常后线程仍然继续正常执行。原因是interrupt()唤醒线程之后,sleep()方法抛出异常,同时会自动清除刚才设置的标志位。while() 循环又再次可以执行。

【多线程编程】创建线程的几种方式 面试题_第3张图片

加了break之后,程序抛出异常后线程立即结束。

【多线程编程】创建线程的几种方式 面试题_第4张图片

 

线程等待 join

无参join()  谁调用,等待谁,直至结束为止 

带参join(1000)  单位是毫秒,等待一秒,但这里会有一个调度开销,等待完成后只是立即进入就绪状态,并不一定立即就执行。

进程,线程的状态

对进程来说,最核心的状态就是就绪状态和阻塞状态,对线程也是。

而在Java中,还赋予了线程一些其他的状态。

状态 说明
NEW Thread对象已经创建但start方法还没调用时
TERMINATED Thread对象还在,内核中的线程不存在了
RUNNABLE 就绪状态(线程已经上CPU执行 / 线程正等待上CPU)
TIMED_WAITING 阻塞:由于sleep这种固定时间的方式产生的阻塞
WAITING 阻塞:由于wait这种不固定时间的方式产生的阻塞
BLOCKED 阻塞:由于锁竞争导致的阻塞

通过这些方法,若在编程中遇到线程卡死等问题时就可以通过这些状态来确定原因。

你可能感兴趣的:(习题总结,多线程安全,java,开发语言,多线程)