Java线程入门需要的知识(面试必问)

线程是什么

线程(Thread)是操作系统能进行运算调度的最小单位,被包含在进程中,是进程的实际运作单位
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

线程是独立调度分派的基本单位。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环(registercontext),自己的线程本地存储(thread-local storage)。

线程的生命周期

线程的生命周期
新建—>就绪(可运行)—>运行—>等待/阻塞–>可运行–>死亡

新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

使用线程有什么好处

计算机运行过程中,进程不断执行,假如需要不间断的交叉输出两个用户的信息,一般代码很难完成这种功能,使用线程能很好的解决并发执行问题 ,线程相当于是计算机的一个运行资源,能和代码中其他执行方法分隔开,方便,效率高。

线程创建的不止三种方法

  1. 继承Thread类 ,直接new类.start(),一般最好实现Runnable接口的方法,这样,本类就可以继承其他的类了,不影响代码。Thread这个类其实就是实现了Runnable接口的一个实例,进去看Thread的源码就知道其实本质还是实现的Runnable接口的,Start()是一个native方法。
// 线程类 ,调用run方法
public class Thread1 extends Thread {
    @Override
    public void run() {
        while (true){
            System.out.println("aa"+Thread.currentThread().getId());
        }
    }
}
//测试类
public class Test1 {
    public static void main(String[] args){
        Thread1 thread1 = new Thread1();
        thread1.start();//start 创建一个线程,再执行run()方法
        while (true){
            System.out.println("bb"+Thread.currentThread().getId());
        }
    }
}


  1. 实现Runnable接口,没有Start方法,使用时要new Thread®.start() 嵌套一层
//线程类,实现Runnable接口
public class Thread2 implements Runnable {
    @Override
    public void run() {
        while (true){
            System.out.println("aaaaaaaaaaaa");
        }
    }
}
//测试类 使用一层Thread嵌套
public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        Thread2 thread2 = new Thread2();
        Thread thread = new Thread(thread2);
//        Thread.sleep(1000);//使线程睡眠1秒种
        thread.start();//开始执行线程
        while (true){
            System.out.println("bbbbbbbbb");

        }
    }
}

3.实现Callable接口,配合上线程池(偷偷拷了一段龟兔赛跑的线程模拟代码)我们使用Callable的好处是不仅可以通过获取Future的对象来得到任务的返回值,还可以获取异常,但是Runnable就不行。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;


public class Thred {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        创建线程
        ExecutorService ser = Executors.newFixedThreadPool(2);
        Race tortoise = new Race("乌龟",1000);
        Race rabbit = new Race("兔子",500);

        Future<Integer> result1 = ser.submit(tortoise);
        Future<Integer> result2 = ser.submit(rabbit);

        Thread.sleep(2000);
        tortoise.setFlag(false);
        rabbit.setFlag(false);


//        获取值
        Integer num1 = result1.get();
        Integer num2 = result2.get();

        System.out.println("乌龟"+num1);
        System.out.println("兔子"+num2);

//        停止服务
        ser.shutdownNow();


    }


}

class Race implements Callable<Integer>{
    private String name;//名称
    private long time;//延时时间
    private boolean flag = true;
    private int step = 0;

    public Race() {
    }

    public Race(String name) {
        super();
        this.name = name;
    }

    public Race(String name, long time) {
        super();
        this.name = name;
        this.time = time;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public int getStep() {
        return step;
    }

    public void setStep(int step) {
        this.step = step;
    }

    @Override
    public Integer call() throws Exception {
        while (flag){
            Thread.sleep(time);
            step++;
        }
        return step;
    }
}

线程池

  • 线程池的好处是:由于线程资源每次需要的时候要创建,不需要就杀掉,使用的线程越多,资源浪费就越严重,而且很占用系统调用资源,使用线程池可以大大减少资源浪费,这也是缓存策略的一种(划重点:面试官最喜欢问缓存,各大缓存策略学它个十来个,面试绝对有自信)。
  • 那么线程池有看过源码就知道,最高的接口是Executor,但是它还不算是个线程池,实际上线程池接口是继承了它的儿子ExecutorService
  1. 使用线程池(ThreadPool)(四种创建线程池的方法)
    一、 CachePool 有就用原来的,没有就创建新线程 ,创建数量最高为(Integer.MAX_VALUE)
public class CachePool {
    public static void main(String[] args) throws InterruptedException {
        // lombda表达式创建匿名实现类对象(必须是接口且只有一个方法)
        Runnable runnable = () -> System.out.println("hahaha...."+Thread.currentThread().getId());//创建线程的简便方法
        // 线程池,有四种
        /*
        第一种CachePool
        1. 没有线程的时候,创建一个新的线程,使用完后放回
        池中
        2. 下次调用会重复利用该池中的线程,而不需要重新创建
        新的线程
        3. 如果池中没有线程, 新的任务会新建一个线程
        */
        //线程池放在外面,否则循环中每次创建新的池
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            //启动线程
            executorService.execute(runnable);//相当于调start方法
        }
        //发送一个关闭指令,等待线程池中所有线程任务完成后再关闭池
        executorService.shutdown();
        System.out.println("end");
    }
}

