正文
一、为什么要有多线程
随着计算机的发展,单核的CPU发展到多核的CPU,CPU的性能越来越高,为了充分发挥CPU的计算性能和提高CPU硬件资源的利用率于是在进程的基础上演变出了多线程。使用多线程可以提高程序效率,快速响应给客户端,给用户更加好的体验,每个线程之间并行执行互不影响。
二、名词解释
1、进程和线程
进程:进程是一个具有独立功能的程序(例如 QQ、微信、酷狗等应用),进程是系统进行资源分配和调度的一个独立单位。那么什么是java程序的进程呢?Java编写的程序都运行在Java虚拟机(JVM)中,每当使用Java命令启动一个Java应用程序时,就会启动一个JVM进程,这个应用就是一个JAVA进程。
线程:线程是进程的一个实体,是指“进程代码段”的一次顺序执行流程。线程是CPU调度的最小单位。一个进程可以有一个或者多个线程(例如 QQ是一个进程,发消息是一个线程,视频也是一个线程),各个线程之间共享进程的内存空间、系统资源,进程是操作系统资源分配的最小单位。
2、并发和并行
串行:一次只能执行一个任务,并且这个任务执行完之后才能执行下一个任务。
并行:当系统有一个以上CPU时,则线程的操做有可能非并发.当一个CPU执行一个线程时,另外一个CPU能够执行另外一个线程,两个线程互不抢占CPU资源,能够同时进行,这种方式称为并行。
注:进程也可以并行执行。
并发:并发是一种现象,同时运行多个程序或多个任务需要被处理的现象。这些任务可能是并行执行的,也可能是串行执行的,和CPU核心数无关,是操作系统进程调度和CPU上下文切换达到的结果。、
注:当有多个线程在运行时,若是系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间片段,再将时间片段分配给各个线程执行,由于CPU切换特别快对我们来说就像是并行执行,实际是线程之间在交替执行。
3、总结
进程和线程的区别
1、线程是进程的一个实体,是指“进程代码段”的一次顺序执行流程,一个进程由一个或者多个线程组成,一个进程中至少有一个线程。
2、线程是CPU的最小调度单位,进程是操作系统的最小调度单位
3、线程是出于高并发的调度诉求从进程中演变出来的。线程的出现充分发挥了多核CPU的优势,也弥补了进程调度过于笨重的问题。
4、进程之间是相互独立的,但进程内的线程之间并不独立,各个线程之间共享进程的方法区内存、堆内存、系统资源(文件句柄、系统信号等)。
5、切换速度不同,线程上下文切换比进程要快的多,因此线程也称为轻量级进程。
并发和并行的区别
并发和并行是两个相似但又有区别的概念。它们都可以表示两个或者多个任务一起执行,但是侧重点有所不同。并发偏向于多个任务交替执行,而多个任务之间还可以是串行的。而并行就是指同时执行。
三、创建线程
1、继承Thread类
package com.xiaojie.juc.thread.mytest; /** * @author xiaojie * @version 1.0 * @description: 继承的方式创建线程 * @date 2021/12/11 22:34 */ public class ExtendsDemo{ public static void main(String[] args) { System.out.println("主线程执行,线程名称是:" + Thread.currentThread().getName()); new MyThread().start(); } static class MyThread extends Thread { @Override public void run() { System.out.println("子线程执行,线程名称是:" + Thread.currentThread().getName()); } } }
2、实现Runnable接口
/** * @author xiaojie * @version 1.0 * @description: 实现 Runnable * @date 2021/12/11 22:39 */ public class RunableDemo { public static void main(String[] args) { System.out.println("主线程执行,线程名称是:" + Thread.currentThread().getName()); // MyThread myThread = new MyThread(); // Thread thread=new Thread(myThread); // thread.start(); //或者 下面这种写法 Thread thread1 = new Thread(() -> { System.out.println("子线程执行,线程名称是:" + Thread.currentThread().getName()); }); thread1.start(); } static class MyThread implements Runnable { @Override public void run() { System.out.println("子线程执行,线程名称是:" + Thread.currentThread().getName()); } } }
3、实现Callable接口
package com.xiaojie.juc.thread.mytest; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @author xiaojie * @version 1.0 * @description: 实现Callable接口 * @date 2021/12/11 22:48 */ public class CallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { MyThread myThread = new MyThread(); FutureTask<String> futureTask = new FutureTask<String>(myThread); new Thread(futureTask).start(); //获取执行结果 System.out.println(futureTask.get()); } static class MyThread implements Callable<String > { //这种方式是获取现场执行结果,有返回值 @Override public String call() throws Exception { return "实现Callable接口的方式创建线程。。。。。。"; } } }
4、线程池创建线程
/** * @author xiaojie * @version 1.0 * @description: 基于线程池创建线程 * 有4中线程池,以后总结。 * @date 2021/12/11 22:55 */ public class ExecutorsDemo { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> System.out.println("基于线程池创建线程,线程名称为"+Thread.currentThread().getName())); } }
四、线程状态
public enum State { NEW, //新建 RUNNABLE,//运行状态 包含正在运行和就绪俩种状态 BLOCKED,//阻塞状态 WAITING,//等待状态 TIMED_WAITING,//限时等待 TERMINATED;//终止状态 }
1、在调用start()方法之后进入就绪状态,等待线程获取CPU时间片。
2、当线程获取到CPU的时间片时开始执行。
3、当CPU时间片用完之后就会再次进入就绪状态。
4、如果线程执行遇到synchronized关键字或者调用wait()、sleep()、join()等方法就会进入阻塞状态。
5、wait()方法遇到notify()或者notifuAll();join()等目标现成执行完毕等就会重新进入就绪状态。
6、当线程执行完毕,或者线程的run()方法发生异常,都会使线程进入终止状态
注意:
在调用线程实例的start()方法之后,线程可能不会马上执行而是等待CPU分配时间片给当前线程。
RUNNABLE状态包含了就绪和运行两个状态。
一旦线程新建之后,线程就不能再回到新建状态。同样线程进入终止状态之后,也不能再返回运行状态。
五、守护线程和线程优先级
守护线程
守护线程是进程运行时提供的某种后台服务的线程,比如GC线程。当用户线程都执行结束时,守护线程也会自动退出。守护线程需要在线程启动之前设置为守护线程
thread.setDaemon(true);
使用守护线程注意点:
1、守护线程必须在线程启动前设置。
2、守护线程存在被虚拟机强制终止的风险,所以守护线程中尽量不去访问系统资源,如文件句柄、数据库库连接。
3、守护线程创建的线程也是守护线程,如果显示调用thread.setDaemon(false),则新的线程可以调整为用户线程。
线程优先级
线程的调度方式是基于CPU的时间片方式进行线程调度。线程的调度模型主要有两种
分时调度模型:系统平均分配CPU的时间片,所有的线程轮流占用CPU。
抢占式调度模型:系统按照线程的优先级分配CPU时间片,优先级高的线程更容易获取到时间片而执行。
public static void main(String[] args) { System.out.println("主线程执行,线程名称是:" + Thread.currentThread().getName()); MyThread myThread1 = new MyThread(); myThread1.setName("thread1----"); myThread1.setPriority(10); MyThread myThread2 = new MyThread(); myThread2.setName("thread2----"); myThread2.setPriority(3); MyThread myThread3 = new MyThread(); myThread3.setName("thread3----"); myThread3.setPriority(1); myThread3.start(); myThread2.start(); myThread1.start(); }
运行结果如下图
注意:
1、线程的优先级最大是10,最小是1,超过这个范围就会报java.lang.IllegalArgumentException异常。
2、由图可见线程3执行优先于线程2。并不是说线程优先级高就一定先执行,而是优先级的高的线程更容易获取到CPU的时间片,先执行的机会就越多。
参考:
https://www.pdai.tech/md/java/thread/java-thread-x-theorty.html
《JAVA高并发核心编程(卷2):多线程、锁、JMM、JUC、高并发设计》-尼恩编著
《JAVA高并发程序设计》-葛一鸣著