1. 如何创建线程?

四种:

  • 继承Thread 重写run方法
//定义一个子类继承Thread类,并重写run方法
//创建Thread的子类对象
//调用start方法启动线程(启动线程后,会自动执行run方法中的代码)
public class MyThread extends Thread{
    // 必须重写Thread类的run方法
    @Override
    public void run() {
        // 描述线程的执行任务。
        for (int i = 1; i <= 5; i++) {
            System.out.println("子线程MyThread输出:" + i);
        }
    }
}

测试类:
public class ThreadTest1 {
    // main方法是由一条默认的主线程负责执行。
    public static void main(String[] args) {
        // 3、创建MyThread线程类的对象代表一个线程
        Thread t = new MyThread();
        // 4、启动线程(自动执行run方法的)
        t.start(); 

        for (int i = 1; i <= 5; i++) {
            System.out.println("主线程main输出:" + i);
        }
    }
}
  • 实现Runable接口重写run
/**
 * 1、定义一个任务类,实现Runnable接口
 */
public class MyRunnable implements Runnable{
    // 2、重写runnable的run方法
    @Override
    public void run() {
        // 线程要执行的任务。
        for (int i = 1; i <= 5; i++) {
            System.out.println("子线程输出 ===》" + i);
        }
    }
}

测试类:
public class ThreadTest2 {
    public static void main(String[] args) {
        // 3、创建任务对象。
        Runnable target = new MyRunnable();
        // 4、把任务对象交给一个线程对象处理。
        //  public Thread(Runnable target)
        new Thread(target).start();

        for (int i = 1; i <= 5; i++) {
            System.out.println("主线程main输出 ===》" + i);
        }
    }
}
  • 实现Callable接口重写 call方法
/**
 * 1、让这个类实现Callable接口
 */
public class MyCallable implements Callable<String> {
    private int n;
    public MyCallable(int n) {
        this.n = n;
    }

    // 2、重写call方法
    @Override
    public String call() throws Exception {
        // 描述线程的任务,返回线程执行返回后的结果。
        // 需求:求1-n的和返回。
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return "线程求出了1-" + n + "的和是:" + sum;
    }
}

测试类:
public class ThreadTest3 {
    public static void main(String[] args) throws Exception {
        // 3、创建一个Callable的对象
        Callable<String> call = new MyCallable(100);
        // 4、把Callable的对象封装成一个FutureTask对象(任务对象)
        // 未来任务对象的作用?
        // 1、是一个任务对象,实现了Runnable对象.
        // 2、可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果。
        FutureTask<String> f1  = new FutureTask<>(call);
        // 5、把任务对象交给一个Thread对象
        new Thread(f1).start();


        Callable<String> call2 = new MyCallable(200);
        FutureTask<String> f2  = new FutureTask<>(call2);
        new Thread(f2).start();


        // 6、获取线程执行完毕后返回的结果。
        // 注意:如果执行到这儿,假如上面的线程还没有执行完毕
        // 这里的代码会暂停,等待上面线程执行完毕后才会获取结果。
        String rs = f1.get();
        System.out.println(rs);

        String rs2 = f2.get();
        System.out.println(rs2);
    }
}
  • 使用线程工厂ThreadFactory
ExecutorService pool = new ThreadPoolExecutor(
    3,	//核心线程数有3个
    5,  //最大线程数有5个。   临时线程数=最大线程数-核心线程数=5-3=2
    8,	//临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。
    TimeUnit.SECONDS,//时间单位(秒)
    new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在,任务队列中等待
    Executors.defaultThreadFactory(), //用于创建线程的工厂对象
    new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略
);

2. 线程有几种状态?

NEW 【新建状态 new Thread()】。一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t=new MyThread(),只有线程象,没有线程特征。

RUNNABLE 【可运行的状态 thread.satrt()】。当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态,也就是说它具备执行资格,但是并没有真正的执行起来而是等待CPU的调度。
	
BLOCKED,【阻塞状态 wait方法 或者 等待管程锁的时候】。当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runable状态。
	
WAITING,【等待状态 wait, join,无参 LockSuport.park 】。一个正在等待的线程的状态,也称之为等待状态。造成线程等待状态的原因有两种,分别是:调用Object.wait()、join()方法;处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
	
TIMED_WAITING, 【wait,sleep, join 带参数】。一个在限定时间内等待的线程的状态,也称之为现实等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long),join(long)。
	
TERMINATED 【终止状态 线程执行完成进入状态】。一个完全运行完成的线程的状态。也称之为终止状态、结束状态。

3. 线程池7大参数,4个拒绝策略

7大参数:
corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:临时线程存活时间
unit:时间单位
workQueue:线程任务等待队列
threadFactory:线程工厂
handler:线程池任务拒绝策略

4个拒绝策略:
AbortPolicy:默认策略,当任务无法被执行时,ThreadPoolExecutor会抛出RejectedExecutionException异常。

CallerRunsPolicy:当任务无法被执行时,ThreadPoolExecutor会将任务退回给调用线程来执行。换句话说,由调用线程来执行该任务,而不是交给线程池中的线程来执行。

DiscardPolicy:当任务无法被执行时,ThreadPoolExecuor会默默的丢弃无法被执行的任务,不会给出任何警告和错误信息。

DiscardOldestPolicy;当任务无法被执行时,ThreadPoolExecutor会丢弃队列中最旧的任务,然后尝试将新任务加入队列。

  除了上述默认的拒绝策略,也可以通过实现RejectedExecutionHandler接口来定义拒绝策略,并将其作为参数传递给ThreadPoolExecutor的构造函数。这样可以根据具体的需求进行灵活的处理。

4. 最大线程数什么时候创建?

  最大线程数是在操作系统启动时创建的。当操作系统启动时,它会根据系统资源和配置设置来确定最大线程数。这个值通常是一个固定的数目,可以根据操作系统和硬件配置的的不同而有所差异。
  值得注意的是,最大线程数实在操作系统级别管理的,它决定了系统可以同时运行的线程数量。每个线程需要一定的系统资源,包括内存和处理器时间。如果线程数超过了最大限制,系统可能会变得不稳定或者出现性能问题。
  在实际应用程序开发中,通常会根据具体的需求和系统配置来控制线程的数量,以确保系统运行的稳定性和性能。

5. shoutdown和shoutdownNow方法的区别?

shoutdown方法会等线程运行完毕后,关闭线程;
shoutdownNow方法会立刻关闭线程,并强制停止正在运行的线程。

6. 怎么参考设置核心线程数的具体值?

IO密集型:CPU核数*2;CPU密集型:CPU核数+1

  IO密集型是指在执行过程中主要涉及输入/输出操作的任务或应用程序。这类任务的执行过程中,大部分的时间都花费在等待IO操作完成上,而不是在CPU计算上。
  典型的IO密集型任务包括读写文件、网络通信、数据库查询等操作。当一个任务需要频繁地与外部资源进行2交互,如从磁盘读取大量数据、通过网络传输大量数据或者执行频繁的数据库查询时,就会导致任务的执行时间主要由IO操作的速度决定,而不是CPU的运算能力。

  CPU密集型是指在执行过程中主要依赖于CPU计算能力的任务或应用程序。这类任务的执行过程中,大部分的时间都花费在进行复杂的数学运算、逻辑判断和算法处理上,而不是在IO操作上。
  典型的CPU密集型任务包括科学计算、图像处理、密码学算法、编码/解码操作、大规模数据分析等。这些任务通常要求大量的CPU计算资源,并且其执行时间主要取决于CPU的性能和速度。