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

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

相关文章

Linux 网络编程 --- 应用层

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

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

Go Playground 在线编程环境

For all examples in this and the next chapter, we will use Go Playground. Go Playground represents a web service that can run programs written in Go. It can be opened in a web browser using the follow

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

函数式编程思想

我们经常会用到各种各样的编程思想,例如面向过程、面向对象。不过笔者在该博客简单介绍一下函数式编程思想. 如果对函数式编程思想进行概括,就是f(x) = na(x) , y=uf(x)…至于其他的编程思想,可能是y=a(x)+b(x)+c(x)…,也有可能是y=f(x)=f(x)/a + f(x)/b+f(x)/c… 面向过程的指令式编程 面向过程,简单理解就是y=a(x)+b(x)+c(x)

多线程解析报表

假如有这样一个需求,当我们需要解析一个Excel里多个sheet的数据时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。 Way1 join import java.time.LocalTime;public class Main {public static void main(String[] args) thro

Java并发编程之——BlockingQueue(队列)

一、什么是BlockingQueue BlockingQueue即阻塞队列,从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。被阻塞的情况主要有如下两种: 1. 当队列满了的时候进行入队列操作2. 当队列空了的时候进行出队列操作123 因此,当一个线程试图对一个已经满了的队列进行入队列操作时,它将会被阻塞,除非有另一个线程做了出队列操作;同样,当一个线程试图对一个空

Java 多线程概述

多线程技术概述   1.线程与进程 进程:内存中运行的应用程序,每个进程都拥有一个独立的内存空间。线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换、并发执行,一个进程最少有一个线程,线程实际数是在进程基础之上的进一步划分,一个进程启动之后,进程之中的若干执行路径又可以划分成若干个线程 2.线程的调度 分时调度:所有线程轮流使用CPU的使用权,平均分配时间抢占式调度