当前位置: 首页 > news >正文

多线程【基础】

一、进程与线程[了解]

什么是进程?

  • 一个进程就是一个运行的程序(app)

  • 例如: idea,内网通,qq,微信

  • 进程是电脑资源分配的最小单位

  • 进程中至少包含一个线程

  • 进程中的资源,线程会共享,比如堆中的对象,静态域中的数据

什么是线程?

  • 线程是进程中的一个执行路径

  • 线程是资源调度的最小单位

  • 在内存栈中的数据,是属于线程的

为什么需要多线程?

  • 因为代码运行是有顺序的,执行完前面才能执行后面

  • 但是很多时候,需要功能同时执行

  • 顺序执行的代码,功能太简单,有局限性

  • 多线程,可以实现代码并发执行,即同时执行

  • 提升资源利用率,提升效率

Java代码是多线程的吗?

  • 是,至少有两个

    • 代码运行的main线程

    • 垃圾回收线程

二、创建线程[重点]

main方法是一个线程,我们需要额外单独再开一条独立线程.方式

  • 继承Thread类

  • 实现Runnable接口

  • 实现Callable接口

  • 线程池

2.1 继承Thread类

Thread类,在java中代表一个线程,想要实现多线程,方式1

  • 继承Thread

  • 重写run方法,写的是线程的任务

  • 创建线程对象

  • 开启线程,调用的是start

需求: main线程输出1-100,再开一个线程输出101-200,两个线程同时执行

// 线程类

public class MyThread1 extends Thread{
​/*** 重写run方法* 该方法内,就是新线程的执行任务代码*/@Overridepublic void run() {for (int i = 101; i < 201; i++) {System.out.println("新线程--->" + i );}}
}

//测试

public class TestThread {
​public static void main(String[] args) {// 创建线程对象MyThread1 t1 = new MyThread1( );
​// 开线程,注意!!! 此处开线程是start方法,不是run()// 调用start方法,开启新线程,自动调用run方法t1.start();
​//===============================
​// main线程for (int i = 1; i < 101; i++) {System.out.println("主线程--->" + i );}}
}

执行效果,出现main线程和新线程在争抢执行....

匿名内部类实现继承Thread

new Thread(){ // 相对于是在创建子类对象@Overridepublic void run() {for (int i = 201; i < 301; i++) {System.out.println("线程3--->" + i );}}
}.start();

3.2 实现Runnable接口

  • 子类实现Runnable接口

  • 重写run方法

  • 创建子类对象

  • 将子类对象当做参数,传递给Thread类的构造方法,创建出thread对象

  • 使用thread对象开启start

// 子实现类

public class MyThread2 implements Runnable{
​@Overridepublic void run() {for (int i = 1; i < 101; i++) {System.out.println("R1 线程执行 -->" + i  );}}
}

// 测试

public class TestThread2 {
​public static void main(String[] args) {// 创建子类型对象MyThread2 m2 = new MyThread2( );
​// 将子类对象,当参数传递给Thread类构造Thread t2 = new Thread(m2);// 开启线程t2.start();
​// =================================// 主线程for (int i = 1; i < 101; i++) {System.out.println("main线程执行 -->" + i  );}}
}

特别注意,第4步 第5步

image-20240611104913861

匿名内部类,实现RUnnable接口完成多线程

// 使用匿名内部类完成开启多线程
​
new Thread(new Runnable( ) { // 创建子类对象@Overridepublic void run() {for (int i = 1; i < 101; i++) {System.out.println("R2 线程执行 -->" + i);}}
}).start();

3.3 二者区别

  • 从使用方便程度来说,继承Thread类实现多线程更直接方便点,因为创建对象,直接调用start即可

  • 但是呢,继承Thread类有局限性,类只能单继承,即一个类如果已经继承过别的,此时无法再继承Thread,只能实现Runnable接口开启线程

  • 所以,建议还是使用实现Runnable接口完成多线程

3.4 Callable接口

目前,实现多线程无论是继承Thread,还是实现Runnable接口,都必须重写run方法,但是这个run方法无返回值,且无异常抛出,只能捕获


基于以上问题,jdk提供了可以有返回值和抛出异常的开启线程的方案

