1、基本概念
程序 (program) 是为完成特定任务、用某种语言编写的一组指令的集合。即指 一段静态的代码 ,静态对象。 进程 (process) 是程序的一次执行过程,或是 正在运行的一个程序 。是一个动态的过程:有它自身的产生、存在和消亡的过程。—— 生命周期 >如:运行中的QQ ,运行中的 MP3 播放器 >程序是静态的,进程是动态的 >进程作为资源分配的单位, 系统在运行时会为每个进程分配不同的内存区域 线程 (thread) ,进程可进一步细化为线程,是一个程序内部的一条执行路径。 >若一个进程同一时间 并行 执行多个线程,就是支持多线程的 >线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器 (pc) ,线程切换的开 销小 >一个进程中的多个线程共享相同的内存单元 / 内存地址空间, 它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来 安全的隐患 。 并行与并发 >并行:多个 CPU 同时执行多个任务。比如:多个人同时做不同的事。 >并发:一个 CPU( 采用时间片 ) 同时执行多个任务。比如:秒杀、多个人做同一件事。每个线程拥有独立的虚拟机栈和程序计数器,一个进程的多个线程共享方法区和堆
2、线程的创建和使用
2.1、Thread类的构造器
构造器 >Thread(): 创建新的 Thread 对象 >Thread(String threadname): 创建线程并指定线程实例名 >Thread(Runnable target): 指定创建线程的目标对象,它实现了 Runnable 接口中的run 方法 >Thread(Runnable target, String name): 创建新的 Thread 对象2.2、Thread类的有关方法:
>void start(): 启动线程,并执行对象的 run() 方法 >run(): 程在被调度时执行的操作 >String getName(): 返回线程的名称 >void setName(String name) : 设置该线程名称 >static Thread currentThread(): 返回当前线程。在 Thread 子类中就是this ,通常用于主线程和 Runnable 实现类 >static void yield() : 线程让步 >暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程(等待cpu调度) >若队列中没有同优先级的线程,忽略此方法 >join() : 当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止 >低优先级的线程也可以获得执行 >static void sleep(long millis) : ( 指定时间 : 毫秒 ) >令当前活动线程在指定时间段内放弃对CPU 控制 , 使其他线程有机会被执行 , 时间到后 重排队。 >抛出InterruptedException 异常 >stop(): 强制线程生命期结束,不推荐使用 >boolean isAlive() : 返回 boolean ,判断线程是否还活着2.2、线程的优先级
线程的优先级等级 >MAX_PRIORITY : 10 >MIN _PRIORITY : 1 >NORM_PRIORITY : 5 涉及的方法 >getPriority() : 返回线程优先值 >setPriority(int newPriority) : 改变线程的优先级 说明 >线程创建时继承父线程的优先级 >低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用2.3、线程的分类
Java 中的线程分为两类:一种是 守护线程 ,一种是 用户线程 。 >它们在几乎每个方面都是相同的,唯一的区别是判断JVM 何时离开。 >守护线程是用来服务用户线程的,通过在start() 方法前调用 thread.setDaemon(true ) 可以把一个用户线程变成一个守护线程。 >Java 垃圾回收就是一个典型的守护线程。 >若JVM 中都是守护线程,当前 JVM 将退出。2.4、创建线程的方法一 : 继承Thread类
1)将一个类声明为Thread
的子类。
2)这个子类应该重写Thread
类的方法run 。
3)创建子类实例
4)调用start()方法
/**
* @author wiscourper
* @create 2021-11-05 4:32 PM
*/
//1)将一个类声明为Thread的子类
class MYThread extends Thread {
// 这个子类应该重写Thread类的方法run
@Override
public void run() {
for(int i=0; i<100; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
// 创建子类实例
MYThread t1 = new MYThread();
// 调用start()方法; 启动当前线程,Java虚拟机调用当前线程的run方法
t1.start();
//如果没有通过start()启动当前线程,直接调用run(),那么就是简单的调用,程序会按顺序执行
// t1.run();
//start()只能启动一次
MYThread t2 = new MYThread();
t2.start();
for(int i=0; i<100; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
//创建thread类的匿名子类的方式
// new Thread(){
// @Override
// public void run() {
// for(int i=0; i<100; i++) {
// if(i % 2 != 0) {
// System.out.println(Thread.currentThread().getName() + ":" + i);
// }
// }
// }
// }.start();
}
}
注意点:
a. 如果自己手动调用 run() 方法,那么就只是普通方法,没有启动多线程模式。 b. run() 方法由 JVM 调用,什么时候调用,执行的过程控制都有操作系统的 CPU调度决定。 c. 想要启动多线程,必须调用 start 方法。 d. 一个线程对象只能调用一次 start() 方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException ”。2.5、创建线程的方法二 :实现Runnable接口
1) 定义实现类,实现 Runnable 接口。 2) 实现 类中去实现 Runnable 接口中的 run 方法。 3) 创建实现类对象 4) 将 Runnable 接口的实现类对象 作为实际参数传递给 Thread 类的构造器中,创建Thread类的对象。(以实现类对象作为实参,调用Thread构造器,新建一个线程) 5) 调用 Thread 类的 start 方法:开启线程,调用 Runnable 子类接口的 run 方法。
/**
* @author wiscourper
* @create 2021-11-05 6:48 PM
*/
//1) 定义实现类,实现Runnable接口
class MThread implements Runnable {
// 2) 实现类中去实现Runnable接口中的run方法
@Override
public void run() {
for(int i=0; i<100; i++) {
if(i % 2 == 0) {
System.out.println(i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
// 3) 创建实现类对象
MThread mThread = new MThread();
// 4) 将Runnable接口的实现类对象 作为实际参数传递给Thread类的构造器中,创建Thread类的对象。
Thread t1 = new Thread(mThread);
// 5) 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
// 启动当前线程;调用当前线程的run()-->调用了Runable类型的target的run();
t1.start();
//再启一个线程,遍历100以内的偶数
Thread t2 = new Thread(mThread);
t2.start();
}
}
特别:两种实现方式的区别
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。
实现方式的好处 避免了单继承的局限性 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。2.6、线程的声明周期
JDK 中用 Thread.State 类定义了线程的几种状态 要想实现多线程,必须在主线程中创建新的线程对象。 Java 语言使用 Thread 类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五 种状态 : 新建: 当一个 Thread 类或其子类的对象被声明并创建时,新生的线程对象处于新建状态 就绪: 处于新建状态的线程被 start() 后,将进入线程队列等待 CPU 时间片,此时它已具备了运行的条件,只是没分配到CPU 资源 运行: 当就绪的线程被调度并获得 CPU 资源时 , 便进入运行状态, run() 方法定义了线程的操作和功能 阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态 死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束