进程和线程详解
一、进程和线程的概述
1、要想了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在。
2、什么是进程?
通过任务管理器我们就看到了进程的存在。 而通过观察,我们发现只有运行的程序才会出现进程。
进程:就是正在运行的程序。
进程:是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
3、多进程有什么意义呢?
单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。
举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。 也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个程序。 可以提高CPU的使用率。
问题: 一边玩游戏,一边听音乐是同时进行的吗?
不是。因为单核CPU在某一个时间点上只能做一件事情。 而我们在玩游戏,和在听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。
4、什么是线程?
在同一个进程(正在运行的程序)内又可以执行多个任务,而这每一个任务我就可以看出是一个线程。
线程:是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
单线程:如果程序只有一条执行路径。
多线程:如果程序有多条执行路径。 一个进程 = 一个正在运行的程序 = 1个线程+1个线程+1个线程+... = 多个线程 = 多个任务
5、多线程有什么意义呢?
多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率(提高能使用cpu的概率)。
程序的执行其实都是在抢CPU的资源,或者叫CPU的执行权。
多个进程是在抢这个资源,而其中的某一个进程如果执行路径(线程)比较多,那么就会有更高的几率抢到CPU的执行权。
我们是不敢保证哪一个线程能够在哪个时刻抢到cpu的执行权的,所以线程的执行具有随机性。
小结:
多进程的意义:提高使用cpu的效率。(多用cpu)
多线程的意义:提高能使用cpu的概率。(有多少机会用cpu,因为cpu不是你想用就能用啊)
多线程程序的引入图解
二、Java程序的运行原理及JVM的启动是多线程的吗?
A:Java程序的运行原理
Java通过java命令会启动java虚拟机。启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
B:JVM的启动是多线程的吗?
垃圾回收线程也要先启动,否则很容易会出现内存溢出。
JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
示例代码如下:
1 package cn.itcast_01; 2 /* 3 * 进程: 4 * 正在运行的程序,是系统进行资源分配和调用的独立单位。 5 * 每一个进程都有它自己的内存空间和系统资源。 6 * 线程: 7 * 是进程中的单个顺序控制流,是一条执行路径。 8 * 是程序的执行单元,执行路径。是程序使用CPU的最基本单位。 9 * 10 * 一个进程如果只有一条执行路径,则称为单线程程序。 11 * 一个进程如果有多条执行路径,则称为多线程程序。 12 * 13 * 举例: 14 * 扫雷程序,迅雷下载 15 * 16 * 大家注意两个词汇的区别:并行和并发。 17 * 并行:前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。 18 * 并发:后者是物理上同时发生,指在某一个时间点同时运行多个程序。 19 * 20 * 21 * 22 * Java程序的运行原理: 23 * 通过java命令会启动 java虚拟机。启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。 24 * 该进程会自动启动一个 “主线程”,然后主线程去调用某个类的 main方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。 25 * 26 * 思考题: 27 * jvm虚拟机的启动是单线程的还是多线程的? 28 * 多线程的。 29 * 原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。 30 * 现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。 31 */ 32 public class MyThreadDemo { 33 public static void main(String[] args) { 34 System.out.println("hello"); 35 new Object(); // 造对象 36 new Object(); // 造对象 37 new Object(); // 造对象 38 new Object(); // 造对象 39 //...造很多很多对象后,如果垃圾回收线程不启动的话,内存就会溢出! 40 System.out.println("world"); 41 } 42 }
3、多线程的实现方案(掌握)
A:自定义类继承Thread类
1:自定义类MyThread继承Thread类
2:MyThread类里面重写run()方法
3:在测测试类MyThreadTest中创建MyThread类的对象
4:启动线程
代码演示:
package com.thread; public class ThreadDemo extends Thread { @Override public void run() { for (int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"执行了"+i+"次"); } } }
class Test{ public static void main(String[] args) { ThreadDemo thread = new ThreadDemo(); thread.start(); } }
运行结果:
Thread-0执行了0次 Thread-0执行了1次 Thread-0执行了2次 Thread-0执行了3次 Thread-0执行了4次 Thread-0执行了5次 Thread-0执行了6次 Thread-0执行了7次 Thread-0执行了8次 Thread-0执行了9次
B:自定义类实现Runnable接口
1:自定义类MyRunnable实现Runnable接口
2:MyRunnable类里面重写run()方法
3:在测测试类MyRunnableTest中创建MyRunnable类的对象
4;在测测试类MyRunnableTest中再创建Thread类的对象,并把3步骤的对象作为构造参数进行传递
5:启动线程
代码演示:
package com.thread; public class Runable1 implements Runnable{ @Override public void run() { for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+"执行了"+i+"次"); } } }
class Runnable1Demo { public Runnable1Demo() { } public static void main(String[] args) { Runable1 runable1 = new Runable1(); Thread thread = new Thread(runable1); Thread thread1 = new Thread(runable1); thread.start(); thread1.start(); } }
运行结果:
Thread-0执行了0次 Thread-1执行了0次 Thread-0执行了1次 Thread-1执行了1次 Thread-0执行了2次 Thread-1执行了2次 Thread-0执行了3次 Thread-1执行了3次 Thread-0执行了4次 Thread-1执行了4次
注意事项:
1:Thread类的方法:
public final String getName() 获取线程对象的名称(一般放在需要被线程执行的代run()方法里面)
public final void setName(String name) 设置线程对象的名称
对象名.setName("林青霞");
2:在不是Thread类的子类中,如何获取线程对象的名称呢?
public static Thread currentThread() 返回当前正在执行的线程对象(静态方法)
Thread.currentThread().getName()
3:该自定义的类为什么要重写run()方法?
自定义类中不是所有的代码都需要被线程执行。
而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()方法,用来包含那些需要被线程执行的代码。
注意:这里的 被线程执行 = 开一个新线程执行
4:由于自定义类实现了接口,所以就不能在自定义类中直接使用Thread类的getName()方法了,但是可以间接的使用。
Thread.currentThread().getName()
5:为什么要实现Runnable接口?
-Java不支持多继承
-不打算重写Thread类的其他方法
小问题:
1:为什么要重写run()方法?
答:run()方法里面封装的是被线程执行的代码。
2:启动线程对象用的是哪个方法?
答:start()方法
3:run()方法和start()方法的区别?
答:run()方法直接调用仅仅是普通方法。
start()方法是先启动线程,再由jvm去调用run()方法。
4:有了方式1,为什么还来一个方式2呢?
答:若自定义类MyThread类已经有一个父类了,那么它就不可以再去继承Thread类了。(java不支持多继承)
若自定义类MyRunnable类已经实现了一个接口了,那么它还可以再去实现Runnable接口。(java支持多实现)
即可以避免由于Java单继承带来的局限性。
在测试类MyThreadTest中,要想开多个线程,就要先new多个自定义类MyThread的对象,每一个自定义类MyThread的对象的成员变量都相同,这样需要在栈中开辟很多内存;
在测试类MyRunnableTest中,要想开多个线程,只需要new一个自定义类MyRunnable的对象,再new多个Thread类的对象即可,这样就大大节约了内存。
即适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码和数据有效分离(即耦合性降低),较好的体现了Java面向对象的设计思想。
4、线程的调度模型和如何获取和设置线程优先级
假如我们的计算机只有一个CPU,那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。 那么Java是如何对线程进行调用的呢?
线程有两种调度模型。
A:线程的调度模型
a:分时调度模型 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
b:抢占式调度模型 (Java采用的是该调度方式) 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片的概率相对高一些。
B:如何获取和设置线程优先级
线程默认的优先级是:5。
线程优先级的范围是:1-10。
如何获取线程对象的优先级?
public final int getPriority()
返回线程对象的优先级 int i = 对象名.getPriority();
如何设置线程对象的优先级?
public final void setPriority(int newPriority) 更改线程的优先级 对象名.setPriority(10); IllegalArgumentException:非法参数异常 抛出的异常表明向方法传递了一个不合法或不正确的参数。
5、线程的控制(即线程常见的方法)
A:线程休眠
public static void sleep(long millis) 单位是毫秒(该方法会抛出异常)
Thread.sleep(1000);
package com.thread; public class Sleep implements Runnable{ @Override public void run() { for (int i=0;i<10;i++){ if (i==5){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"执行了"+i+"次"); } } }
class SleepDemo { public SleepDemo() { } public static void main(String[] args) { Sleep sleep = new Sleep(); Thread thread = new Thread(sleep); thread.start(); } }
B:线程加入
public final void join() 等待该线程终止(为了使某线程先执行完毕)(该方法会抛出异常)
对象名.join(); // 该方法必须在启动线程后调用
package com.join; public class MyThread extends Thread{ @Override public void run() { for (int i=0;i<10;i++){ System.out.println(this.getName()+"执行了"+i+"次"); } } }
class JoinDemo { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); try { myThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } for (int i=0;i<5;i++){ System.out.println("主线程执行了"+i+"次"); } System.out.println("主线程执行结束"); } }
运行结果:
Thread-0执行了0次 Thread-0执行了1次 Thread-0执行了2次 Thread-0执行了3次 Thread-0执行了4次 Thread-0执行了5次 Thread-0执行了6次 Thread-0执行了7次 Thread-0执行了8次 Thread-0执行了9次 主线程执行了0次 主线程执行了1次 主线程执行了2次 主线程执行了3次 主线程执行了4次 主线程执行结束
C:线程礼让
public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 能够在一定的程度上,让多个线程的执行更和谐,但是不能靠它保证一个线程一次。
Thread.yield();
D:后台线程(守护线程/用户线程)
public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程
对象名.setDaemon(true); // 设置守护线程
当正在运行的线程都是守护线程时,Java虚拟机退出。该方法必须在启动线程前调用。
E:中断(终止)线程(掌握)
public final void stop() 让线程停止,过时了,但是还可以使用。(为什么会过时呢?因为该方法太暴力了,具有固有的不安全性,直接把线程停止,该线程之后的代码都不能执行了)
对象名.stop();
public void interrupt() 中断线程。 把线程的状态终止,并抛出一个InterruptedException异常。
对象名.interrupt();
注意事项:
如果被重写的方法没有异常抛出,那么子类的方法绝对不可以抛出异常,如果子类方法内有异常发生,那么子类只能try,不能throws。
6、线程的状态和生命周期
A:线程的创建 创建线程对象,无资格无权。
B:线程的就绪 有资格无权
C:线程的运行 有资格有权
D:线程的阻塞 无资格无权
E:线程的死亡 无资格无权
作者:泰斗贤若如
微信公众号:去有风的地方飞翔
Github:https://github.com/zyx110
有事微信:zyxt1637039050
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。
我不能保证我所说的都是对的,但我能保证每一篇都是用心去写的,我始终认同: “分享的越多,你的价值增值越大”,我们一同在分享中进步,在分享中成长,越努力越幸运。再分享一句话“十年前你是谁,一年前你是谁,甚至昨天你是谁,都不重要。重要的是,今天你是谁,以及明天你将成为谁。”
人生赢在转折处,改变从现在开始!
支持我的朋友们记得点波推荐哦,您的肯定就是我前进的动力。