二、 FixedPool 有就用原来的,没有就创建新线程,但是创建线程数量固定上限,后续任务等

public class FixedPool {
    public static void main(String[] args) throws InterruptedException {

    // lombda表达式创建匿名实现类对象(必须是接口且只有一个方法)
    Runnable runnable = () -> System.out.println("hahaha...."+Thread.currentThread().getId());//创建线程的简便方法
        try{
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    // 线程池,有四种
        /*
        第二种FixedPool
        1. 固定线程池中多少个线程,有就用原来的,没有就创建,但不能比规定多
        2. 下次调用会重复利用该池中的线程,而不需要重新创建
        新的线程
        3. 如果池中没有线程, 新的任务会新建一个线程,直到最大数量
        */
        // 线程池放外面,否则循环中每次创建新的池
    ExecutorService executorService = Executors.newFixedThreadPool(5); // 规定线程数量上限
        for (int i = 0; i < 10; i++) {
        //启动线程
        executorService.execute(runnable);//相当于调start方法
    }
    //发送一个关闭指令,等待线程池中所有线程任务完成后再关闭池
        executorService.shutdown();
        System.out.println("end");
    }

}

三、 ScheduledPool 周期性执行的线程池(类似setInterval)

	public class ScheduledPool {
    public static void main(String[] args) {
        Runnable r = () -> System.out.println("hahaha...."+Thread.currentThread().getId());
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
        executor.scheduleAtFixedRate(r,5000,1000, TimeUnit.MILLISECONDS);
        //如果定时任务需要周期执行,则不能执行shutdown
       // executor.shutdown();
    }
}

四、SinglePool 只创建一个线程来完成任务等

补充 : run和start 的区别

  1. 当线程开始运行,会调用run方法。(如果手动调用该run方法,那么它和普通方法一样)
  2. start是告诉JVM启动一个新线程运行run方法

线程的方法

  1. join 示例:thread1.join
    阻塞运行,等待thread1执行完成后,程序继续往下执行

  2. stop 过时不推荐,直接停止线程,导致不安全

  3. interrupt 设置一个interrupt布尔标志为true,然后线程根据这个标志
    可以决定什么时候退出. 如果在线程阻塞的情况下调用了interrupt,那么线程会抛出
    一个interruptException并重置interrupt为false

  4. sleep(ms)阻塞当前线程一段时间,并且不会释放同步块(锁)

  5. yield 退让,给OS调用其他同级别线程的机会,自己重新回到就绪状态参与竞选,
    可以理解为自动时间的sleep.不会释放锁.

  6. wait 等待,释放对象锁并且进入等待状态.释放的必须为同步块的锁对象
    可以有参数设置ms多久进入就绪状态(一般不推荐,可能导致线程不安.全).
    也可以不写参数,那么会一直等待下去,需要其他线程唤醒它才能继续运行

  7. notify 随机唤醒一个等待状态的线程.
    该方法必须和要唤醒的线程,在同一个锁对象中.而notifyAll就是唤醒所有等待中的线程。

几个面试题分享

Q:请问sleep()和wait()区别?
A:sleep()是线程Thread类的方法,一般sleep()会带一个参数,作用是使线程暂停一段时间,到时间自动恢复,而且调用sleep()不会释放对象锁。而wait()是Object的方法,调用此方法会释放对象锁,那么直到调用了Object.notify()或Object.notifyAll()方法才会重新获得对象锁进入运行状态,当然wait()也可以带时间参数,在无锁竞争情况下,在等待时间过去后就直接重新获取锁,再往后执行;但是在竞争条件下,都会等获取到锁了才可以往下执行。

Q:请问线程的休眠状态有几种?
A:有三种, 第一种是blocked(阻塞),由synchronized锁将线程释放监视器锁,线程进入阻塞状态。第二种是waiting(等待),这种方法就是调用Object.wait()或Object.join()方法让线程释放对象锁,使其休眠。第三种是timed.waiting(有时间的等待),这种方法一般是调用Thread.sleep(time),Object.wait(time),Object.join(time)等方法使其休眠,无锁竞争下,到时间也会直接重新获取锁往后执行。

Q: 线程currentThread()与interrupt()方法是做什么用的(简单说明)?
A:1. currentThread()方法是获取当前线程。2. interrupt()唤醒休眠线程,休眠线程发生InterruptedException异常

Q:JVM启动时是单线程的还是多线程的?
A:当然是多线程的,因为它不仅自己的启动main需要线程,它的回收GC线程也是需要线程启动的,这就说明了是多线程的。


以上确实是线程比较基础的知识,那么往后的多线程有许多锁,锁优化,JUC锁框架,CAS,以及上下文切换的优化,死锁等等知识敬请期待后续肝出来!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值