高级编程--第五章 多线程

2024-09-07 04:04

本文主要是介绍高级编程--第五章 多线程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、目标

理解线程的概念

掌握线程的创建和启动

了解线程的状态

掌握线程调度的常用方法

掌握线程的同步

理解线程安全的类型

2、进程

3、多线程

3.1 什么是多线程

一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

如果一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程

多线程交替占用CPU资源,而非真正的并行执行

线程的生命周期

线程是一个动态执行的过程,他有一个从产生到死亡的过程

线程的状态
  • 新建状态:

使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

  • 死亡状态:

一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

线程优先级

每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。

Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

多线程好处

充分利用CPU的资源

简化编程模型

带来良好的用户体验

3.2 主线程

java提供了三种创建线程的方法

1、通过实现Runnable接口

2、通过继承Thread类本身

3、通过Callable和Future创建线程

Thread类

java提供了java.lang.thread类支持多线程编程

主线程

main()方法即为主线程入口

产生其他子线程的线程

必须最后完成执行,因为它执行各种关闭动作

public static void main(String args[]) {Thread t= Thread.currentThread(); System.out.println("当前线程是: "+t.getName()); t.setName("MyJavaThread"); System.out.println("当前线程名是: "+t.getName()); 
}

使用线程步骤

3.2.1 通过继承Thread类创建线程

定义Mythread类继承Thread类

重写run()方法,编写线程执行体

创建线程对象,调用start()方法启动线程

//Mythread类
package Test01;public class Mythread extends Thread{@Overridepublic void run() {for (int i = 1;i<100;i++){System.out.println(Thread.currentThread().getName()+ ":" + i);}}
}
//测试类
package Test01;public class Test {public static void main(String[] args) {Mythread mt =new Mythread();mt.start();
//        mt.run();}
}

多个线程是交替执行的,不是真正的“并行”

线程每次执行时长由分配的CPU时间片程度决定

常见问题

启动线程是否可以直接调用run()方法?

调用run方法只有线程一条执行路径 调用start方法有多条执行路径,主线程和子线程并行交替执行

3.2.2 实现Runnalbe接口创建线程

定义MyRunnable类实现Runnable接口

实现run()方法,编写线程执行体

创建线程对象,调用start()方法启动线程

//MyRunnable
package Test02;public class MyRunnalbe implements Runnable{@Overridepublic void run() {for (int i = 1;i<100;i++){System.out.println(Thread.currentThread().getName()+ ":" + i);}}
}

//测试类
package Test02;public class Test {public static void main(String[] args) {MyRunnalbe mr1 = new MyRunnalbe();Thread mythread = new Thread(mr1);mythread.start();}}
3.2.3 通过Callable和Future创建线程

1、创建Callable接口的实现类,并实现call()方法,该call方法将作为线程执行体,并且有返回值。

2、创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call方法的返回值。

3、使用FutrueTask对象作为Thread对象的target创建并启动新线程。

4、调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

package Test03;import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;public class Test {public static void main(String[] args) {CallableThreadT ctt = new CallableThreadT();FutureTask<Integer> ft = new FutureTask<>(ctt);for (int i = 0;i < 100;i++){System.out.println(Thread.currentThread().getName() + "的循环变量i的值" + i);if (i == 20){new Thread(ft,"有返回值的线程").start();}}try{System.out.println(ft.get());}catch (Exception e){e.printStackTrace();}}
}
package Test03;import java.util.concurrent.Callable;public class CallableThreadT implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int i = 0;for (;i<100;i++){System.out.println(Thread.currentThread().getName() + ":" + i);}return null;}
}

三种方法对比

继承Thread类编写简单,可直接操作线程适用于单继承

实现Runnable接口避免单继承局限性便于共享资源

  • 1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
  • 2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

推荐使用实现Runnable接口方式创建线程

4、线程调度

线程调度指按照特定机制为多个线程分配CPU的使用权

说 明

void setPriority(int newPriority)

更改线程的优先级

static void sleep(long millis)

在指定的毫秒数内让当前正在执行的线程休眠

void join()

等待该线程终止

static void yield()

暂停当前正在执行的线程对象,并执行其他线程

void interrupt()

中断线程

boolean isAlive()

测试线程是否处于活动状态

setPriority

