第20 章 多线程

20.1线程简介.


20.2创建线程
2.1继承Thread类
Thread 类是java.lang包中的一个类,从这个类中实例化的对象代表线程,程序员启动一个新线程需要建立Thread 实例。Thread类中常用的两个构造方法如下:

public Thread():创建一个新的线程对象。
public Thread(String threadName):创建一个名称为threadName的线程对象。
继承Thread 类创建一个新的线程的语法如下:

public class ThreadTest extends Thread{
}

完成线程真正功能的代码放在类的run(方法中,当一个类继承Thread类后,就可以在该类中覆盖 run()方法,将实现该线程功能的代码写入run()方法中,然后调用Thread类中的start()方法执行线程,也就是调用run()方法。
Thread 对象需要一个任务来执行,任务是指线程在启动时执行的工作,该工作的功能代码被写在 run()方法中。run()方法必须使用以下语法格式:

public void run(){
}

注意如果 start()方法调用一个已经启动的线程,系统将抛出IllegalThreadStateException异常。

2.2实现Runnable接口(单继承,所以要使用接口)
实现接口语法:

public class Thread extends  Object implements Runnable

实现Runnable接口的程序会创建一个Thread 对象,并将Runnable对象与Thread对象相关联。Thread    
类中有以下两个构造方法:


☑public Thread(Runnable target)
☑public Thread(Runnable target,String name)

这两个构造方法的参数中都存在Runnable实例,使用以上构造方法就可以将Runnable 实例与 Thread 实例相关联。
使用Runnable接口启动新的线程的步骤如下:

(1)建立Runnable 对象。
(2)使用参数为Runnable对象的构造方法创建Thread实例。(3)调用start()方法启动线程。
通过 Runnable接口创建线程时,程序员首先需要编写一个实现 Runnable接口的类,然后实例化该类的对象,这样就建立了Runnable对象;接下来使用相应的构造方法创建 Thread 实例;最后使用该实例调用Thread类中的 start()方法启动线程。

 import java.awt.Container;import javax.swing.JFrame;
import javax.swing.JLabel;public class SwingAndThread extends JFrame{int count =0;public SwingAndThread(){setBounds(300,200,250,100);	//绝对定位窗体大小与位置	Container container = getContentPane();	//主容器	container.setLayout(null);	//使窗体不使用任何布局管理器	lcon icon = new lmagelcon("/D20/src/1.gif");	//图标对象	JLabel jl = new JLabel(icon);	//显示图标的标签	jl.setBounds(10,10,200,50);	//设置标签的位置与大小	Thread t = new Thread(){	//定义匿名线程对象	public void run() {while (true) {jl.setBounds(count,10,200,50);	//将标签的横坐标用变量表示	try{Thread.sleep(500);	//使线程休眠 500毫秒	} catch (InterruptedException e){e.printStackTrace();}count += 4;	//使横坐标每次增加4	if (count >= 200){count = 10;	//当图标到达标签的最右边时,使其回到标签最左边	}}}};	t.start();	container.add(jl);	//启动线程	//将标签添加到容器中	setVisible(true);	//使窗体可见	setDefaultCloseOperation(EXIT_ON_CLOSE);	//设置窗体的关闭方式}public static void main(String[] args) {new SwingAndThread();}}

20.3线程的生命周期
线程具有生命周期,其中包含7种状态,分别为出生状态、就绪状态、运行状态、等待状态、休眠状态、阻塞状态和死亡状态。出生状态就是线程被创建时处于的状态,在用户使用该线程实例调用 start()方法之前线程都处于出生状态;当用户调用start()方法后,线程处于就绪状态(又被称为可执行状态);当线程得到系统资源后就进入运行状态。
一旦线程进入可执行状态,它会在就绪与运行状态下转换,同时也有可能进入等待、休眠、阻塞或死亡状态。当处于运行状态下的线程调用Thread类中的wait()方法时,该线程便进入等待状态,进入等待状态的线程必须调用Thread类中的notify0方法才能被唤醒,而调用notifyAl1O)方法可将所有处于等待状态下的线程唤醒;当线程调用Thread类中的sleep(方法时,则会进入休眠状态。如果一个线程在运行状态下发出输入/输出请求,该线程将进入阻塞状态,在其等待输入/输出结束时线程进入就绪状态,对于阻塞的线程来说,即使系统资源空闲,线程依然不能回到运行状态。当线程的run()方法执行完毕时,线程进入死亡状态。
说明
使线程处于不同状态下的方法会在20.4节中进行讲解,在此读者只需了解线程的多个状态即可

20.4操作线程的方法
4.1线程的休眠
  

	import java.awt.*;import java.util.Random; import javax.swing.*;public class SleepMethodTest extends JFrame {private static Color[]color={ Color.BLACK, Color.BLUE,Color.CYAN,Color.GREEN,Color.ORANGE, Color.YELLOW, Color.RED,Color.PINK,Color.LIGHT_GRAY};//定义颜色数组private static final Random rand=new Random();	//创建随机对象	private static Color getC(){	//获取随机颜色值的方法	return color[(rand.nextInt(color.length))];}public SleepMethodTest() {Thread t = new Thread(new Runnable() {//创建匿名线程对象	int x=30;	Il定义初始坐标	int y=50;public void run(){while (true) {	//无限循环	try{	Thread.sleep(100);	//线程休眠 0.1秒	} catch (InterruptedException e){e.printStackTrace();Graphics graphics=getGraphics();	//获取组件绘图上下文对象	graphics.setColor(getC());	//设置绘图颜色	graphics.drawLine(x,y,100,y++);	//绘制直线并递增垂直坐标	if (y >=80) {y = 50;}}}});t.start();	//启动线程	}public static void main(String[]args){init(new SleepMethodTest(),100,100);}public static void init(JFrame frame, int width, int height){//初始化程序界面的方法frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(width, height); frame.setVisible(true);}}

4.2线程的加入
使用join()方法加入线程

import java.awt.BorderLayout;
import javax.swing.*;
public class JoinTest extends JFrame {
private Thread threadA;	//定义两个线程	
private Thread threadB;
private JProgressBar progressBar = new JProgressBar();	//定义两个进度条组件	
private JProgressBar progressBar2=new JProgressBar();
public static void main(String[] args) {JoinTest test = new JoinTest(); test.setVisible(true);public JoinTest(){setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(200,200,200,100);getContentPane().add(progressBar, BorderLayout.NORTH); //将进度条设置在窗体最北面 getContentPane().add(progressBar2,BorderLayout.SOUTH); //将进度条设置在窗体最南面progressBar.setStringPainted(true);	//设置进度条显示数字字符	progressBar2.setStringPainted(true);threadA = new Thread(new Runnable() {	//使用匿名内部类形式初始化 Thread实例	int count = 0;public void run() {	//重写 run()方法	while (true) {progressBar.setValue(++count);	//设置进度条的当前值	try {Thread.sleep(100);	//使线程A 休眠 100毫秒	threadB.join();	//使线程B调用 join()方法	}catch (InterruptedException e){e.printStackTrace();}}}});threadA.start();	//启动线程AthreadB= new Thread(new Runnable(){int count = 0;public void run() {while (true) {progressBar2.setValue(++count);	//设置进度条的当前值	try{	Thread.sleep(100);	//使线程B休眠 100 毫秒	} catch (InterruptedException e) {e.printStackTrace();if (count ==100)	break;	//跳出循环	}}});	//启动线程B	threadB.start();}}

 4.3线程的中断
现在提倡在r un()方法中使用无限循环的形式,然后使用一个布尔型标记控制循环的停止。
如果线程是因为使用了sleep()或 wait()方法:进入了就绪状态,可以使用Thread 类中interrupt)方法使线程离开run()方法,同时结束线程,但程序会会抛出 InterruptedException异常,用户可以在处理该异常时完成线程的中断业务处理,如终止while 循环。
下面的实例演示了某个线程使用interrupted0方法,同时程序抛出了InterruptedException异常,在异常处理时结束了while循环。在项目中,经常在这里执行关闭数据库连接和关闭Socket连接等操作。

import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;public class InterruptedSwing extends JFrame {public InterruptedSwing(){JProgressBar progressBar = new JProgressBar();	//创建进度条	getContentPane().add(progressBar, BorderLayout.NORTH);//将进度条放置在窗体合适位置JButton button=new JButton("停止");getContentPane().add(button,BorderLayout.SOUTH);progressBar.setStringPainted(true);	//设置进度条上显示数字	Thread t = new Thread(new Runnable(){int count = 0; public void run() {while (true){progressBar.setValue(++count);//设置进度条的当前值try{ Thread.sleep(100);	//使线程休眠 100毫秒	} catch (InterruptedException e){//捕捉InterruptedException 异常System.out.println("当前线程序被中断"); break;}}}});button.addActionListener(new ActionListener(){@Overridepublic void actionPerformed(ActionEvent e){t.interrupt();	//中断线程	}});	t.start();	//启动线程	}public static void init(JFrame frame, int width, int height) {frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(width, height); frame.setVisible(true);}public static void main(String[] args){init(new InterruptedSwing(),100,100);}}

 4.4线程的礼让
Thread类中提供了一种礼让方法,使用 yield()方法表示,它只是给当前正处于运行状态的线程一
个提醒,告知它可以将资源礼让给其他线程    但这仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。
yield()方法使具有同样优先级的线程有过进入可执行状态的机会,在当前线程放弃执行权时会再度回到就绪状态。对于支持多任务的操作系统来说,不需要调用yield()方法,因为操作系统会为线程自动分配 CPU 时间片来执行。

20.5线程的优先级
 

public class PT implements Runnable {String name;public PT(String name) {this.name = name; }@Overridepublic void run() {String tmp= "";for (int i= 0; i< 50000; i++) {tmp +=i;
//完成5万次字符串拼接}System.out.println(name +"线程完成任务");}public static void main(String[]args){Thread a = new Thread(new PT("A"));a.setPriority(1);	//A线程优先级最小	Thread b = new Thread(new PT("B"));b.setPriority(3);Thread c = new Thread(new PT("C"));	c.setPriority(7);	Thread d = new Thread(new PT("D"));d.setPriority(10);	//D线程优先级最大	a.start();	b.start();	c.start();	d.start();	}}

线程的优先级可以使用setPriority()方法调整,如   果使用该方法设置的优先级不在1~10,将产生  IllegalArgumentException 异常。

20.6线程同步
在单线程程序中,每次只能做一件事情,后面的事情需要等待前面的事情完成后才可以进行,但是如果使用多线程程序,就会发生两个线程抢占资源的问题,如两个人同时说话、两个人同时过同一个独木桥等。所以,在多线程编程中需要防止这些资源访问的冲突。Java提供了线程同步的机制来防止资源访问的冲突。


6.1 线程安全

实际开发中,使用多线程程序的情况很多,在编写多线程程序时,应该考虑到线程安全问题,实质上线程安全问题来源于两个线程同时存取单一对象的数据。

 

 public class ThreadSafeTest implements Runnable {//设置当前总票数int num = 10;public void run() {//设置无限循环while (true) {if (num > 0){	//判断当前票数是否大于0	try {Thread.sleep(100);	//使当前线程休眠100毫秒	} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+ "----票数"+ num--);//票数减1}}}public static void main(String[]args){ThreadSafeTest t= new ThreadSafeTest();	//实例化类对象	Thread tA = new Thread(t,"线程一");	//以该类对象分别实例化4个线程	Thread tB = new Thread(t,"线程二"); Thread tC = new Thread(t,"线程三"); Thread tD= new Thread(t,"线程四");tA.start();	//分别启动线程	tB.start(); tC.start();tD.start();}
}

从这个结果可以看出,最后打印出的剩下的票数为负值,这样就出现了问题。这是由于同时创建了4个线程,这4个线程执行run()方法,在num变量为1时,线程一、线程二、线程三、线程四都对num 变量有存储功能,当线程一执行run()方法时,还没有来得及做递减操作,就指定它调用sleep()方法进入就绪状态,统等。这种多线程的    这时线程二、线程三和线程四也都进入了run()方法,发现num 变 如果大于0则执行    量依然大于 0,但此时线程一休眠时间已到,将num变量值递减张票),第一个线程    同时线程二、线程三、线程四也都对num变量进行递减操作,从 而产生了负值。

6.2线程同步机制
1.同步块
Java 中提供了同步机制,可以有效地防止资源冲突。同步机制使用synchronized关键字, 
 语法如下: 

synchronized (Object){
}

通常将共享资源的操作放置在synchronized定义的区域内,这样当其他线程获取到这个锁时,就必须等待锁被释放后才可以进入该区域。Object为任意一个对象,每个对象都存在一个标志位,并具有两个值,分别为0和1。一个线程运行到同步块时首先检查该对象的标志位,如果为0状态,表明此同步块内存在其他线程,这时当期线程处于就绪状态,直到处于同步块中的线程执行完同步块中的代码后,这时该对象的标识位设置为1,当期线程才能开始执行同步块中的代码,并将Object对象的标识位设置为0,以防止其他线程执行同步块中的代码。

public class SynchronizedTest implements Runnable { int num = 10;//设置当前总票数public void run() {while (true){	//设置无限循环	synchronized (this){	//设置同步代码块	if (num > 0) {	//判断当前票数是否大于0	try {Thread.sleep(100);	//使当前线程体眠 100} catch (InterruptedException e) {e.printStackTrace();}//票数减 1System.out.println(Thread.currentThread().getName()+"--票数" + num--);}}}}public static void main(String[] args){//实例化类对象SynchronizedTest t = new SynchronizedTest();//以该类对象分别实例化4个线程 Thread tA= new Thread(t,"线程一"); Thread tB = new Thread(t,"线程二");Thread tC = new Thread(t,"线程三"); Thread tD = new Thread(t,"线程四");tA.start();	//分别启动线程	tB.start(); tC.start(); tD.start();}}

2.同步方法

同步方法就是在方法前面用synchronized关键字修饰的方法,其语法如下:

synchronized void f(){}

当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行。必须将每个能访问共享资源的方法修饰为synchronized,否则就会出错。
修改例20.7的代码,将共享资源操作放置在一个同步方法中,代码如下:

int num = 10;
public synchronized void doit() {    //定义同步方法
if(num>0){
try{
Thread.sleep(10);
}catch(interruptedException e){
e.printStackTrace():
System.out.printin(Thread.currentThread().getName()+"-票数” +num--);
public void run(){while(true){
doit():	//在run()方法中调用该同步方法


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/184275.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】单链表---C语言版

【数据结构】单链表---C语言版 一、顺序表的缺陷二、链表的概念和结构1.概念&#xff1a; 三、链表的分类四、链表的实现1.头文件&#xff1a;SList.h2.链表函数&#xff1a;SList.c3.测试函数&#xff1a;test.c 五、链表应用OJ题1.移除链表元素&#xff08;1&#xff09;题目…

多媒体信号处理复习笔记 --脑图版本

多媒体信号处理复习笔记 --脑图版本 依据 [2020多媒体信号处理复习笔记] 考前复习时使用Xmind制作 例图: PDF下载 BaiduYunPan 提取码&#xff1a;jbyw CSDN 下载

从零构建属于自己的GPT系列1:文本数据预处理、文本数据tokenizer、逐行代码解读

&#x1f6a9;&#x1f6a9;&#x1f6a9;Hugging Face 实战系列 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在PyCharm中进行 本篇文章配套的代码资源已经上传 从零构建属于自己的GPT系列1&#xff1a;文本数据预处理 从零构建属于自己的GPT系列2&#xff1a;语…

渲染到纹理:原理及WebGL实现

这篇文章是WebGL系列的延续。 第一个是从基础知识开始的&#xff0c;上一个是向纹理提供数据。 如果你还没有阅读过这些内容&#xff0c;请先查看它们。 NSDT在线工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - …

ffmpeg 把mp4文件中某段视频转成gif文件

一 缘起背景&#xff1a; 有视频文件转gif动图的需求&#xff1b;网上下载的转换工具需要注册会员、否则带水印&#xff0c;还限制时长。 二 工具环境&#xff1a; win10 下 dos 操作 ffmpeg 三 操作命令&#xff1a; ffmpeg -i test.mp4 -ss 00:01:01 -t 00:00:19 -vf &q…

什么牌子的台灯对孩子的眼睛好?安利五款适合孩子备考的护眼台灯

近年来&#xff0c;青少年的近视问题越来越严重&#xff0c;近视率持续升高&#xff0c;不少上小学一年级就已经戴上了厚厚的近视眼镜。导致这种现象发生的原因有两个&#xff0c;一个是孩子长时间使用电子产品导致。还有就是现在孩子的学习任务&#xff0c;不仅远比80、90后上…

【开源】基于JAVA的高校学生管理系统

项目编号&#xff1a; S 029 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S029&#xff0c;文末获取源码。} 项目编号&#xff1a;S029&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生管理模块2.2 学院课程模块2.3 学…

Python基础语法之学习表达式进行符串格式化

Python基础语法之学习表达式进行符串格式化 一、代码二、效果 一、代码 print("11等于%d" % (1 1)) print(f"2/1等于{2 / 1}") print("字符串类型是%s" % type("字符串"))二、效果 坚持追求自己的梦想&#xff0c;即使道路漫长曲折&…

模板引擎详解

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 &#x1f324;️动态页面的渲染方式 …

盘点68个Android系统源码安卓爱好者不容错过

盘点68个Android系统源码安卓爱好者不容错过 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 源码下载链接&#xff1a;https://pan.baidu.com/s/1FcBxCe7KpJsh0zFxNZ_7wg?pwd8888 提取码&#xff1a;8888 项目名称 Android …

外贸B2B自建站怎么建?做海洋建站的方法?

如何搭建外贸B2B自建站&#xff1f;外贸独立站建站方法有哪些&#xff1f; 对于许多初次涉足者来说&#xff0c;搭建一个成功的外贸B2B自建站并不是一件轻松的任务。海洋建站将为您详细介绍如何有效地建设外贸B2B自建站&#xff0c;让您的国际贸易之路更加畅通无阻。 外贸B2B…

Android中使用Google Map

在app的使用过程中&#xff0c;我们经常会跟地图进行交互&#xff0c;如果是海外的应用&#xff0c;那选择使用Google Map 是最合适的选择。 在Android中如何使用Google Map&#xff0c;这里做一个简要的说明。 Google API_KEY的申请 Google Map 的使用并不是免费的&#xf…

主播岗位面试

一、自我介绍 在面试的开始阶段&#xff0c;你需要准备一个简洁而有力的自我介绍。这个自我介绍应该包括你的姓名、教育背景、工作经验以及你为何对这个主播职位感兴趣。这个自我介绍应该控制在1-2分钟之内&#xff0c;避免冗长的表述。 二、主播经历和特点 在这个环节&…

javaagent字节码增强浅尝

概述 javaagent 技术广泛应用于对代码的增强&#xff0c;比如统计方法执行时间、GC 信息打印、分布式链路跟踪等&#xff1b;实现方式包括 javassist 和 bytebuddy&#xff0c;bytebuddy 是对 javassist 的改进&#xff1b;类似于 spring 中的 AOP&#xff1b; Instrumentati…

京东数据运营-京东数据平台-京东店铺数据分析-2023年10月京东烘干机品牌销售榜

鲸参谋监测的京东平台10月份烘干机市场销售数据已出炉&#xff01; 10月份&#xff0c;烘干机市场整体销售上涨。鲸参谋数据显示&#xff0c;今年10月份&#xff0c;京东平台上烘干机的销量将近5万件&#xff0c;环比增长约77%&#xff0c;同比增长约22%&#xff1b;销售额将近…

XUbuntu22.04之OBS强大录屏工具(一百九十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

NX/UG二次开发—踩坑(边上点与面上点)

获取视图内遮挡面时&#xff0c;特别是与视图平行的面认为是可视面&#xff0c;但NX选择认为是非可视面&#xff0c;设计方案时只检查边上的点&#xff0c;发现一些面显示干涉遮挡&#xff0c;通过打印数据发现&#xff0c;以边上点为参考&#xff0c;获取面上点&#xff0c;会…

kubernetes(K8s)(Namespace、Pod、Deployment、Service资源的基本操作)-04

Namespace Namespace是kubernetes系统中的一种非常重要资源&#xff0c;它的主要作用是用来实现多套环境的资源隔离或者多租户的资源隔离。 默认情况下&#xff0c;kubernetes集群中的所有的Pod都是可以相互访问的。但是在实际中&#xff0c;可能不想让两个Pod之间进行互相的…

leetcode 18. 四数之和(优质解法)

代码&#xff1a; class Solution {public List<List<Integer>> fourSum(int[] nums, int target) {List<List<Integer>> listsnew ArrayList<>();int lengthnums.length;Arrays.sort(nums);for(int i0;i<length-4;){for(int ji1;j<lengt…

第十五届蓝桥杯(Web 应用开发)模拟赛 2 期-大学组(详细分析解答)

目录 1.相不相等 1.1 题目要求 1.2 题目分析 1.3 源代码 2.三行情书 2.1 题目要求 2.2 题目分析 2.3 源代码 3.电影院在线订票 3.1 题目要求 3.2 题目分析 3.3 源代码 4.老虎坤&#xff08;不然违规发不出来&#xff09; 4.1 题目要求 4.2 题目分析 4.3 源代码 …