二十章多线程

概念

有很多工作是可以同时完成的,这种思想放在Java中被称为并发,并发完成每一件事被称为线程。

程序员可以在程序中执行多个线程,每一个线程完成一个功能//与其他线程并发执行,这种机制被称为多线程,并不算所有编程语言都支持多线程。

创建线程

继承Thread类和实现Runnable接口两种方法

继承Thread类

是Java.long包下的一个类,在这个类中实例化对象代表线程,程序员启动一个新线程需要建立一个实例。Thread类常用的两种构造方法如下:

public Thread():创建一个新的线程对象

public Thread(String threadName):创建一个名为threadName的线程对象

继承Thread类创建一个新的线程的语法如下:

public class ThreadTest extends Thread{

}

完成线程真实代码的功能放在run()方法,当继承Thread类后,就可以在该线程中覆盖run()方法,将实现线程功能的代码写入run()方法中,调用run()方法。

Thread对象需要一个任务来执行,任务是指线程在启动时执行的工作,这个代码写在了run()方法中,语法格式如下:

public void run(){

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

例题20.1

package lx;public class Demo20_1 extends Thread {public void run(){for(int i=0;i<=10;i++) {System.out.println(i+"");}}public static void main(String[] args) {Demo20_1 th=new Demo20_1();th.start();}}

结果

 

实现Runnable接口

线程都是通过扩展 Thread 类来创建的,如果需要继承其他类(非 Thread类),而且还要使当前类实现多线程,那么可以通过 Runnable 接口来实现。实现 Runnable 接口的语法如下:

public class Thread extends Object implements Runnable{
}

 实质上 Thread 类实现了 Runnable 接口,其中的run()方法正是对 Runnable 接口中的 run()方法的具体实现

实现 Runnable 接口的程序会创建一个 Thread 对象,并将 Runnable 对象与 Thread 对象相关联。

Thread 类中有两个构造方法:

public Thread(Runnable target)。

public Thread(Runnable target,String name)。

这两个构造方法的参数中都存在 Runnable 实例

使用 Runnable 接口启动新的线程的步骤如下:

建立 Runnable 对象。

使用参数为 Runnable 对象的构造方法创建 Thread 实例。

调用 start()方法启动线程。

 

 线程最引人注目的部分应该是与 Swing 相结合创建GUI程序

例题20.2

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

结果

 

 

为了使图标具有滚动功能,需要在类的构造方法中创建 Thread 实例。

在创建该实例的同时需要 Runnable 对象作为 Thread 类构造方法的参数,然后使用内部类形式实现 run()方法。

在 run()方法中主要循环图标的横坐标位置,

当图标横坐标到达标签的最右方时,再次将图标的横坐标置于图标滚动的初始位置。
启动一个新的线程,不是直接调用 Thread 子类对象的 run()方法,而是调用 Thread 子类的 start()方法,Thread 类的 start()方法产生一个新的线程,该线程运行 Thread 子类的 run()方法。

 

 线程的生命周期

线程具有生命周期,其中包含 7 种状态,分别为出生状态、就绪状态、运行状态、等待状态、休眠状态、阻塞状态和死亡状态。

出生状态就是线程被创建时处于的状态,在用户使用该线程实例调用start()方法之前线程都处于出生状态.

当用户调用 start()方法后,线程处于就绪状态 

当线程得到系统资源后就进入运行状态。

旦线程进入可执行状态,它会在就绪与运行状态下转换,同时也有可能进入等待、休眠、阻塞或死亡状态。

虽然多线程看起来像同时执行,但事实上在同一时间点上只有一个线程被执行,只是线程之间切换较快,所以才会使人产生线程是同时进行的假象。

操作线程的方法

操作线程有很多方法,这些方法可以使线程从某一种状态过波到另一种状态。

 线程的休眠

一种能控制线程行为的方法是调用 seep()方法,sleep()方法需要一个参数用于指定该线程休眠的时间,该时间以毫秒为单位。slep()方法的语法如下:

try{
Thread.sleep(2000);

}catch(InterruptedException e){
e.printStackTrace();

}

上述代码会使线程在 2 秒之内不会进入就绪状态。

由于 sleep()方法的执行有可能抛InterrupledException 异常,所以将 sleep()方法的调用放在 try-catch 块中。

不能保证线程醒来后进入运行状态,只能保证它进入就绪状态。
 

 例题20.3

package lx;import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;import javax.swing.JFrame;public class Demo20_3 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 Demo20_3() {Thread t=new Thread(new Runnable() {//创建匿名线程对象int x=70;//定义初始坐标int y=50;public void run() {while(true) {//无限循环try {Thread.sleep(100);//线程休眠0.1秒}catch(InterruptedException e){e.printStackTrace();}Graphics g= getGraphics();//获取组件绘图上下文对象g.setColor(getC());//设置绘图颜色g.drawLine(x, y, 200, y++);//绘制直线并递增垂直坐标if(y>=100) {y=50;}}}});t.start();//启动线程}public static void main(String[] args) {init(new Demo20_3(),300,100);}public static void init(JFrame f,int w,int h) {//初始化程序界面的方法f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);f.setSize(w, h);f.setVisible(true);}
}

结果

 

在本实例中定义了 getC()方法,该方法用于随机产生 Color 类型的对象并且在产生线程的匿名内部类中使用 getGraphics()方法获取 Graphics 对象,使用该对象调用 setColor()方法为图形设置颜色。调用 drawLine()方法绘制一条线段,同时线段会根据纵坐标的变化自动调整。
 

线程的加入

如果当前某程序为多线程程序,假如存在一个线程 A,现在需要插入线程 B,并要求线程 B 先执行完毕,然后再继续执行线程 A,此时可以使用 Thread 类中的 join()方法来完成。

例题20.4

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

结果

 

线程的中断

如果线程是因为使用了 sleep()或 wait()方法进入了就绪状态,可以使用 Thread 类中 interrupt()方法使线程离开run()方法,同时结束线程,但程序会抛出 InterruptedException 异常,用户可以在处理该异常时完成线程的中断业务处理,如终止 while 循环。
例题20.5

package lx;import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JProgressBar;public class Demo20_5 extends JFrame{public Demo20_5(){JProgressBar Bar=new JProgressBar();//创建进度条getContentPane().add(Bar,BorderLayout.NORTH);//将进度条设置在窗体最北面JButton b=new JButton("停止");getContentPane().add(b,BorderLayout.SOUTH);//将进度条设置在窗体最南面Bar.setStringPainted(true);//设置进度条显示数字字符Thread t=new Thread(new Runnable() {int c=0;public void run() {while(true) {Bar.setValue(++c);//设置进度条当前值try {Thread.sleep(100);//让A线程休眠100毫秒}catch(InterruptedException e) {//捕捉InterruptedException异常System.out.println("当前线程程序被中断");break;}}}});b.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {t.interrupt();//中断线程}});t.start();//启动线程}public static void init(JFrame frame,int w,int h) {frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.setSize(w, h);frame.setVisible(true);}public static void main(String[] args) {init(new Demo20_5(),100,100);}}

结果

 

线程的礼让

Thread 类中提供了一种礼让方法,使用 yield()方法表示,它只是给当前正处于运行状态的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。yicld()方法使具有同样优先级的线程有进入可执行状态的机会,在当前线程放弃执行权时会再度回到就绪状态。

线程的优先级

每个线程都具有各自的优先级,线程的优先级可以表明在程序中该线程的重要性。

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

例题20.6
package lx;public class Demo20_6 implements Runnable{String name;public 	Demo20_6(String name) {this.name=name;}public void run() {String tmp="";for(int i=0;i<50000;i++) {//完成5万次字符串拼接tmp+=i;}System.out.println(name+"线程完成任务");}public static void main(String[] args) {Thread a=new Thread(new Demo20_6("A"));//A线程优先级最小a.setPriority(1);Thread b=new Thread(new Demo20_6("B"));b.setPriority(3);Thread c=new Thread(new Demo20_6("C"));c.setPriority(7);Thread d=new Thread(new Demo20_6("D"));//D线程优先级最大d.setPriority(10);a.start();b.start();c.start();d.start();//线程的执行顺序由CPU决定,所有可能不一定按优先级排序}}

结果

 

由于线程的执行顺序是由 CPU 决定的,即使线程设定了优先级也是作为 CPU 的参考数据,所以真实的运行结果可能并不一定按照优先级排序

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

 线程安全
实际开发中,使用多线程程序的情况很多,以火车站售票系统为例,在代码中判断当前票数是否大于 0,如果大于 0 则执行将该票出售给乘客的功能,但当两个线程同时访问这段代码时(假如这时只剩下一张票),第一个线程将票售出,与此同时第二个线程也已经执行完成判断是否有票的操作,并得出票数大于 0 的结论,于是它也执行售出操作,这样就会产生负数。

所以,在编写多线程程序时,应该考虑到线程安全问题。实质上线程安全问题来源于两个线程同时存取单一对象的数据。

实例
 

package lx;public class Demo20_6_1 implements Runnable {int n=10;//设置当前总票数public void run() {while(true) {//设置无限循环if(n>0) {//判断当前票数是否大于 0try {Thread.sleep(100);		//使当前线程休眠 100毫秒	}catch(InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"————票数"+n--);//票数减1}	}}public static void main(String[] args) {Demo20_6_1 t=new Demo20_6_1();//实例化类对象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();}}

结果 

 

线程同步机制
该如何解决资源共享的问题呢? 所有解决多线程资源冲突问题的方法基本上都是采用给定时间只允许一个线程访问共享资源的方法,这时就需要给共享资源上一道锁。

同步块
Java 中提供了同步机制,可以有效地防止资源冲突。同步机制使用 synchronized 关键字,使用该关键字包含的代码块称为同步块,也称为临界区,语法如下:

synchronized (Object){

}

 通常将共享资源的操作放置在 synchronized 定义的区域内,这样当其他线程获取到这个锁时,就必须等待锁被释放后才可以进入该区域。

例题20.7

package lx;public class Demo20_6_1 implements Runnable {int n=10;//设置当前总票数public void run() {while(true) {//设置无限循环synchronized (this) {if(n>0) {//判断当前票数是否大于 0try {Thread.sleep(100);		//使当前线程休眠 100毫秒	}catch(InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"————票数"+n--);//票数减1}	}}}public static void main(String[] args) {Demo20_6_1 t=new Demo20_6_1();//实例化类对象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();}}

结果 

 

从这个结果可以看出,打印到最后票数没有出现负数,这是因为将共享资源放置在了同步块中,不管程序如何运行都不会出现负数。

同步方法

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

synchronized void f(){

}

 当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行。必须将每个能访问共享资源的方法修饰为 synchronized,否则就会出错。

修改20.7的代码如下:
package lx;public class Demo20_6_1 implements Runnable {int n=10;//设置当前总票数public  synchronized void du() {if(n>0) {//判断当前票数是否大于 0try {Thread.sleep(100);		//使当前线程休眠 100毫秒	}catch(InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"————票数"+n--);//票数减1}	}public void run() {while(true) {//设置无限循环du();}}public static void main(String[] args) {Demo20_6_1 t=new Demo20_6_1();//实例化类对象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();}}

结果

 

执行结果一样 

 

 

 

 

 

 

 

 

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

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

相关文章

10.索引

一.索引简介 索引用于快速找出在某个列中有一特定值的行。 不使用索引&#xff0c;MySQL必须从第1条记录开始读完整个表&#xff0c;直到找出相关的行。表越大&#xff0c;查询数据所花费的时间越多。 如果表中查询的列有一个索引&#xff0c;MySQL能快速到达某个位置去搜寻…

鞋厂ERP怎么样?工厂要如何选项契合的ERP

鞋帽这类商品是我们的生活必需品&#xff0c;存在款式多、尺码多、用料复杂、营销渠道多、销售策略和价格策略灵活等情况&#xff0c;伴随电商等行业的发展&#xff0c;鞋帽行业的管理模式也在发生变化。 鞋厂规模的不同&#xff0c;遇到的管理问题各异&#xff0c;而如何解决…

[英语学习][3][Word Power Made Easy]的精读与翻译优化

[序言] 这次翻译校验, 难度有点大, 原版中英翻译已出现了严重地偏差. 昨晚11点开始阅读如下段落, 花费了1个小时也没有理解原作者的核心表达, 索性睡觉了. 今早学习完朗文单词之后, 9点半开始继续揣摩. 竟然弄到了中午11点30, 终于明白原作者要表达的意思了. 废话不多说&#x…

「Verilog学习笔记」非整数倍数据位宽转换8to12

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 要实现8bit数据至12bit数据的位宽转换&#xff0c;必须要用寄存器将先到达的数据进行缓存。8bit数据至12bit数据&#xff0c;相当于1.5个输入数据拼接成一个输出数据&#…

Find My电容笔|苹果Find My技术与电容笔结合,智能防丢,全球定位

随着平板电脑的流行&#xff0c;有不少厂商都投入到了电容笔的开发当中&#xff0c;现在的电容笔不仅在精度上有了提高&#xff0c;甚至在笔触和压感上的研究都有进步。电容笔是利用导体材料制作的具有导电特性、用来触控电容式屏幕完成人机对话操作用的笔&#xff0c;电容笔通…

Mysql 不执行索引问题与优化

难以查找的隐藏问题 及 解决办法&#xff1a; 问题总结&#xff1a;

计算机网络:网络层

0 本节主要内容 问题描述 解决思路 1 问题描述 两大问题&#xff08;重点&#xff0c;也是难点&#xff09;&#xff1a; 地址管理&#xff1b;路由选择。 1.1 子问题1&#xff1a;地址管理 网络上的这些主机和节点都需要使用一种规则来区分&#xff0c;就相当于是一种身…

ESP32-Web-Server编程-CSS 基础 2

ESP32-Web-Server编程-CSS 基础 2 概述 如上节所述&#xff0c;可以使用外部 CSS 文件来修饰指定的 HTML 文件。 外部引用 - 使用外部 CSS 文件。 当样式需要被应用到很多页面的时候&#xff0c;外部样式表将是理想的选择。使用外部样式表&#xff0c;就可以通过更改一个文件…

JAVA进阶之路JVM-2:类加载机制,类的生命周期,类加载过程,类加载时机,类加载器,双亲委派模型,对象创建过程

JVM类加载机制 类加载 ​ 在JVM虚拟机实现规范中&#xff0c;通过ClassLoader类加载把*.class字节码文件&#xff08;文件流&#xff09;加载到内存&#xff0c;并对字节码文件内容进行验证&#xff0c;准备&#xff0c;解析和初始化&#xff0c;最终形成可以被虚拟机直接使用…

猫头虎分享已解决Bug || Environment for Full Errors and Additional Helpful Warnings

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页: &#x1f405;&#x1f43e;猫头虎的博客&#x1f390;《面试题大全专栏》 &#x1f995; 文章图文并茂&#x1f996…

【Linux】初识重定向(输入输出)

一切皆文件 这是Linux的设计理念&#xff0c;因为这个理念的存在我们可以使用统一的方法对待不同的东西&#xff0c;&#xff0c;这也是为什么嵌入式之类的会需要Linux&#xff0c;因为用LInux来操纵硬件真的很方便 另外我们下文也会都基于这个理念来命名&#xff0c; 比如&am…

异步爬虫提速实践-在Scrapy中使用Aiohttp/Trio

在构建爬虫系统时&#xff0c;提高爬虫速度是一个关键问题。而使用异步爬虫技术可以显著提升爬取效率。在本文中&#xff0c;我将与大家分享如何在Scrapy中利用Aiohttp或Trio库实现异步爬取&#xff0c;以加快爬虫的速度。让我们开始吧&#xff01; 1. 安装所需的库 首先&…