  • Callable + FutureTask

// 实现Callable接口

public class MyThread3 implements Callable<Integer> {@Overridepublic Integer call() throws Exception {System.out.println("--- Callable线程开始工作 ---" );int sum = 0;for (int i = 1; i < 101; i++) {sum += i;}System.out.println("--- Callable线程结束工作 ---" );return sum;}
}

// 测试,将对象交给FutureTask

public class TestThread3 {
​public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建Callable子实现类对象MyThread3 c = new MyThread3( );
​// 将对象交给FutureTask的构造FutureTask<Integer> task = new FutureTask<>(c);
​// 将task对象,当参数传递给Thread对象.创建ThreadThread t = new Thread(task);
​// 开启线程t.start();
​// 一定注意!!!要线程结束,获得返回值Integer i = task.get( );System.out.println("线程返回结果:" + i );}
}

四、线程的API

已经掌握了开启线程的方式,下面学习线程对象的一些方法,这些方法可以改变线程的运行状态,例如线程名字,运行,停止,加入,阻塞,优先级和守护

4.1 关于线程名字[常用]

线程在创建时,默认都会给其一个指定名字,thread-0,-1,-2

  • getName()

也可以通过 方法设置名字

  • setName(String n)

也可以在创建对象时,通过构造方法设置名字

  • Thread(String name)

public static void main(String[] args) {// 自定义线程Thread t1 = new Thread("按摩线程"){@Overridepublic void run() {Thread thread = Thread.currentThread( );System.out.println(thread.getName()+"自定义线程" );}};// 设置线程名// t1.setName("洗浴线程");// 获得线程名String name = t1.getName( );System.out.println("name = " + name);t1.start();
}

4.2 获得线程对象[重要]

JDK提供了一个方法,可以获得当前正在运行的线程对象

  • Thread Thread.currentThread();

public static void main(String[] args) {// 自定义线程Thread t1 = new Thread(  ){@Overridepublic void run() {Thread thread = Thread.currentThread( );System.out.println(thread.getName()+"自定义线程" );}};t1.start();// 获得主线程对象Thread main = Thread.currentThread( );System.out.println("主线程名字 = " + main.getName() );
}

4.3 线程优先级[了解]

线程是有优先级,最高优先级是10,最低优先级是1,默认初创线程优先级是5

  • getPriority() 获得优先级