package Test01;public class priority {public static void main(String[] args) {//线程优先级由1~10表示,1最低,默认为5//优先级搞得线程获得CPU资源的概率大Thread t1 = new Thread(new Mythread(),"线程A");Thread t2 = new Thread(new Mythread(),"线程B");t1.setPriority(Thread.MAX_PRIORITY);t2.setPriority(Thread.MIN_PRIORITY);t1.start();t2.start();}
}

sleep

package Test01;public class sleep {public static void main(String[] args) {//sleep方法,设置休眠时间//让进程展示睡眠指定时长,线程进入阻塞状态//睡眠时间过后线程会在进入可运行状态//public static void sleep(long mills)//mills为休眠时长,以毫秒为单位//调用sleep方法需要处理InterruptedException异常for (int i= 0;i < 5;i++){System.out.println(i + 1 + "秒");try{//休眠一秒Thread.sleep(1000);}catch (InterruptedException e){e.printStackTrace();}}}
}

join

package Test01;public class join {public static void main(String[] args) {//强制执行当前进程,join写在哪个线程,就阻塞谁//public final  void join()//public final  void join(long mills)//public final  void join(long mills,int nanos)//mills:以毫秒为单位的等待时长//nanos:要等待的附加纳秒时长//需处理interruptedException异常Thread temp = new Thread(new Mythread());temp.start();for (int i = 0;i < 20;i++){if (i == 5){//当i=5时就会终止main,转而运行Thread-1/0try {temp.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + "运行:" + i);}}
}

yield

package Test01;public class Mythread extends Thread{@Overridepublic void run() {for (int i = 1;i<5;i++){System.out.println(Thread.currentThread().getName()+ ":" + i);if(i == 3){System.out.println("线程礼让");//线程礼让//yield展厅当前线程,允许其他具有相同优先级的线程获得运行机会//该线程处于就绪状态,不转为阻塞状态//只提供一种可能,但是不能保证一定会实现礼让//public static void yield();Thread.yield();}}}
}

interrupt

(总结一下:调用interrupt()方法,立刻改变的是中断状态,但如果不是在阻塞态,就不会抛出异常;如果在进入阻塞态后,中断状态为已中断,就会立刻抛出异常)

多线程共享数据引发的问题

多个线程操作同一共享资源时,将引发数据不安全问题

多个线程操作同一共享资源时,将引发数据不安全问题

5、线程同步

5.1 同步方法

使用synchronized修饰的方法控制对类成员变量的访问

访问修饰符 sunchronized 返回类型 方法名 (参数列表){...}
or
sunchronized 访问修饰符 返回类型 方法名 (参数列表){...}
//synchronized就是为当前的线程声明一把锁

5.2 同步代码块

使用synchronized关键字修饰的代码块

synchronized(syncObject){//需要同步的代码
}//其中syncObject为需同步的对象、通常为this
//效果与同步方法相同

public void run() {while (true) {synchronized (this) {   //同步代码块// 省略修改数据的代码......// 省略显示信息的代码......
}}}

多个并发线程访问同一资源的同步代码块时

同一时刻只能有一个线程进入synchronized(this)同步代码块

当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定

当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码

线程安全的类型

查看ArrayList类的add()方法的定义

ArrayList类的add方法为非同步方法

当多个线程向同一个ArrayList对象添加数据时,可能出现数据不一致问题


 

方法是否同步

效率比较

适合场景

线程安全

多线程并发共享资源

非线程安全

单线程

为达到安全性和效率的平衡,可以根据实际场景来选择合适的类型

常见类型对比

Hashtable && HashMap

Hashtable

继承关系

实现了Map接口,Hashtable继承Dictionary类

线程安全,效率较低

键和值都不允许为null

HashMap

继承关系

实现了Map接口,继承AbstarctMap类

非线程安全,效率较高

键和值都允许为null

StringBuffer && StringBuilder

前者线程安全、后者线程非安全

练习

练习一

需求说明创建两个子线程,每个线程均输出20次消息数字、“你好”、线程名观察多个线程交替执行的过程

HelloThrea

package exercise01;public class HelloThread extends Thread{@Overridepublic void run() {for (int i = 0;i < 20;i++){System.out.println(i + "、你好,来自线程" + Thread.currentThread().getName());}}
}

Test

package exercise01;public class Test {public static void main(String[] args) {HelloThread ht1 = new HelloThread();HelloThread ht2 = new HelloThread();ht1.start();ht2.start();}
}

练习二

需求说明修改上机练习1,要求线程类使用实现Runnable接口的方式创建,并修改测试类

HelloThread

package exercise02;public class HelloThread implements Runnable{@Overridepublic void run() {for (int i = 0;i < 20;i++){System.out.println(i + "、你好,来自线程" + Thread.currentThread().getName());}}
}

Test

package exercise02;import exercise02.HelloThread;public class Test {public static void main(String[] args) {HelloThread ht1 = new HelloThread();Thread t1 = new Thread(ht1);Thread t2 = new Thread(ht1);t1.start();t2.start();}
}

练习三

需求说明每个线程代表一个人可设置每人爬山速度每爬完100米显示信息爬到终点时给出相应提示

测试类


package exercise03;public class Test {public static void main(String[] args) {Thread t1 = new Thread(new ClimbThread(5,10),"年轻人");Thread t2 = new Thread(new ClimbThread(10,10),"老人");t1.start();t2.start();}
}

ClimbThread

package exercise03;public class ClimbThread extends Thread {private int time;private int num;public ClimbThread(int time, int num) {this.time = time;this.num = num;}public int getTime() {return time;}public void setTime(int time) {this.time = time;}public int getNum() {return num;}public void setNum(int num) {this.num = num;}@Overridepublic void run() {for(int i = num-1;i>0;i--){try {Thread.sleep(((long) this.time *(num-i)));System.out.println(Thread.currentThread().getName() + "爬完一百米");} catch (InterruptedException e) {e.printStackTrace();}}}
}

练习四

需求说明显示主线程、子线程默认优先级将主线程设置为最高优先级、子线程设置为最低优先级并显示

测试类

package exercise04;import java.util.Scanner;public class main {public static void main(String[] args) {Thread t = new Thread(new ChildThread());System.out.println("******修改前优先级******");System.out.println("主线程:" + Thread.currentThread().getName() + "、优先级为: " + Thread.currentThread().getPriority());System.out.println("子线程:" + t.getName() + "、优先级为: " + t.getPriority());System.out.println("修改主优先级与子线程优先级");Scanner sc = new Scanner(System.in);Thread.currentThread().setPriority(sc.nextInt());t.setPriority(sc.nextInt());System.out.println("******修改后优先级******");System.out.println("主线程:" + Thread.currentThread().getName() + "、优先级为: " + Thread.currentThread().getPriority());System.out.println("子线程:" + t.getName() + "、优先级为: " + t.getPriority());}
}

Child

package exercise04;public class ChildThread extends  Thread{@Overridepublic void run() {System.out.println(" " + Thread.currentThread().getName());}
}

练习五

需求说明某科室一天需看普通号50个,特需号10个特需号看病时间是普通号的2倍开始时普通号和特需号并行叫号,叫到特需号的概率比普通号高当普通号叫完第10号时,要求先看完全部特需号,再看普通号使用多线程模拟这一过程

CummonNumber

package exercise05;public class CummonNumber extends Thread{private Thread VIpNumber1;public CummonNumber(Thread VIpNumber1){this.VIpNumber1 = VIpNumber1;}public Thread getVIpNumber1() {return VIpNumber1;}public void setVIpNumber1(Thread VIpNumber1) {this.VIpNumber1 = VIpNumber1;}@Overridepublic void run() {for (int i = 0;i<40;i++){System.out.println("普通号:" + (i + 1) + "号病人开始看病" );if (i == 9){try {VIpNumber1.join();} catch (InterruptedException e) {e.printStackTrace();}}try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}
}

VIpNumber

package exercise05;public class VIpNumber extends Thread{@Overridepublic void run() {for (int j = 0; j<10;j++){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("特需号:" + (j + 1)+ "号病人在看病");}}
}

Test

package exercise05;public class Test {public static void main(String[] args) {VIpNumber v1 = new VIpNumber();Thread t1 = new Thread(new CummonNumber(v1)) ;v1.setPriority(10);t1.setPriority(1);v1.start();t1.start();}
}

练习六

需求说明多人参加1000米接力跑每人跑100米,换下个选手每跑10米显示信息

Test

package exercise06;public class Test {public static void main(String[] args) {Run r1 = new Run();Thread t1 = new Thread(r1,"一号");Thread t2 = new Thread(r1,"二号");Thread t3 = new Thread(r1,"三号");t1.start();t2.start();t3.start();}
}

Run

package exercise06;public class Run extends Thread{private int sum = 300;private int num = 100;private Boolean flag =false;//这里加入syncronized使该方法一次只能由一个线程调用,来实现选手逐个交棒跑,而不是一起跑@Overridepublic synchronized void run() {
//            while(true){
//                if (flag){
//                    break;
//                }else {
//                    go();
//                }
//            }if (!flag){go();}}public void go(){System.out.println(Thread.currentThread().getName() + "拿到了接力棒");for (int i = 0;i<=100;i++){if (i%10 == 0){System.out.println(Thread.currentThread().getName() + "选手跑了" + i + "米");}num -= 1;sum -= 1;if (sum <= 0){System.out.println("跑完了");flag = true;break;}}}
}

练习七

“桃跑跑”、“张票票”、“黄牛党”共同抢10张票限“黄牛党”只能抢一张票

Test

package exercise07;public class Test {public static void main(String[] args) {Ticket ticket = new Ticket();Thread t1 = new Thread(ticket,"陶泡泡");Thread t2 = new Thread(ticket,"里跳跳");Thread t3 = new Thread(ticket,"黄牛党");t1.start();t2.start();t3.start();}
}

Ticket

package exercise07;public class Ticket extends Thread{private int sum = 10;
//    private Boolean flag = false;//这里不在这里使用synchronized是因为这里如果给润添加该修饰,会导致只有第一个进入的线程能获得票,而其他的线程不会货的票@Overridepublic void run() {while(true){//这里限制了进入方法体的数量,不会让他们同时进入方法体,而是一个一个进行访问synchronized (this) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}if (sum == 0) {
//                    flag = true;break;}}System.out.println(Thread.currentThread().getName() + "抢到了一张票" + "剩余" + sum + "张");sum -= 1;if ("黄牛党".equals(Thread.currentThread().getName())){break;}}}
}

这篇关于高级编程--第五章 多线程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1144004

相关文章

Python异步编程中asyncio.gather的并发控制详解

《Python异步编程中asyncio.gather的并发控制详解》在Python异步编程生态中,asyncio.gather是并发任务调度的核心工具,本文将通过实际场景和代码示例,展示如何结合信号量... 目录一、asyncio.gather的原始行为解析二、信号量控制法:给并发装上"节流阀"三、进阶控制

Java使用多线程处理未知任务数的方案介绍

《Java使用多线程处理未知任务数的方案介绍》这篇文章主要为大家详细介绍了Java如何使用多线程实现处理未知任务数,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 知道任务个数,你可以定义好线程数规则,生成线程数去跑代码说明:1.虚拟线程池:使用 Executors.newVir

JAVA封装多线程实现的方式及原理

《JAVA封装多线程实现的方式及原理》:本文主要介绍Java中封装多线程的原理和常见方式,通过封装可以简化多线程的使用,提高安全性,并增强代码的可维护性和可扩展性,需要的朋友可以参考下... 目录前言一、封装的目标二、常见的封装方式及原理总结前言在 Java 中,封装多线程的原理主要围绕着将多线程相关的操

kotlin中的行为组件及高级用法

《kotlin中的行为组件及高级用法》Jetpack中的四大行为组件:WorkManager、DataBinding、Coroutines和Lifecycle,分别解决了后台任务调度、数据驱动UI、异... 目录WorkManager工作原理最佳实践Data Binding工作原理进阶技巧Coroutine

Python中多线程和多进程的基本用法详解

《Python中多线程和多进程的基本用法详解》这篇文章介绍了Python中多线程和多进程的相关知识,包括并发编程的优势,多线程和多进程的概念、适用场景、示例代码,线程池和进程池的使用,以及如何选择合适... 目录引言一、并发编程的主要优势二、python的多线程(Threading)1. 什么是多线程?2.

SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

《SpringBoot中使用ThreadLocal进行多线程上下文管理及注意事项小结》本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLo... 目录前言技术积累1.什么是 ThreadLocal2. ThreadLocal 的原理2.1 线程隔离2

Java多线程父线程向子线程传值问题及解决

《Java多线程父线程向子线程传值问题及解决》文章总结了5种解决父子之间数据传递困扰的解决方案,包括ThreadLocal+TaskDecorator、UserUtils、CustomTaskDeco... 目录1 背景2 ThreadLocal+TaskDecorator3 RequestContextH

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

深入解析Spring TransactionTemplate 高级用法(示例代码)

《深入解析SpringTransactionTemplate高级用法(示例代码)》TransactionTemplate是Spring框架中一个强大的工具,它允许开发者以编程方式控制事务,通过... 目录1. TransactionTemplate 的核心概念2. 核心接口和类3. TransactionT

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言