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

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

相关文章

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引言

浅析Rust多线程中如何安全的使用变量

《浅析Rust多线程中如何安全的使用变量》这篇文章主要为大家详细介绍了Rust如何在线程的闭包中安全的使用变量,包括共享变量和修改变量,文中的示例代码讲解详细,有需要的小伙伴可以参考下... 目录1. 向线程传递变量2. 多线程共享变量引用3. 多线程中修改变量4. 总结在Rust语言中,一个既引人入胜又可

Python中列表的高级索引技巧分享

《Python中列表的高级索引技巧分享》列表是Python中最常用的数据结构之一,它允许你存储多个元素,并且可以通过索引来访问这些元素,本文将带你深入了解Python列表的高级索引技巧,希望对... 目录1.基本索引2.切片3.负数索引切片4.步长5.多维列表6.列表解析7.切片赋值8.删除元素9.反转列表

正则表达式高级应用与性能优化记录

《正则表达式高级应用与性能优化记录》本文介绍了正则表达式的高级应用和性能优化技巧,包括文本拆分、合并、XML/HTML解析、数据分析、以及性能优化方法,通过这些技巧,可以更高效地利用正则表达式进行复杂... 目录第6章:正则表达式的高级应用6.1 模式匹配与文本处理6.1.1 文本拆分6.1.2 文本合并6

C#反射编程之GetConstructor()方法解读

《C#反射编程之GetConstructor()方法解读》C#中Type类的GetConstructor()方法用于获取指定类型的构造函数,该方法有多个重载版本,可以根据不同的参数获取不同特性的构造函... 目录C# GetConstructor()方法有4个重载以GetConstructor(Type[]

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor