编译软件:IntelliJ IDEA 2019.2.4 x64
运行环境:win10 家庭中文版
jdk版本:1.8.0_361
提示:以下是本篇文章正文内容,下面案例可供参考
一、进程与线程的相关概念
1.1 程序
它是选择一种编程语言,完成一个功能/任务,而编写的一段代码,这段代码最后被编译/解释为指令程序是一组指令的集合。
程序是静态的。当我们电脑、手机安装了一个程序之后,只是占用硬盘/存储卡的空间。
1.2 进程
①一个程序的一次运行
按快捷键 CTRL+SHIFT+ESC ,调出任务管理器,如下所示:
当程序启动后,操作系统部会给这个程序分配一个进程的ID, 并且会给他分配一块独立的内存空间。
②如果一个程序被启动了多次,那么会有多个进程。
如下所示:
多个进程之问是无法共享数据。
如果两段代码需要进行数据的交互,成本比较高,要么遇过一个硬盘的文件,要么通过网络。
进程之间的切换成术也比较高。现在的操作系统都支持多任务,嫌作系统在每一个任务之同进行切换,要给整个进程做镜像,要记录当前进程的状态,执行到哪个指令的.
1.3 线程
进程中的其中一条执行路径。
多个线程会同属于一个进程。
这多个线程会共享同一个进程中的一些资源。
比如Java中堆内存的数据,方法区的数据。
如下所示:
如果同一个进程的两段代码需要进行数据的交互,非常方便,可以直接在内存中共享。当然,同时要考虑安全问题。进程之间的切换,需要记录的信息要少很多,因为很多线程之间的数据是共享的,这些数据就不用单独在做镜像了,只需要记录每一个线程要执行的下一条指令。
二、并发与并行
- 并行(parallel):指两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令在多个处理器上同时执行。
- 并发(concurrency):指两个或多个事件在同一个时间段内发生。指在同一个时刻只能有一条指令执行,但多个进程的指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
如下图所示:
在操作系统中,启动了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一个程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。
例子:
- 并行:多项工作一起执行,之后再汇总,例如:泡方便面,电水壶烧水,一边撕调料倒入桶中
- 并发:同一时刻多个线程在访问同一个资源,多个线程对一个点,例如:春运抢票、电商秒杀…
三、线程调度
有两种调度模型:分时调度模型和抢占式调度模型。
- 分时调度模型
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。 - 抢占式调度模型优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
- 抢占式调度详解
大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。
实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,斜体样式使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。
四 、线程的创建与使用
4.1 线程的创建
java中开启一个线程的方共有四种:
1.继承Thread类
2.实现Runnable接口
3.实现Callable接口
4. 线程池
ps:本文今天暂且只介绍前两种,后两种在后期再行探讨。
4.1.1 继承Thread类
Java使用java.lang.Thread
类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下:
(1) 编与一个类,让它继承Thread类
(2)重写父类的public void run()}这个run()方法不是由程序员调用的,而且线程调度时,自动调用。要让一个线程做什么事,必须把这个代码写到run 中把run()方法的方法体,称为线程体。
(3) 创建自定义线程类的对象
(4)启动线程,调用自定义线程类的对象的start()
代码演示如下:
public class TestMyThread { public static void main(String[] args) { MyThread myThread=new MyThread(); myThread.start();//启动线程 for (int i = 1; i <=10 ; i+=2) { System.out.println("main线程打印【1-10】之间的奇数:"+i); } } } class MyThread extends Thread{ @Override public void run() { for (int i = 1; i <=10 ; i++) { if (i%2==0){ System.out.println("自定义线程打印【1-10】之间的偶数:"+i); } } } }
4.1.2 实现Runnable接口
Java有单继承的限制,当我们无法继承Thread类时,那么该如何做呢?在核心类库中提供了Runnable接口,我们可以实现Runnable接口,重写run()方法,然后再通过Thread类的对象代理启动和执行我们的线程体run()方法
步骤如下:
(1)编写线程类,实现Runnable接口
(2)重写接口的抽象方法public void run()
(3) 创建自定义线程类的对象
(4)创建一个Thread类的对象,同时让Thread对象代理我们的自定义线程对象创建它的目的是为了调用start方法
(5)启动线程
线程调度器会调用t对象的run方法,因为这里启动的是t线程 ,(t.start())
Thread类的run() @Override public void run() { if(target != null) { (target.run(); } }
这里的target对象就是创建Thread类对象时传入的Runnable接口的实现类对象,即被代理对象。
代码演示如下:
public class test { public static void main(String[] args) { TestRunnable tr=new TestRunnable(); Thread thread=new Thread(tr); thread.start(); //启动线程 } } class TestRunnable implements Runnable{ @Override //线程体 public void run() { for (int i = 1; i <=10 ; i++) { if (i%2==0){ System.out.println("自定义线程打印【1-10】之间的偶数:"+i); } } } }
通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现,Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
💡tips:
Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。
4.3 使用匿名内部类对象来实现线程的创建和启动
①使用匿名内部类对象继承Thread类
代码演示如下:
new Thread(){ @Override public void run() { //依次打印1-10 System.out.println("使用匿名内部类创建Thread对象打印1-10:"); for (int i = 1; i <=10 ; i++) { System.out.print(i+"\t"); } } }.start();
②使用匿名内部类对象实现Runnable接口
代码演示如下:
new Thread(new Runnable(){ @Override public void run() { //依次打印1-10 System.out.println("使用匿名内部类实现Runnable接口创建的线程打印1-10:"); for (int i = 1; i <=10 ; i++) { System.out.print(i+"\t"); } } }).start();
4.2 线程的使用
4.2.1 Thread类
4.2.1.1 构造方法
- public Thread() :分配一个新的线程对象。
- public Thread(string name) :分配一个指定名字的新的线程对象。
- public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
- public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字
4.2.1.2 常用方法①
- public void run() :此线程要执行的任务在此处定义代码。
- public String getName() :获取当前线程名称。
如果没有手动指定线程名称,默认是Thread-编号,从0开始。 如果需要手动指定线程名称,可以通过构造器,或者setName(String name)方法设置线程名称。
- public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
- public final boolean isAlive():测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。
- public final int getPriority() :返回线程优先级
- public final void setPriority(int newPriority):改变线程的优先级
- 每个线程都有一定的优先级,优先级高的线程将获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。Thread类提供了setPriority(int newPriority)和getPriority()方法类设置和获取线程的优先级,其中setPriority方法需要一个整数,并且范围在[1,10]之间,通常推荐设置Thread类的三个优先级常量:
- MAX_PRIORITY(10):最高优先级
- MIN _PRIORITY (1):最低优先级
- NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。
案例:声明一个匿名内部类继承Thread类,重写run方法,在run方法中获取线程名称和优先级。设置该线程优先级为最高优先级并启动该线程。
代码演示如下:
Thread t= new Thread(){ @Override public void run() { System.out.println(getName()+"的优先级:"+getPriority()); } }; t.setPriority(10); t.start();
4.2.1.3 常用方法②
- public static void sleep(long millis) throws InterruptedException: 线程休眠,单位毫秒
- public static void yield(): 让当前线程暂停一下当前线程暂停下,让出CPU,但是下一次CPU有可能还是调用它。
- void join() throws InterruptedException : 等待该线程终止。该线程是调用ioin 方法的线程。
阻塞当前线程的执行,等到被调用join的线程对象执行完毕才执行继续执行当前线程
- void join(long millis) : 等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。
- void join(long millis,int nanos) : 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
案例:
声明一个PrintEvenThread线程类,继承Thread类,重写run方法,实现打印[1,100]之间的偶数,要求每隔1毫秒打印1个偶数。
声明一个PrintOddThread线程类,继承Thread类,重写run方法,实现打打印[1,100]之间的奇数在main线程中:
(1) 创建两个线程对象,并启动两个线程
(2)当打印奇数的线程结束了,让偶数的线程也停下来,就算偶数线程没有全部打印完[1,100]之间的偶数.
案例演示代码如下:
package test; //打印1-100之间的偶数 public class PrintEvenThread extends Thread { private boolean flag=true; public void setFlag(boolean flag) { this.flag = flag; } @Override public void run() { for (int i = 2; i <=100 & flag; i+=2) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("自定义异常打印【1-100】之间的偶数:"+i); } } } //打印1-100之间的奇数 public class PrintOddThread extends Thread { @Override public void run() { for (int i = 1; i <= 100 ; i+=2) { System.out.println("自定义异常打印【1-100】之间的奇数:"+i); } } } public class TestPrint { public static void main(String[] args) { PrintEvenThread p1=new PrintEvenThread();//偶数进程 PrintOddThread p2=new PrintOddThread();//奇数进程 p1.start(); p2.start(); try { p2.join(); } catch (InterruptedException e) { e.printStackTrace(); } p1.setFlag(false); } }