什么是线程
线程就是某些进程内部还需要执行多个子任务,我们就把子任务成为线程,线程是进程划分为的更小的运行单位。
进程和线程的关系就是:一个进程可以包含一个或多个线程,但是至少有一个主线程。
与进程不同的是,同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或者在各个线程之间做切换工作的时候,负担要比进程小得多。
进程与线程的区别
根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位;
资源开销:每个进程都有独立的代码副本和数据空间,进程之间的切换,资源开销较大;线程可以看做轻量级的进程,每个线程都有自己 独立的运行栈和程序计数器,线程之间切换,资源开销小;
包含关系:一个进程内包含有多个线程,在执行过程,线程的执行不是线性串行的,而是多条线程并行共同完成;
内存分配:同一进程内的所有线程共享本进程的内存空间和资源;进程之间的内存空间和资源相互独立;
影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响;一个线程崩溃,会导致整个进程退出。所以多进程要比多线程健壮;
执行过程:每个独立的进程有程序运行的入口和程序出口。但是线程不能独立执行,必须依存在应用程序(进程)中,由应用程序提供多个线程执行控制;
线程的创建方式
线程又分为单线程和多线程,单线程就是进程中只有一个线程;多线程是由一个以上的线程组成的程序,在Java中一定是从主线程开始执行。
方式一:继承Thread类
/ 创建线程方式1:继承Thread类 重写run方法
public class Test02 {
public static void main(String[] args) throws InterruptedException {
// 每个线程都是一个Thread对象
System.out.println("主线程执行开始......................");
// 创建子线程1(继承Thread类)
Thread thread1 = new Thread("线程1") {
@Override
public void run() {
for (int i = 0; i <= 26; i++) {
System.out.println("[线程1]" + i);
}
}
};
// 创建子线程2
Thread thread2 = new Thread("线程2") {
@Override
public void run() {
for (char c = 'A'; c < 'z'; c++) {
System.out.println("[线程2]" + c);
}
}
};
// thread1.join();// 阻断线程
// thread2.join();
// 设置优先级
thread1.setPriority(1);
thread2.setPriority(10);
thread1.start();// 启动线程
thread2.start();// 启动线程
System.out.println("主线程执行结束......................");
}
}
1、创建Thread对象;
2、匿名类实现Thread类中的run方法 ;
3、然后调用start方法启动线程。
方式二:实现Runnable接口
// 创建线程方式2:实现Runnable接口
public class Test03 {
public static void main(String[] args) {
// 每个Runnable的实现类,封装了线程执行逻辑
EmailTask email = new EmailTask();
// 创建三个线程
Thread t1 = new Thread(email, "线程1");
Thread t2 = new Thread(email, "线程2");
Thread t3 = new Thread(email, "线程3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
class EmailTask extends Task implements Runnable {
@Override
public void execute() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取线程名称
String name = t.getName();
System.out.println(name + "发送邮件成功");
}
@Override
public void run() {
execute();
}
}
abstract class Task {
public abstract void execute();
}
方式三:实现Callable接口,允许子线程返回结果,抛出异常
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 线程创建方法3:实现Callable接口
public class Test04 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// Callable接口实现类:不同数据范围的计算任务
SumCalcTask sumTask1 = new SumCalcTask(1, 300);
SumCalcTask sumTask2 = new SumCalcTask(301, 500);
SumCalcTask sumTask3 = new SumCalcTask(501, 1000);
SumCalcTask sumTask = null;
// Callable------> FutureTask(Runnable接口实现类)
FutureTask<Integer> ftask1 = new FutureTask<Integer>(sumTask1);
FutureTask<Integer> ftask2 = new FutureTask<Integer>(sumTask2);
FutureTask<Integer> ftask3 = new FutureTask<Integer>(sumTask3);
// 创建并启动线程
Thread th1 = new Thread(ftask1);
Thread th2 = new Thread(ftask2);
Thread th3 = new Thread(ftask3);
// ftask1.run() ----> Callable.call() --->结果 --->outcome
th1.start();
th2.start();
th3.start();
// 线程执行结束,分别获取线程执行返回的结果
Integer sum1 = ftask1.get();
Integer sum2 = ftask2.get();
Integer sum3 = ftask3.get();
Integer res = sum1 + sum2 + sum3;
System.out.println(res);
}
}
// 通过Callable实现类SumCalcTask封装某个范围数据的累加和
class SumCalcTask implements Callable<Integer> {
private int begin, end;
public SumCalcTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
public Integer call() throws Exception {
int res = 0;
for (int i = begin; i <= end; i++) {
res += i;
}
return res;
}
}
1、实现Callable接口需要创建一个FutureTask来接受Callable;
2、FutureTask是Runnable的实现类;
3、 最后同样创建Thread对象启动线程。
方式四:线程池
线程池,按照配置参数(核心线程数、最大线程数等)创建并管理若干线程对象。程序中如果需要使用线程,将一个执行任务传给 线程池,线程池就会使用一个空闲状态的线程来执行这个任务。执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状 态,等待执行下一个任务。使用线程池可以很好地提高性能。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 创建线程方法4:通过线程池创建
public class Test05 {
public static void main(String[] args) {
// 创建固定数量的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 不确定数量的线程请求
while (true) {
// 向线程池提交一个执行任务(Runnable接口实现类对象)
// 线程池分配一个“空闲线程”执行任务
// 如果没有空闲线程,则该任务加入等待队列 (工作队列)
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "进行了1次投票");
try {
Thread.sleep(1000);// 当前线程休眠1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
1、使用Executors.newFixedThreadPool(10)创建一个固定大小的线程池;
2、调用execute方法启动线程。
扩展:线程池执行Callable接口封装的线程任务
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
// 线程池执行Callable接口封装的线程任务
public class Test06 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// Callable接口实现类:不同数据范围的计算任务
SumCalcTask sumTask1 = new SumCalcTask(1, 300);
SumCalcTask sumTask2 = new SumCalcTask(301, 500);
SumCalcTask sumTask3 = new SumCalcTask(501, 1000);
// 线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 提交线程任务
Future<Integer> f1 = executorService.submit(sumTask1);
Future<Integer> f2 = executorService.submit(sumTask2);
Future<Integer> f3 = executorService.submit(sumTask3);
int res1 = f1.get();
int res2 = f2.get();
int res3 = f3.get();
System.out.println(res1 + res2 + res3);
// 关闭线程池
executorService.shutdownNow();
}
}
1、同样使用一个Future对象来接受线程任务;
2、最后关闭线程池。
归根结底,其实创建线程的方法只有一种:创建Thread对象!!!!!!