概念
-
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合,即一段静态的代码,静态对象
-
进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡过程,(生命周期)
-
比如:运行中的微信、运行中的网易云音乐播放器
-
程序是静态的,而进程是动态的
-
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
-
-
线程(thread) 进程进一步细化即为线程,是一个程序内部的一条执行路径
-
若一个进程同一时间可以并行执行多个线程,就是支持多线程的
-
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的花销小
-
一个进程的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通讯功能简便、高效。但多个线程操作共享的系统资源可能会带来安全隐患,这就需要线程的同步
-
单核CPU和多核CPU的理解
在单核CPU中,我们似乎也能感受到多线程,但是其是一个假的多线程。因为单核CPU在单位时间元内,只能执行一个线程的任务。但是CPU的时间单元特别短,因此我们感觉不出来。比如:在超市购物时,有多个购物口,但是只有一个收银员,顾客只有付款后才能离开。此时CPU就好比收银员。若某个人网络不好,付款失败,那么收银员就可以先让他等网络好了再来付款,然后让其他人先过来付款。因为收银员的提醒时间非常短(CPU的时间单元特别短),因此后面的顾客感觉不出来有阻塞。
现在我们身边的设备,例如智能手机、电脑、服务器,它们的CPU都是多核的,多核CPU才能极致地发挥出多线程的效率。
一个Java应用程序 java.exe, 其至少拥有三个线程,分别是:main()主线程 、gc()垃圾回收线程、异常处理线程。如果应用程序在执行中发生了异常,则会影响主线程。
并行与并发
-
并行:多个CPU同时执行多个任务(多人干多件事)。
-
并发:一个CPU(采用时间片)同时执行多个任务(电商秒杀活动、多个人做一件事)
使用多线程的优点
问题:以单核CPU为例,只使用单个线程先后完成多个任务,可能比多个线程来完成用的时间更短,为何仍然需要多线程呢?
多线程程序的优点:
-
-
提高应用程序的响应能力,对于满足图形化界面的设置更有意义,可以增强用户的使用体验
-
提高计算机CPU的利用率
-
改善程序结构,将一些执行流程长且复杂的进程分为多个线程,独立运行,方便开发人员进行开发、维护。
-
何时需要用到多线程
-
程序需要同时执行多个任务
-
程序需要实现一些需要等待的任务,比如用户输入、文件读写操作、付款等
-
需要一些后台运行的程序等
创建多线程的四种方法
方法一:继承Thread类
public class ThreadTest {
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
MyThread2 myThread2 = new MyThread2();
myThread1.start();
myThread2.start();
}
}
class MyThread1 extends Thread{
@Override
public void run() {
for(int i = 0; i < 100; i++){
if(i % 2 == 0) System.out.println(Thread.currentThread().getName() + "偶数:" + i);
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for(int i = 0; i < 100; i++){
if(i % 2 != 0) System.out.println(Thread.currentThread().getName() + "奇数:" + i);
}
}
}
上述代码中我们创建了两个线程,让其分别输出100以内的奇数和偶数。在主程序中,我们创建其实例对象,执行线程的start()方法,即可看到控制台输出效果:
可见两个线程是交替打印的。
方法二:继承Runnable接口
public class ThreadTest {
public static void main(String[] args) {
MyThread3 myThread3 = new MyThread3();
Thread myThread = new Thread(myThread3);
myThread.start();
}
}
class MyThread3 implements Runnable{
@Override
public void run() {
System.out.println("66666");
}
}
在该实现方法中,是先创建一个继承Runnable接口的类,然后在主程序中,利用这个类,创建Thread的实例化对象,再调用start() 方法即可。此外,还有一个更简洁的写法,即创建匿名对象:
public class ThreadTest {
public static void main(String[] args) {
//匿名创建
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名创建的线程...");
}
}).start();
}
}
比较方法一和方法二
-
-
开发中应优先选择方法二,原因是:避免了类单继承的局限性、更适合用来处理有共享数据的情况
-
接下来两种创建多线程的方法是jdk5.0新增的方法。
方法三:实现Callable接口
public static void main(String[] args) {
MyThread4 myThread4 = new MyThread4();
//将此对象作为参数,传入FutureTask构造器中,创建FutureTask对象
FutureTask<Integer> futureTask = new FutureTask<>(myThread4);
//将futureTask作为参数,传入Thread的构造方法中,创建Thread对象
Thread thread = new Thread(futureTask);
thread.start();
try{
//获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
Integer sum = futureTask.get();
System.out.println("[1,100]中所有偶数总和为:" + sum);
}catch (InterruptedException e){
e.printStackTrace();
}catch (ExecutionException e1){
e1.printStackTrace();
}
}
//创建线程方法3:实现callable接口
class MyThread4 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 1;i <= 100; i++) {
if(i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
与使用Runnable相比,Callable功能更为强大
-
-
相比run()方法,可以有返回值
-
方法可以抛出异常
-
支持泛型的返回类
-
获取结果需要借助FutureTask类
-
方式四:使用线程池
在实际开发中,需要频繁地创建和销毁、使用量特别大的资源(比如高并发下的线程),一般都是使用线程池来创建多线程。
线程池,顾名思义即存放线程的池子,与此相似的是数据库连接池。
实现思路:提前创建好多个线程,放入线程池中,使用时直接通过线程池获取,使用完成后放回池子中。这样,就实现了重复利用,避免频繁地创建销毁,提高了系统的性能。
使用线程池的好处~~
-
-
提高响应速度(减少了创建、销毁线程的时间)
-
降低资源消耗(重复利用线程池中的线程)
-
便于线程管理
-
话不多说,上代码~~
public class ThreadPoolTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//1、提供指定线程数量的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//执行指定线程的操作,需要提供实现Runnable接口或者Callable接口实现的对象
executorService.execute(myThread); //适用于Runnable
//executorService.submit(Callable callable); //适用于Callable
//关闭连接池
executorService.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
for(int i = 0; i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
控制台输出
线程池相关API
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
ExecutorService:线程池接口