多线程
基本概念:
程序:
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合 即指一 段静态的代码,静态对象。
进程:
进程(process)是程序的一次执行过程,或是正在运行的一个程序 是一个动态的过程
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程:
线程(thread)
进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间 并行
执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间
- 它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效
但多个线程操作共享的系统资源可能就会带来安全的隐患
单核CPU和多核CPU的理解:
单核CPU,其实是一种假的多线程,因为在一个时间单元内,单核
同时也只能执行一个线程的任务
例如:
- 有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过:
- 那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”
晾着他,等他想通了,准备好了钱,再去收费
- 收费的车就相当于一个个的线程任务,收费员收费
执行线程
收完费用执行结束
对于大的任务, 也可以进行挂起,或一点点的处理...
- 因为CPU时 间单元特别短,因此感觉不出来
一个个的在工作运行,感觉是一起执行的!
如果是多核的话,才能更好的发挥多线程的效率。 CPU一个核就相当于一个工作人员...
现在的服务器都是多核的)
一个Java应用程序java.exe,其实至少有三个线程:
- main()主线程,gc() 垃圾回收线程,异常处理线程 当然如果发生异常,会影响主线程
并行 与 并发:
并行:
- 多个CPU同时执行多个任务:
比如:多个人同时做不同的事
并发:
- 一个CPU(采用时间片)同时执行多个任务:
比如:秒杀、多个人做同一件事
并行与并发: 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。 并发的关键是你有处理多个任务的能力,不一定要同时 并行的关键是你有同时处理多个任务的能力 它们最关键的点就是:是否是『同时』
多线程优点:
- 提高应用程序的响应。对图形化界面更有意义 可增强用户体验
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和 修改
但:多线程并不一定提高效率: 注意是不一定!
- 以单核CPU为例,使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短
多线程还要计算,cpu 分配不同时间片来执行时,切换的时间...
何时需要多线程:
程序需要同时执行两个或多个任务
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写 操作、网络操作、搜索等;
用户在输入,此时压力并不是特别大,后台还可以做其它事情:
校验输入数据
文件上传时间较长可以开个线程缓慢上传…让用户不用一直等待
- 需要一些后台运行的程序时…
可以提高程序响应速度..
线程的创建 和 使用:
JAVA实现多线程方式: 4种
JDK5.0之后新增两种:
线程的创建和启动:
Java语言的JVM允许程序运行多个线程,它通过 Java.lang.Thread
类来体现
Thread类:
Java中每个线程都是通过某个特定Thread对象的run()方法来完成操作的 把run()方法的主体称为线程体
通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
构造器:
- Thread():创建新的Thread对象
- Thread(String threadname):创建线程并指定线程实例名
- Thread(Runnable target, String name):创建新的Thread对象
- Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口,中的 run() 方法;
常用方法()
其实 Thread 类本身也是实现了 Runnable 接口 因此 Therad 不是抽象类(类 重写了Runable接口 run(); 方法
Java 通过 Thread类将线程,所必需的功能都封装了起来
Thread类: 支持多线程类提供了大量的方法,控制操作线程
void Thread(); //分配新的 Thread 对象;
void Thread(Runnable); //分配新的 Thread 对象 参数 Runnable 实现类 对象
void Thread(Runnable,String); //分配新的 Thread 对象 参数1 Runnable 实现类 对象 ,参数2 线程名....
void run(); //通常需要子类重写,将线程执行的操作声明在方法中,对象直接调用单纯执行run()方法; 不是以线程方式;
void start(); //使线程开始执行 启动当前线程;java虚拟机调用 run() 方法;
//注意:一个对象不可以调两次start();方法; 启动两次?? (底层固定好了!)
void sleep(long); //Theread静态方法(); 在指定毫秒数内让当前正在执行线程,休眠(暂停);需要处理异常!!
String getName(); //返回线程 名称;
void setName(String); //修改线程名; 注意:要在 start(); 之前改名;
int getPriority(); //返回线程 优先级; 0-10 默认5; 越高 CPU 获取几率越大,也只是概率~
void setPriority(int); //更改线程 优先级
static Thread.currentTherad(); //Theread类静态方法 返回当前正在执行线程的线程对象Thread;
//注意: main();是java主线程,方法内调用 指main();中的线程;
boolean isAlive(); //检查线程是否属于 运行状态; 需要处理异常!!
void join(); //等待终止线程; 使当前线程运行完毕!
//线程A调用,此时A进入阻塞状态,直到B线程完全执行完,A才结束阻塞;
void interrupt(); //终止线程;
void yieId(); //礼让: 暂停当前线程对象允许其他线程获得CPU资源;
(停一下则立刻就绪!)该线程仍处于就绪状态,系统随机选择:就绪线程运行; 有可能该线程继续获取到资源!
void stop(); //已过时,当执行此方法,强制结束当前线程; 生命周期结束;
void wait(); //线程 run()中调用,当前线程就进入阻塞状态,并开启锁,其它线程可访问数据;
void notify()/notfyAll(); //线程 run()中调用,notify();随机取消一个线程设置wait(); notfyAll();把所有的wait();阻塞线程都取消阻塞;
wait() notfyAll() notfy()
三个方法是在 Java.lang.Object类中
三个方法的调用者必须是:当前同步代码块/同步监视器
否则会出现,IllegalMonitorStateExeption异常;
sleep()和wait()方法的异同:
- sleep() wait()一旦执行,都可以使当前线程进入阻塞状态;
- sleep()可以在任何场景下使用,wait()只能在同步代码块/同步方法中
wait需要由 同步监视器来调用!
- 关于同步建视器,如果两个方法都在同步代码块/同步方法中使用 sleep()不会释放锁(同步监视器); wait()会释放锁(同步监视器);
线程的分类:
Java中的线程分为两类:守护线程
用户线程
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开
守护线程:
是用来服务用户线程的,
通过在 start()
方法前调用 thread.setDaemon(true)
可以把一个用户线程变成一个守护线程
Java垃圾回收就是一个典型的守护线程 main 就是一个用户线程!
若JVM中都是守护线程,当前JVM将退出
守护线程,随着用户线程消亡而消亡
守护线程和其它线程生命周期不同…
线程的生命周期:
线程一般具有五种状态: 创建 就绪 运行 堵塞 死亡
代码语言:javascript复制1.创建状态:
//用构造函数 创建 出
( Thread )或实现( Runnable )类 的 类对象; 就是线程 创建;
2.就绪状态:
//调用了 .start(); 方法的 就是线程 就绪;
3.运行状态:
//线程 从就绪到运行 获得了CPU 的资源进入运行状态....失去CUP权限/yieId();就绪专题....
4.堵塞状态:
//一个 正在运行 的线程 因某种原因不在运行时(一个线程 不能获取 CPU资源) 进入 阻塞状态....
//常见: 1. .sleep(int); (休眠) 2.yieId(); (线程显示让出CPU资源) 3.I/O 事件阻塞...等待同步锁...
5.死亡状态:
//线程 的run(); 方法 中代码执行完毕... 不在具有 继续运行的 能力!
JDK中用Thread.State类定义了线程的几种状态: Java线程有6中状态
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选 中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
- 阻塞(BLOCKED):表示线程阻塞于锁。
- 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
- 终止(TERMINATED):表示该线程已经执行完毕。
线程的创建:
JDK1.5之前创建新执行线程有两种方法:
- 继承Thread类的方式 定义子类继承Thread类 子类中重写Thread类中的run方法 创建Thread子类对象,即创建了线程对象 调用线程对象start方法:启动线程,调用run方法
- 实现Runnable接口的方式
注意:
- 想要启动多线程,只有调用:
start(); 方法
- 多线程中的run()方法,不要手动调用,那样并不是多线程执行…只是正常的调用run() 方法调用!
只有通过:
线程对象.start();
才是正确的多线程就绪方法, Java底层会去开启多线程调用run();多线程执行run() 中的步骤!
- Java一个线程对象只能调用一次start()方法启动,如果重复调用了
则将抛出以上 的异常“IllegalThreadStateException”
继承Thread类:
Thread.java
package com.wsm.thread;
/**
* 多线程的创建,方式一:继承于Thread类
* 1. 创建一个继承于Thread类的子类
* 2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
* 3. 创建Thread类的子类的对象
* 4. 通过此对象调用start()
* <p>
* 例子:遍历100以内的所有的偶数
*
*/
//1. 创建一个继承于Thread类的子类
class MyThread extends Thread {
//2. 重写Thread类的run()
@Override
public void run() {
//循环输出100行数据
for (int i = 0; i < 100; i ) {
if(i % 2 == 0){
//不设置默认线程名,默认Tread-0 递增~
System.out.println(Thread.currentThread().getName() ":" i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3. 创建Thread类的子类的对象
MyThread t1 = new MyThread();
//4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
t1.start();
//问题一:我们不能通过直接调用run()的方式启动线程。
// t1.run();
//问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException
// t1.start();
//我们需要重新创建一个线程的对象
MyThread t2 = new MyThread();
t2.start();
//如下操作仍然是在main线程中执行的。
for (int i = 0; i < 100; i ) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() ":" i "***********main()************");
}
}
}
}
运行可以看到多线程,交替执行…
扩:匿名子类的方式创建线程
匿名类,当程序只有一处地方使用到该类,则可以创建一个匿名子类 内存用完即丢,快速回收~
创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
代码语言:javascript复制public static void main(String[] args) {
//创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
//创建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();
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();
}
}
总结:
继承 Thread 类创建线程:
代码语言:javascript复制//使用此方式创建线程类
//此线程类需要继承 Thread类, 并重写类中 run()方法;
//因为Thread类中的 run()方法是,线程要执行的操作任务方法; 所以,线程要执行的操作代码需要写 在 run()方法中; //并通过子类实例.调用 start();方法来启动线程;
操作:
A 类 继承Thread类;
A a = new A(); //创建 A 类实例/线程
a.start(); //启动 线程.....
//创建两个线程就是 A a1 = new A();
//每 new A(); 创建一个即每个线程都是独立类对象..只有static修饰的类属性才共享...
实现Runnable接口:
RunnableTest.java
package com.wsm.thread;
/**
* 创建多线程的方式二:实现Runnable接口
* 1. 创建一个实现了Runnable接口的类
* 2. 实现类去实现Runnable中的抽象方法:run()
* 3. 创建实现类的对象
* 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5. 通过Thread类的对象调用start()
*
* 比较创建线程的两种方式。
* 开发中:优先选择:实现Runnable接口的方式
* 原因:1. 实现的方式没有类的单继承性的局限性
* 2. 实现的方式更适合来处理多个线程有共享数据的情况。
*
* 联系:public class Thread implements Runnable
* 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
*/
//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(Thread.currentThread().getName() ":" i);
}
}
}
}
public class RunnableTest{
public static void main(String[] args) {
//3. 创建实现类的对象
MThread mThread = new MThread();
//4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
t1.setName("线程1");
//5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
t1.start();
//再启动一个线程,遍历100以内的偶数
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();
}
}
- 上面的线程:t1 和 t2 都是同一个Runnable实现类对象创建的,
类内部的数据是可以共享的!
- 这里没有表现出,后面举例…
总结:
实现Runnable接口:
代码语言:javascript复制//因为 Java是单继承的
//所以如果 一个类继承一个类,又要是 线程类,所以:还可以实现接口来 创建线程了;
//Runnable
//在java.lang; 包中 其中 有一个 抽象方法 run();
//通过实现 Runable 接口重写run(); 方法,方法中写代码完成线程要完成操作;
操作:
A 实现了 Runnable接口;
A a = new A(); //创建 A类对象 a;
Thread xc = new Thread(a); //创建 线程 xc;
xc.start(); // 启动线程;
....................
注意:
//如果想要在创建一个线程就不需要
new A();
//只需要在 Thread xc1 = new Thread(a); 即创建新的线程,但每次参数都是实例 a,即创建线程的属性值共享;
//不想共享某些属性,可以重新
A a1 new A();
Thread n = new Thread(a1);
//当然也可以使用:匿名内部类:匿名子类/匿名实现类形式, 调用启动线程; 前提这个线程只用一次;