  • setPriority() 设置优先级

public class Demo2 {public static void main(String[] args) {Thread t1 = new Thread("T1") {@Overridepublic void run() {for (int i = 1; i < 101; i++) {System.out.println(getName()+"执行-->" +i );}}};t1.setPriority(1);Thread t2 = new Thread("T2") {@Overridepublic void run() {for (int i = 1; i < 101; i++) {System.out.println(getName()+"执行-->" +i );}}};t2.setPriority(1);Thread t3 = new Thread("T3") {@Overridepublic void run() {for (int i = 1; i < 101; i++) {System.out.println(getName()+"执行-->" +i );}}};t3.setPriority(10);t1.start();t2.start();t3.start();}
}

注意: 经过测试,虽然设置优先级,但是效果并不明显,不会保证绝对的顺序,只能是大概率优先..

且main方法只要在最上面先执行,优先级最高!

想要保证绝对的顺序或者交替执行等效果需要后续其他api...

4.4 守护线程

守护线程就是守护别的线程,随着守护的线程消失

需要在开启线程前,设置某一线程为守护线程,其他的线程就是被守护的 ; 即被守护的线程停止了,守护线程会随之停止

例如: qq聊天窗口和主面板,聊天窗口就是守护线程,主面板就是被守护.即聊天窗口可以随意关闭,但是主面板如果推出聊天窗口立即关闭


setDaemon(boolean n); 标记为true即为守护线程

public static void main(String[] args) {Thread dw = new Thread("大王") {@Overridepublic void run() {System.out.println("大王打仗");}};Thread xb = new Thread("小兵") {@Overridepublic void run() {for (int i = 1; i < 10000; i++) {System.out.println("小兵" + i + "在攻击...");}}};// 开启之前,设置小兵线程为守护线程xb.setDaemon(true);/*** 大王线程结束* 小兵线程发现后,也会结束不再执行完*/dw.start();xb.start();
}

4.5 礼让线程

public class Demo4 {public static void main(String[] args) {Thread t1 = new Thread("汽车") {@Overridepublic void run() {for (int i = 1; i < 1001; i++) {if (i == 100){Thread.yield();// 让出线程}System.out.println("汽车" + i + "执行...");}}};Thread t2 = new Thread("行人") {@Overridepublic void run() {for (int i = 1; i < 1001; i++) {System.out.println("行人" + i + "执行...");}}};t1.start();t2.start();}
}

4.6 join(插队)

void join() 等待该线程终止。 (无限期等待) void join(long millis) 等待该线程终止的时间最长为 millis 毫秒。 (有限期等待)

public class Demo5 {public static void main(String[] args) {Thread t2 = new Thread("行人") {@Overridepublic void run() {for (int i = 1; i < 1001; i++) {System.out.println("行人" + i + "执行...");}}};Thread t1 = new Thread("汽车") {@Overridepublic void run() {for (int i = 1; i < 1001; i++) {if (i == 100){try {//t2.join();// 让线程2加入,直到线程2执行完毕,线程1才会继续t2.join(1);// 让线程2加入,但是线程2只运行1毫秒,1毫秒后,线程1 2继续争抢} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("汽车" + i + "执行...");}}};t1.start();t2.start();}
}

4.7 sleep(线程休眠)[重要]

static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),

public static void main(String[] args) throws InterruptedException {Thread t2 = new Thread("行人") {@Overridepublic void run() {for (int i = 1; i < 1001; i++) {System.out.println("行人" + i + "执行...");}}};Thread t1 = new Thread("汽车") {@Overridepublic void run() {for (int i = 1; i < 1001; i++) {if (i == 100){try {/*** 让当前线程休眠* 让出系统资源,别的线程执行* 等当前线程休眠结束,继续执行*/Thread.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("汽车" + i + "执行...");}}};// t1.start();// t2.start();for (int i = 10; i > 0; i--) {System.out.println("倒计时: "+i+ "秒");Thread.sleep(999);}System.out.println("发射!" );
}

4.8 线程停止stop

public static void main(String[] args) {new Thread(  ){@Overridepublic void run() {for (int i = 1; i < 1001; i++) {if (i == 100) {// this.interrupt();// 给线程一个中断的信号this.stop();// 立即让线程停止}System.out.println("线程执行 --> " +i );}}}.start();
}

五、线程的状态[面试]

线程的几种状态:

  • 创建/新建/初始

    • new 完线程对象

  • 就绪

    • 调用start

  • 等待/阻塞

    • join()或者sleep()

  • 运行

    • 执行run()方法

  • 死亡/销毁/终止

    • run方法执行完,或者调用stop()或者interrupt()中断等


清楚状态之间的转换

image-20240611163017552

线程对象 new完是没有开启线程

  • new 完属于初始状态(New)

  • 调用start了开启线程,如果抢到资源执行了run方法,属于运行状态(Running)

  • 如果没抢到,属于就绪(Ready)状态

  • 线程正常执行完就进入死亡(Terminated)状态(结束)


  • 如果过程中有sleep,那么当前线程进入等待状态,但是是有期限的等待(Timed Waiting),到时进入就绪状态,准备争抢!!

  • 如果别的线程使用了join(),那么当前线程会进入无限期等待(Waiting)

  • 在加锁的情况下,一个线程获得锁执行代码,另外一个线程就进入阻塞状态(Blocked)


http://www.mrgr.cn/news/2060.html

相关文章:

  • 设计模式---构建者模式(Builder Pattern)
  • 瑞友科技项目经理认证负责人杨文娟受邀为第四届中国项目经理大会演讲嘉宾︱PMO评论
  • 如何在Java中将数据库查询结果转换为枚举类型
  • 乾坤qiankun搭建前端微服务
  • 橙色简洁大气体育直播自适应模板赛事直播门户自适应网站源码
  • 基于Shader实现的UGUI描边解决方案遇到的bug
  • 智能安全守护,寺庙安全用电解决方案
  • 基础Floyd-Warshall算法
  • C#单例模式
  • 写一个githubDemo
  • Linux搭建环境:从零开始掌握基础操作(二)
  • 螺纹钢生产线中测径仪对基圆和负公差的测量和影响
  • ???牛客周赛55:虫洞操纵者
  • 【特殊文件---properties】
  • c语言网络编程
  • 欧拉远程桌面 安装tigervnc
  • 【Mybatis-plus】Mybatis-plus的踩坑日记之速查版
  • C语言---栈
  • UE网络架构和数据通信学习笔记
  • 企业高性能web服务器