程序就是静态的数据与指令的集合,注意程序是静态的,只有运行起来才能才能提供对应的服务
而进程就是给程序加入了时间的概念,即正在运行的程序,
动态性,
各个进程之间没有关系;进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
代表操作系统中正在运行的程序,以及程序所占用的内存区域
进程与程序的区别在于.程序只是一个静态的指令集合,而进程是一个正在系统中动态的指令集合;程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的.
多个进程可以在单个处理器CPU上并发执行(抢占资源),多个进程之间不会互相影响
并发:相对来说资源比较仅缺,多个进程同时抢占公共资源,例如CPU
并行:相对来说资源比较充足,多个CPU同时处理不同的进程
线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程我们看到的进程的切换,切换的也是不同进程的主线程多线程扩展了多进程的概念,使的同一个进程可以同时并发处理多个任务(例如Debug时有多个窗口)
一个操作系统中可以有多个进程,一个进程中可以包含一个线程(单线程程序),也可以包含多个线程(多线程程序)
线程的随机性指的是同一时刻,只有一个程序在执行我们宏观上觉得这些程序像是同时运行.但是实际上微观时间是因为CPU在高效的切换着,这使得各个程序从表面上看是同时进行的,也就是说.宏观层面上,所有的进程/线程看似同时运行,但是微观层面上同一时刻,一个CPU只能处理一件事.切换的速度甚至是纳秒级别的,非常快,并且线程执行效果具有随机性,各个线程的执行顺序是不可控的,具体怎么执行,取决于CPU的调度,时间片的分配,用户是控制不了的
时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,如果在时间片用完时线程还在执行,那CPU将被剥夺并分配给另一个线程,将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继续执行。
由于线程状态比较复杂,由易到难,先学习线程的三种基础状态及其转换,简称”三态模型”:
线程生命周期 主要有五种状态:
1.新建状态(New):当线程对象创建后就进入了新建状态,如:Threadt= new MyThread():
2.就绪状态(Runnabie):当调用线程对象的start()方法线程即为进入就绪状态;
处于就绪(可运行)状态的线,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行
3.运行状态(Running) 当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态,
就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态
4.阻塞状态(Blocked)处于运状态中的线程由于某种原因暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行
根据阻塞状态产生的原因不同,阻塞状态又可可以细分成三种:
4.1等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态
4.2同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用)它就会进入同步阻塞状态
4.3其他阻塞:调用线程的sleep()或者join(),以及发出了IO请求时,线程会进入到阻塞状态,当sleep()状态超时,join()等待线程终止或者超时,或者IO处理完毕时线程重新转入就绪状态
5.死亡状态(Dead)线程执行完了或者因异常退出了,该步程结束生命周期,释放资源
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例启动线程的唯一方法就是通过Thread类的start()实例方法,start()方法是一native方法它将通知底层提作系统最终由操作系统启动一个新线程,提作系统将执行run0
这种方式实现的多线程很简单,通过自己的类直接extends Thread,并重写run()方法,就可以自动启动新线程并执行自己定义的run(方法
模拟开启多个线程,每个线程调用run0方法
6.在测试类的main方法中创建线程对象,并运行run,但是 MyThread t1 = new MyThread(); //8.1 t1.run(); 7.为了模拟多线程效果,所以至少需要创建两个线程,若只启动一个线程,就是单线程程序 MyThread t2 = new MyThread(); //8.2 t2.run(); 8.但是如果只是通过两个线程对象去调用run这个方法,程序就不知道要以多线程的方式启动,会按代码运行顺序的先后逻辑,先执行一个线程的任务,完成后再去执行另一个线程的任务,所以并没有抢占资源得到,所以 想要模拟多线程,需要启动start方法,注释掉run方法 t1.start(); t2.start(); 9.start()是将线程添加到就绪队列,为就绪状态, 执行时机取决于操作系统何时选中,对应的是 <就绪状态> 10.当调用start()方法时,底层虚拟机会自动调用run方法执行线程的执行里面的业务 11.总结,线程执行效果具有随机性,各个线程的执行顺序是不可控的,具体怎么执行,取决于CPU的调度,和时间片的分配,我们控制不了 12运行结果是两个线程名交替出现,即表示不同的线程抢占到了CPU
代码实现:
package yichang2023.CGB.线程;
public class TestThread1 {
public static void main(String[] args) {
// 6.在main方法中创建线程对象,并运行
MyThread t1 = new MyThread();
t1.run();
// 7.为了模拟多线程效果,所以至少需要创建两个线程,若只启动一个线程,就是单线程程序
MyThread t2 = new MyThread();
//9.1 t2.run();
/* 8.如果只是通过两个线程对象调用run方法,程序不知道要以多线程的方式启动,
会按代码运行顺序的先后逻辑,先执行一个线程的任务,完成后再去执行另一个线程的任务,所以并没有抢占资源得到*/
// 9.模拟多线程,需要启动start方法,注释掉run方法
t1.start();
t2.start();
// 10.start()是将线程添加到就绪队列,为就绪状态, 执行时机取决于操作系统何时选中,对应的是 <就绪状态>
// 11.当调用start()方法时,底层虚拟机会自动调用run方法执行线程的执行里面的业务
// 12.总结,线程执行效果具有随机性,各个线程的执行顺序是不可控的,具体怎么执行,取决于CPU的调度,时间片的分配,我们控制不了
// 13结果是两个线程名交替出现,即表示不同的线程抢到了CPU
}
}
/*1.自定义线程类 方法一: 继承Thread */
class MyThread extends Thread {
//2.必须要写一个run方法
@Override
public void run() {
//3.自定义线程类的业务要写在重写的run方法中
//4.因为super.run()表示默认调用父类的业务,用不着,所以注释掉
// super.run();
//5.下面来写自己的业务,例如获取当前线程名称 由于本类继承了Thread类,所以可以直接使用这个方法
for (int i = 0; i < 10; i++) {
System.out.println("当前线程名--->" + getName());
}
}
}
总结:
1.自定义一个类并 extends Thread
2.重写run()在里面写业务
3.创建线程对象
4.调用start()
注意: 可以通过调用父类Thread的含参构造Thread(String name)给自定义线程对象起名字,调用方式: super(name);
注意:上面这种方法是让自定义的这个类以面向对象的思想来继承Thread ,就产生了局限性不能继承其他类了,会不利于程序后续开发,为了把这次继承机会留下来在以后更有需要的时候再使用,所以使用第二种面向接口的方法,更加灵活
//1.自定义多线程类 并实现runnable接口 class MyRunnable implements Runnable{ // 2.实现接口中未实现的抽象方法 在里面写自定义业务 @Override public void run() { // 3.业务:打印10次线程名称 for (int i = 0; i <10 ; i++) { // System.out.println(i+"----->"+getName()); System.out.println(i+"---->"+Thread.currentThread().getName()); } //4.这里如果和之前一样,直接调线程的getName()方法就报错了,因为getName()是刚刚上一个自定义类继承的Thread类里面的方法, 而现在是实现runnable接口,就不能使用;在点进runnable接口底层发现里面就只有一个等着我去实现的抽象方法run ,所以还是要去Thread类里面找别的方法用,而如果想不创建对象就调其中的方法,只有一个办法,即<<静态>> } }
知识点:静态方法currentThread() 获取当前正在执行的线程对象;而由于Thread类是java.lang包下的, 所以可以不导包,静态方法直接用类名调用
System.out.println(Thread.currentThread().getName());
测试类中:
class TestThread2 { public static void main(String[] args) { 5.创建线程对象--包含的是之前写的业务,即目标业务对象,只需要创建一次,相当于系统给所有玩家发布了同一个任务target MyRunnable target=new MyRunnable(); 6.因为想要模拟多线程,就需要启动start方法,不能使用run方法,而当前的myRunnable测试类和MyRunnable接口都没有start()方法,所以需要通过下面这个含Runnabile参的构造函数,将MyRunnable接口实现类myRunnable的对象与线程类Thread建立关系,从而使用Thread传Runnabile的构造函数,而因为Runnabile是接口不能创建对象传进来,所以传Runnabile的实现类即这里的业务对象,最终成功的调取start()方法以多线程的方式启动线程 Thread t1=new Thread(target); Thread t2=new Thread(target); Thread t3=new Thread(target); Thread t4=new Thread(target); t1.start(); t2.start(); t3.start(); t4.start();
} }
API
实现代码:
package yichang2023.CGB.线程;
/*本类用于 多线程 实现方法二: 实现runnable接口*/
class TestThread2 {
public static void main(String[] args) {
// 5.创建线程对象--包含的是之前写的业务,即目标业务对象,只需要创建一次,相当于系统给所有玩家发布了同一个任务target
MyRunnable target = new MyRunnable();
// 6.因为想要模拟多线程,需要启动start方法,不能使用run方法,
// 而当前的myRunnable类和MyRunnable接口都没有start()方法
// 所以需要将MyRunnable接口实现类myRunnable的对象与线程类Thread建立关系
// 从而使用Thread的start()方法以多线程的方式启动线程
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
Thread t4 = new Thread(target);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//1.自定义多线程类 并实现runnable接口
class MyRunnable implements Runnable {
// 2.实现接口中未实现的抽象方法 在里面写自定义业务
@Override
public void run() {
// 3.业务:打印10次线程名称
for (int i = 0; i < 10; i++) {
/* 4.这里如果和之前一样,直接调线程的getName()方法就报错了,因为getName()是刚刚继承的Thread类里面的方法,
而现在是实现runnable接口,但点进runnable接口底层发现里面就只有一个等着我去实现的抽象方法run
所以还是要去Thread类里面找别的方法用,而如果想不创建对象就调其中方法,只有一个办法,即<<静态>>
知识点:静态方法currentThread():获取当前正在执行的线程对象,而由于Thread类是java.lang包下的,
所以可以不导包,静态方法直接用类名调用 */
// System.out.println(i+"----->"+getName());
System.out.println(i + "---->" + Thread.currentThread().getName());
}
}
}
总结:
1.自定义一个类implements Runnable
2.实现接口中未实现的run方法
3.打印当前线程名:Thread.carrentThread.getName()
4.创建目标业务对象,即实现进口的对象,包含了写的业务逻辑
5.创建线程对象 Thread t1=new Thread(target);
目的:为了把实现类与Thread建立关系,原因是想使用Thread的start方法,而非要使用start方法而不是run,是因为只有调用start方法才能以多线程的方式调用线程
6.通过线程对象调用start,把线程对象加入到就绪队列
Thread() 创建一个新的线程对象,名字是系统自定义的
Thread(String name) 与上面功能一致,还可以自定义线程名
Thread(Runnable target) 创建一个线程对象,参数为Runnable实现类的对象
Thread(Runnable target, String name) 与上面功能一致,还可以自定义线程名