1 进程和线程
进程:
-
进程是并发执行程序在执行过程中,资源分配和管理的基本单位。
-
进程可以理解为一个应用程序的执行过程,应用程序一旦执行,就是一个进程。
线程:
-
线程是进程的一个执行单元,是进程内可调度实体。
-
线程是比进程更小的独立运行的基本单位。
-
线程也被称为轻量级进程。
二者的区别:
名称 | 进程 | 线程 |
---|---|---|
地址空间 | 不同的进程之间的地址空间是独立的 | 同一进程的所有线程共享本进程的地址空间 |
资源拥有 | 进程之间的资源是独立的,无法共享 | 同一进程的所有线程共享本进程的资源 |
执行过程 | 每一个进程可以说就是一个可执行的应用程序 | 线程不能够独立执行,必须依存在应用程序中 |
2 创建线程的五种方式
2.1 继承 Thread
类
通过继承Thread
并且重写其run()
方法,run()
方法中定义需要执行的任务。
创建后的子类通过调用start()
方法即可执行线程方法。
注意: 通过继承Thread
创建的线程类,多个线程间无法共享线程类的实例变量。 需要创建不同Thread对象,自然不共享资源。
/*
定义线程类,继承Thread
重写run()方法
创建线程类对象
调用start()方法启动线程
*/
public class UserThread extends Thread {
@Override
public void run() {
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " is running " + i);
}
}
}
public class TestThread {
public static void main(String[] args) {
for(int i=0;i<2;i++){
new UserThread().start();
}
}
}
复制代码
输出如下:
Thread-0 is running 0
Thread-1 is running 0
Thread-1 is running 1
Thread-1 is running 2
Thread-0 is running 1
Thread-0 is running 2
2.2 实现 Runnable
接口
需要先定义一个类实现 Runnable
接口并重写该接口的 run()
方法,此run()
方法是线程执行体。
接着创建Runnable
实现类的对象,作为创建Thread
对象的参数target
,此Thread
对象才是真正的线程对象。
利用实现Runnable
接口的线程类创建对象,可以实现线程之间的资源共享。
/*
定义线程类,实现 Runnable接口
重写run()方法
创建实现类对象
创建Thread类,并将线程类对象参数传入Thread构造方法中
启动线程
*/
public class UserRunnable implements Runnable {
@Override
public void run() {
for(int i=0;i<3;i++) {
System.out.println(Thread.currentThread().getName() + " is running" + i);
}
}
}
public class TestUserRunnable {
public static void main(String[] args) {
UserRunnable userRunnable = new UserRunnable();
new Thread(userRunnable).start();
new Thread(userRunnable).start();
}
}
复制代码
输出如下:
Thread-0 is running 0
Thread-1 is running 0
Thread-1 is running 1
Thread-1 is running 2
Thread-0 is running 1
Thread-0 is running 2
2.3 实现 Callable
接口实现带有返回值的线程
Callable
接口如同 Runnable
接口的升级版,其提供的 call()
方法将作为线程的执行体,同时允许有返回值。
Callable
对象不能直接作为 Thread
对象的target,因为 Callable
接口是 Java5 新增接口,不是 Runnable
接口的子接口。
对于这个问题的解决方案,就引入 Future
接口,此接口可以接受 call()
的返回值,RunnableFuture
接口是 Future
接口和 Runnable
接口的子接口,可以作为 Thread
对象的target。
import java.util.concurrent.Callable;
/**
* 定义线程类UserCallable,实现Callable接口
* 重写call()方法
* 创建UserCallable对象
* 创建 FutureTask(实现了接口 RunnableFuture) 的对象,构造函数的参数是 UserCallable 的对象
* 创建 Thread 对象,构造函数的参数是 FutureTask 对象
* 启动线程
*/
public class UserCallable implements Callable {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName() + " 启动!");
return "我重写了call方法";
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestUserCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
UserCallable userCallable = new UserCallable();
FutureTask futureTask = new FutureTask(userCallable);
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}
复制代码
输出如下:
Thread-0 启动!
我重写了call方法
2.4 继承 TimerTask
Timer
和 TimerTask
可以作为实现线程的另一种方式。
Timer
是一种线程设施,用于安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行,可以看成一个定时器,可以调度 TimerTask
。
TimerTask
是一个抽象类,实现了 Runnable
接口,所以具备了多线程的能力。
多线程类
import java.util.Date;
import java.util.TimerTask;
/**
* 创建 UserTimer 类,继承 TimerTask 抽象类
* 创建 UserTimer 对象
* 创建 Timer 类对象,设置任务的执行策略
*/
public class UserTimer extends TimerTask {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running " + new Date());
}
}
复制代码
测试类
import java.util.Timer;
public class TestUserTimer {
public static void main(String[] args) {
UserTimer userTimer = new UserTimer();
Timer timer = new Timer();
timer.schedule(userTimer, 3000, 2000);
}
}
复制代码
输出如下:
Timer-0 is running Sat Sep 18 23:10:47 CST 2021
Timer-0 is running Sat Sep 18 23:10:49 CST 2021
Timer-0 is running Sat Sep 18 23:10:51 CST 2021
Timer-0 is running Sat Sep 18 23:10:53 CST 2021
Timer-0 is running Sat Sep 18 23:10:55 CST 2021
Timer-0 is running Sat Sep 18 23:10:57 CST 2021
2.5 通过线程池启动多线程
通过 Executors
的工具类可以创建线程池。
提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行。
降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗。
方便线程并发数的管控,因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM(内存溢出),并且会造成CPU过度切换。
2.5.1 线程池一:固定大小的线程池FixThreadPool(int n)
创建有固定线程数的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixThreadPoolTest {
public static void main(String[] args) {
//创建固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
//使用线程池执行任务
for(int i=0;i<5;i++){
executorService.submit(new Runnable() {
@Override
public void run() {
for(int j=0;j<3;j++){
System.out.println(Thread.currentThread().getName() + " : " + j);
}
}
});
}
executorService.shutdown();
}
}
复制代码
输出如下:
pool-1-thread-1 : 0
pool-1-thread-2 : 0
pool-1-thread-2 : 1
pool-1-thread-2 : 2
pool-1-thread-1 : 1
pool-1-thread-3 : 0
pool-1-thread-3 : 1
pool-1-thread-3 : 2
pool-1-thread-2 : 0
pool-1-thread-2 : 1
pool-1-thread-1 : 2
pool-1-thread-2 : 2
pool-1-thread-3 : 0
pool-1-thread-3 : 1
pool-1-thread-3 : 2
2.5.2 线程池二:单线程池 SingleThreadPoolExecutor
单线程串行执行任务,确保任务按提交顺序执行;
当线程异常结束后,会有新的线程代替之前的线程。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutor {
public static void main(String[] args) {
ExecutorService es = Executors.newSingleThreadExecutor();
for(int i=0;i<3;i++){
es.submit(new Runnable() {
@Override
public void run() {
for(int j=0;j<3;j++){
System.out.println(Thread.currentThread().getName() + " : " + j);
}
}
});
}
es.shutdown();
}
}
复制代码
输出如下:
pool-1-thread-1 : 0
pool-1-thread-1 : 1
pool-1-thread-1 : 2
pool-1-thread-1 : 0
pool-1-thread-1 : 1
pool-1-thread-1 : 2
pool-1-thread-1 : 0
pool-1-thread-1 : 1
pool-1-thread-1 : 2
2.5.3 线程池三:缓存线程池 CachedThreadPool()
线程池数量不固定,可以达到最大值。
线程可被重复利用和回收。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadExecutor {
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
for(int i=0;i<5;i++){
es.submit(new Runnable() {
@Override
public void run() {
for(int j=0;j<3;j++){
System.out.println(Thread.currentThread().getName() + " : " + j);
}
}
});
}
es.shutdown();
}
}
复制代码
输出如下:
pool-1-thread-2 : 0
pool-1-thread-2 : 1
pool-1-thread-2 : 2
pool-1-thread-5 : 0
pool-1-thread-5 : 1
pool-1-thread-5 : 2
pool-1-thread-4 : 0
pool-1-thread-4 : 1
pool-1-thread-4 : 2
pool-1-thread-3 : 0
pool-1-thread-3 : 1
pool-1-thread-1 : 0
pool-1-thread-1 : 1
pool-1-thread-1 : 2
pool-1-thread-3 : 2
2.5.4 线程池四:周期性的线程池 newScheduledThreadPool()
创建一个周期性的线程池,支持定时及周期性执行任务
创建线程时,指定核心线程数,当执行任务较多超过核心线程时,可额外启动新的线程;
当任务恢复后,仅保留核心线程,其它额外线程将被关闭。
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadTest {
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(5);
ses.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行 " + new Date());
}
}, 5, 3, TimeUnit.SECONDS);
}
}
复制代码
输出如下:
pool-1-thread-1 执行 Sun Sep 19 00:58:55 CST 2021
pool-1-thread-1 执行 Sun Sep 19 00:58:58 CST 2021
pool-1-thread-2 执行 Sun Sep 19 00:59:01 CST 2021
pool-1-thread-1 执行 Sun Sep 19 00:59:04 CST 2021
pool-1-thread-3 执行 Sun Sep 19 00:59:07 CST 2021
pool-1-thread-3 执行 Sun Sep 19 00:59:10 CST 2021
2.5.5 线程池五:新的线程池类 ForkJoinPool
的扩展 newWorkStealingPool
JDK1.8增加, 任务窃取线程池,线程有属于自己的队列,更加适用于多核心处理器。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class WorkStealingPoolTest {
public static void main(String[] args) {
ExecutorService es = Executors.newWorkStealingPool();
for(int i=0;i<10;i++){
es.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---END----");
}
}
复制代码
输出如下:
ForkJoinPool-1-worker-1
ForkJoinPool-1-worker-1
ForkJoinPool-1-worker-1
ForkJoinPool-1-worker-2
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-2
ForkJoinPool-1-worker-1
---END----