多线程程序将单个任务按照功能分解成多个子任务来执行,每个子任务称为一个线程,多个线程共同完成主任务的运行过程,这样可以缩短用户等待时间,提高服务效率。本篇博客将简单介绍Java开发中多线程的使用。
目录:
☍ 程序、进程、线程基本概念
▾ 程序、进程、线程概念
程序(program)
☃ 程序是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process)
☃ 进程(process)是程序的一次执行过程,或是正在运行的一个程序。进程是一个动态的过程:有它自身的产生、存在和消亡的过程——生命周期
↝ 如运行中的音乐播放器,运行的QQ
↝ 程序是静态的,进程是动态的
↝ 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程(thread)
☃ 进程可进一步细化为线程,是一个程序内部的一条执行路径。
↝ 若一个进程同一时间并行执行多个线程,称之为多线程运行
↝ 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销较小
↝ 一个进程中的多个线程共享相同的内存单元/内存地址空间(它们从同一堆中分配对象),可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能会带来安全隐患。
☃ 一个Java应用程序java.exe,至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
单核CPU和多核CPU概念
☃ 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务(线程交替执行)。例如:有多车道(对应多线程)最终汇聚到一个收费站口(对应cpu),只有前面的车付完过路费(一个线程执行完毕),后面的车才能继续通过,否则就要挂起等待。
☃ 如果是多核的话,才能更好的发挥多线程的效率。多条车道,多个收费口。
并行与并发
☃ 并行:多个CPU同时执行多个任务。比如:多个人同时做一个任务中不同的事。
☃ 并发:一个CPU(采用时间片)同时执行多个任务。比如:网上商城秒杀活动、多个人同时做同一件事。
个人对多线程,并行,并发,cpu核数之间的理解(如果有不对的地方欢迎评论指正):
(1)cpu核数是硬件条件,核数越大程序执行速度越快。在单核cpu上是伪多线程(单元时间内只能执行一个线程),只有在多核cpu上才能更好的体现多线程的优点,换句话说多线程就是为了提高进程运行效率,提高cpu利用率。
(2)并行和并发与cpu核数的关系:并行是多个cpu同时处理不同的任务,并发是一个cpu同一时间内执行多个任务。单核严格意义上不存在并行(即使有也是伪并行);并发不论是单核还是多核都有可能发生,只要相同时间内同时有多个请求要执行某一段代码或者指令就会发生并发。
(3)多线程与并行和并发的关系:多线程与并行和并发是不同的概念。多线程不等同于并行或并发,多线程是将复杂的进程分为多个子进程运行,可以理解为多线程是为了合理的利用系统资源。如果子进程之间相互独立,并且运行时互不干扰,那么可以认为它是并行的;如果子进程在执行时同时调用一个公共资源,那么它就是并发的。
▾ 多线程优点
多线程优点
☃ 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
☃ 提高计算机系统CPU的利用率。
☃ 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
多线程不一定就比单线程快,如果线程都是在一个cpu中运行,那么单线程要比多线程快,因为在一个cpu核中多线程来回交替运行需要花费更多的时间;如果是多线程在多核中运行通常就会比单线程快。
▾ 多线程的应用场景
☃ 程序需要同时执行两个或多个任务。
☃ 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
☃ 需要一些后台运行的程序时。
☍ 线程的创建和使用
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread
类来体现。
jdk1.5之前提供两种Java API方式创建新执行线程
- 继承Thread类的方式
- 实现Runable接口的方式
Thread类的特点
☃ 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
。
☃ 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()。
Thread类的构造器
☃ Thread() :创建新的Thread对象
☃ Thread(String threadname):创建线程并指定线程实例名
☃ Thread(Runnable target) :它实现了Runnable接口中的run方法。
☃ Thread(Runnable target, String name) :指定创建线程的目标对象和线程实例名
▾ 创建新线程方式一:继承Thread类
☃ 定义子类继承Thread类。
☃ 子类中重写Thread类中的run方法。
☃ 创建Thread子类对象(即线程对象)。
☃ 调用线程对象start方法:启动线程,调用run方法。
//1、MyThread类继承Thread
class MyThread extends Thread{
// 2、重写Thread中的run()
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println("偶数:" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3、创建Thread类的子类对象
MyThread thread1 = new MyThread();
//4、子类对象调用start方法
thread1.start();
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0){
System.out.println("奇数:" + i);
}
}
MyThread thread2 = new MyThread();
thread2.start();
}
}
重点:
↝ 手动调用run()方法则只是普通方法,没有启动多线程模式。
↝ run()方法由JVM调用,什么时候调用,执行的过程控制都由操作系统的CPU调度决定
↝ 必须使用start方法才能启动多线程
↝ 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出“IllegalThreadStateException”异常。
Thread类的相关方法
Thread类的相关方法 |
---|
☄ void start(): 启动线程,并执行线程对象的run()方法 |
☄ run(): 线程在被调用时执行的操作 |
☄ String getName(): 返回线程的名称 |
☄ void setName(String name): 设置线程的名称 |
☄ static Thread currentThread():返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类 |
☄ static void yield(): : 线程让步 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级或更高优先级的线程,则忽略此方法 |
☄ join() :线程插队,当在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b执行完以后a线程才结束阻塞状态(低优先级的线程也可以获得执行) |
☄ static void sleep(long millis) :(指定时间:毫秒) 睡眠,延时执行 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重新排队。 需要抛出InterruptedException异常 |
☄ stop():强制结束线程的生命周期(过时,不推荐使用) |
☄ boolean isAlive():判断线程是否还在生命周期内 |
线程的调度
调度策略
➢ 时间片
➢ 抢占式:高优先级的线程抢占CPU
Java的调度方法
➢ 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
➢ 对高优先级,使用优先调度的抢占式策略
线程的优先级
➢ MAX_PRIORITY :10
➢ NORM_PRIORITY :5
➢ MIN _PRIORITY :1
线程优先级相关方法
➢ getPriority():返回线程优先级
➢ setPriority(int newPriority):改变线程的优先级
重点说明:
➢ 创建子线程时继承父线程的优先级
➢ 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用,除改变线程的优先级外还可对低优先级线程使用join()方法使其优先执行
代码示例:
class TestThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++){
if(i % 2 == 0){
try {
sleep(500); //睡眠,延时执行
} catch (InterruptedException e) {
e.printStackTrace();
}
//currentThread()获取当前线程,getName()获取线程名,getPriority()获取线程优先级
System.out.println(Thread.currentThread().getName()+":" + getPriority() + ":" + i);
//System.out.println(this.currentThread().getName()+":" + i); this等同于Thread
}
if(i % 10 == 0){
yield(); //线程让步
}
}
}
}
class TestThread2 extends Thread{
public TestThread2(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName()+":" + i);
}
if(i == 80){
this.stop(); //强制结束当前线程,已过时
}
}
}
}
public class ThreadMethodTest{
public static void main(String[] args) {
TestThread1 thread1 = new TestThread1();
thread1.setName("线程1"); //设置线程名
System.out.println(Thread.currentThread()); //返回当前线程
thread1.setPriority(Thread.MAX_PRIORITY); //thread1.setPriority(7)
thread1.start();
TestThread2 thread2 = new TestThread2("线程2"); //构造器方式设置线程名
thread2.start();
Thread.currentThread().setName("主线程");
for(int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + i);
if (i % 10 ==0){
try{
thread2.join(); //线程插队
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
System.out.println(thread1.isAlive()); //判断线程是否还在生命周期内
System.out.println(thread2.isAlive());
}
}
▾ 创建新线程方式二:实现Runnable接口
☃ 定义子类,实现Runnable接口。
☃ 子类中重写Runnable接口中的run方法。
☃ 通过Thread类含参构造器创建线程对象。
☃ 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
☃ 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
//1、创建Runnable接口实现类
class RunnableThread implements Runnable{
//实现Runnable中的抽象方法run()
@Override
public void run() {
for (int i = 0; i < 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+":" + Thread.currentThread().getPriority() + ":" + i);
}
}
}
}
public class RunnableTest {
public static void main(String[] args) {
//3、创建实现类的对象
RunnableThread r1 = new RunnableThread();
//4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中
Thread t1 = new Thread(r1);
//5、调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法
t1.setName("线程一");
t1.start();
Thread t2 = new Thread(r1);
t2.setName("线程二");
t2.start();
}
}
继承方式和实现方式的联系与区别
区别
➢ 继承Thread:线程执行代码存放Thread子类run方法中。
➢ 实现Runnable:线程代码存在接口的子类的run方法中。
联系
➢ Thread也是实现了Runnable中的run()方法。
Runnable接口线程的优点
➢ 避免了单继承的局限性
➢ 多个线程可以共享同一个接口实现类的对象,非常适合多个线程来处理同一份资源
练习:
三个窗口售卖100张票
//继承Thread方式
class SellTicket extends Thread{
private static int ticketNum = 100;
public SellTicket(String windownName){
super.setName(windownName);
}
@Override
public void run() {
while(true){
if(ticketNum > 0){
System.out.println(getName() + "窗口卖票,票号为:" + ticketNum--);
}else{
System.out.println("票已卖光");
break;
}
}
}
}
public class Thread_SellTicket {
public static void main(String[] args) {
SellTicket t1 = new SellTicket("窗口1");
SellTicket t2 = new SellTicket("窗口2");
SellTicket t3 = new SellTicket("窗口3");
t1.start();
t2.start();
t3.start();
}
}
***********************************************
//实现Runnable接口方式
class R_SellTicket implements Runnable{
private int ticketNum = 100;
@Override
public void run() {
while(true){
if(ticketNum > 0){
System.out.println(Thread.currentThread().getName() + "窗口卖票,票号为:" + ticketNum--);
}else{
System.out.println("票已卖光");
break;
}
}
}
}
public class Runnable_SellTicket {
public static void main(String[] args) {
R_SellTicket r = new R_SellTicket();
Thread t1 = new Thread(r, "窗口1");
Thread t2 = new Thread(r, "窗口2");
Thread t3 = new Thread(r, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
//存在线程安全问题,待解决
线程的分类
☃ Java中的线程分为两类:一种是守护线程,一种是用户线程。它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
☃ 守护线程是用来服务用户线程的,在start()方法前调用
thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
☃ 若JVM中都是守护线程,当前JVM将退出。
本博客与CSDN博客༺ཌ༈君☠纤༈ད༻同步发布