1、线程的四种创建方式

1.1、继承Thread类

示例代码:

Thread thread1 = new MyThread();
Thread thread2 = new MyThread();
thread1.start();
thread2.start();

运行结果:
在这里插入图片描述

1.2、实现Runnable接口

代码示例:

MyRunnable runnable1 = new MyRunnable();
MyRunnable runnable2 = new MyRunnable();
Thread run1 = new Thread(runnable1);
Thread run2 = new Thread(runnable2);
run1.start();
run2.start();

运行结果:
在这里插入图片描述

1.3、实现Callable接口

它配合FutureTask才能使用,具体做法上必须先创建Callable的对象,再使用它创建FutureTask(泛型类)的对象,指定泛型参数。最后再使用FutureTask对象创建Thread对象才能启动线程。
代码示例:

MyCallable callable1 = new MyCallable();
MyCallable callable2 = new MyCallable();
FutureTask<Boolean> futureTask1 = new FutureTask<>(callable1);
FutureTask<Boolean> futureTask2 = new FutureTask<>(callable2);
Thread call1 = new Thread(futureTask1);
Thread call2 = new Thread(futureTask2);
call1.start();
call2.start();

运行结果:
在这里插入图片描述

1.4、使用线程池

代码示例:

ExecutorService service = new ThreadPoolExecutor(
    2,
    2,
    0L,
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>()
);

// 只支持Runnable和Thread(它其实就是Runnable实现类)
service.execute(() -> {
    System.out.println("线程池创建的线程" + Thread.currentThread().getName() + "启动了");
});

// 可支持Callable
service.submit(() -> {
    System.out.println("线程池创建的线程" + Thread.currentThread().getName() + "启动了");
});

service.shutdown();

运行结果:
在这里插入图片描述

2、线程池核心参数相关

2.1、非核心线程空闲时的行为

先说结论:线程池的非核心线程在创建并执行完任务后,如果设置的存活时间不为零,那么它会尝试从队列中取任务执行,而不是等待队列放满再取任务。
我们知道线程池的核心参数分别是,核心线程数、最大线程数、空闲线程存活时间、时间单位、阻塞队列。任务执行流程是:新任务到来时,核心线程空闲就交给核心线程处理,否则就放进阻塞队列。队列已满就把任务交给非核心线程,如果非核心线程也都在忙碌状态,会执行拒绝策略。这句结论容易造成一个误解就是非核心线程只有队列满时才处理任务,其实不然,昨天我测试时无意间发现了我上面总结的这个结论。

测试代码如下:

package com.company;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPool {
    public static void main(String[] args) {
			//参数按照顺序分别是:核心线程、最大线程、存活时间、时间单位、阻塞队列
        ExecutorService service = new ThreadPoolExecutor(2,
                4,
                5,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(4));
        Scanner scanner = new Scanner(System.in);
        int sum = 1;
        while (sum > 0) {
            System.out.println("输入需要执行的任务数量:");
            sum = scanner.nextInt();
            if (sum > 0) {
                handle(sum, service);
            }
            //Thread.sleep((long) 5000L * sum);
        }
    }

    public static void handle(int sum, ExecutorService service) {
        for (int i = 0; i < sum; i++) {
            service.submit(() -> {
                System.out.println("--------------------使用线程池执行线程" + Thread.currentThread().getName() + " ");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

运行时截图:
在这里插入图片描述
纠正:图片中所说的新任务不进队列,不准确,应该是新任务先进队列,但是马上被非核心线程取走了。

2.2 拒绝策略抛出异常后主线程行为

结论:主线程(提交任务的线程)会因为抛出异常而终止,而线程池不受影响,哪怕是在主线程创建的。

运行时截图:
在这里插入图片描述

3、线程的生命周期

Java线程包括6种状态:
NEW: 线程对象刚创建,还没调用start()。
RUNNABLE: 正在运行,或者正在等待CPU调度。
BLOCKED: 被阻塞,在等synchronized锁。
WAITING: 无限期等待,等另一个线程显式唤醒。比如Object.wait()没超时参数,Thread.join(),LockSupport.park()。
TIMED_WAITING: 有时限的等待,到了时间自动醒。比如Thread.sleep(),Object.wait(1000),thread.join(1000)。
TERMINATED: 线程运行结束,退出了。

测试代码如下:

private static void testLife() throws Exception {
    Object lock = new Object();
    Thread thread = new Thread(() -> {
        synchronized (lock) {
            System.out.println("新建线程开始执行");
            try {
                Thread.sleep(2000);
                // 主动释放手里的锁
                lock.wait();
                System.out.println("新建线程继续执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    
    thread.start();
    System.out.println("主线程运行中");
    Thread.sleep(2100);
    
    // 获取到锁才能调用notify或notifyAll方法
    synchronized (lock) {
        // 注意:调用notify后不会立即释放锁
        lock.notify();
        System.out.println("主线程持有锁,继续运行");
        Thread.sleep(2000);
    }
}

说明:
1、先创建新线程,执行2秒后主动释放锁。线程状态NEW -> RUNNABLE->WAITING
2、此时同步运行主线程,主线程2.1秒尝试获取锁。
3、主线程获取锁之后,调用notify的方法,唤醒线程,但是主线程没有释放锁,所以唤醒后依然没有运行。线程状态WAITING->BLOCKED。
4、主线程同步代码块执行完之后,释放锁,新线程获取锁,继续执行直到结束。线程状态BLOCKED->RUNNABLE->TERMINATED

运行时截图
在这里插入图片描述

4、ReentrantLock使用

ReentrantLock 是 API 层面的锁,加锁后必须手动在finally中unlock释放锁
测试代码如下:

private static void testReentrantLock() throws Exception {
    ReentrantLock lock = new ReentrantLock();

    Thread thread1 = new Thread(() -> {
        lock.lock();
        try {
            System.out.println("线程" + Thread.currentThread().getName() + "获取锁并执行");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    });

    Thread thread2 = new Thread(() -> {
        if (lock.tryLock()) {
            System.out.println("线程" + Thread.currentThread().getName() + "获取锁成功");
        } else {
            System.out.println("线程" + Thread.currentThread().getName() + "获取锁失败");
        }
    });

    Thread thread3 = new Thread(() -> {
        if (lock.tryLock()) {
            System.out.println("线程" + Thread.currentThread().getName() + "获取锁成功");
        } else {
            System.out.println("线程" + Thread.currentThread().getName() + "获取锁失败");
        }
    });

    thread1.start();
    thread2.start();
    Thread.sleep(2500);
    thread3.start();
}

说明:
线程1会先获取锁并持有2秒;线程2立即尝试获取锁(因锁被持有会失败);线程3在2.5秒后尝试(此时锁已释放会成功)。

运行时截图
在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