第二十章 多线程

Windows操作系统是多任务操作系统,它以进程为单位。一个进程是一个包含有自身地址的程序,每个独立执行的程序都称为进程。也就是说每个正在执行的程序都是一个进程。系统可以分配给每一个进程有一段有限的使用CPU的时间(也可以称为CPU时间片),CPU在这段时间中执行某个进程,然后下一个时间片又跳至另一个进程中去执行。由于CPU转换比较快,所以使得每个进程好像是同时执行一样。

下图表明了Windows操作系统的执行模式

创建线程

继承Thread类

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

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

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

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

java 复制代码
public class ThreadTest extends Threadf {

}

完成线程真正功能的代码放在类的run0方法中,当一个类继承Thread类后,就可以在该类中覆盖run0方法,将实现该线程功能的代码写入runO方法中,然后调用 Thread类中的start0方法执行线程也就是调用run0方法。

Thread 对象需要一个任务来执行,任务是指线程在启动时执行的工作,该工作的功能代码被写在run0方法中。run0方法必须使用以下语法格式:

java 复制代码
public void run() {

}

当执行一个线程程序时,就自动产生一个线程,主方法正是在这个线程上运行的。当不再启动其他线程时,该程序就为单线程程序,如本章以前的程序都是单线程程序。主方法线程启动由Java 虚拟机负责,程序员负责启动自己的线程。代码如下:

java 复制代码
public static void main(String[] args){

        new ThreadTest().start();

}

例题20.1

java 复制代码
 
public class ThreadTest extends Thread{
	public void run() {
		for (int i = 1;i<=10;i++) {
		System.out.print(i+"");
	}
}
	public static void main(String[] args) {
		
		ThreadTest t= new ThreadTest();
		t.start();
	}
 
}//例题20.1

运行结果如下:

实现Runnable 接口

实现Runnable接口的语法如下:

java 复制代码
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 接口启动新的线程的步骤如下:

建立Runnable对象

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

调用startO方法启动线程

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

图20.2表明了实现Runnable接口创建线程的流程。

例题20.2

java 复制代码
import java.awt.Container;
 
import javax.swing.*;
 
public class SwingAndThread extends JFrame{
	int count = 0;								//图标横坐标
	
	public SwingAndThread() {
		setBounds(300,200,250,100);				//绝对定位窗体大小与位置
		Container container = getContentPane();	//主容器
		container.setLayout(null);				//使窗体不使用任何布局管理器
		
		Icon icon = new ImageIcon("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.2

线程的生命周期

下图描述了线程生命周期中的各种状态

虽然多线程看起来像同时执行,但事实上在同一时间点上只有一个线程被执行,只是线程之间切换较快,所以才会使人产生线程是同时进行的假象。在Windows操作系统中,系统会为每个线程分配一小段CPU时间片,一旦 CPU时间片结束就会将当前线程换为下一个线程,即使该线程没有结束。要使线程处于就绪状态,有以下几种方法:

调用sleep()方法

调用wait()方法。

等待输入/输出完成。

当线程处于就绪状态后,可以用以下几种方法使线程再次进入运行状态

线程的休眠时间结束

输入\输出结束

操作线程的方法

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

线程的休眠

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

java 复制代码
try{

        Thread.sleep(2000);

}catch(InterruptedException e){

        e.printStackTrace(();

}

例题20.3

java 复制代码
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;												//定义初始坐标
			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);
	}
}//例题20.3

运行结果如下:

线程的加入

当某一个线程使用join()方法加入另外一个线程时,另一个线程会等待该线程执行完毕后再继续执行

例题20.4

java 复制代码
import java.awt.BorderLayout;
 
import javax.swing.*;
 
public class JionTest extends JFrame{
	private Thread threadA;									//定义两个线程
	private Thread threadB;
	private JProgressBar progressBar = new JProgressBar();	//定义两个进度条组件
	private JProgressBar progressBar2 = new JProgressBar();
	public static void main(String[] args) {
		JionTest test = new JionTest();
		test.setVisible(true);
 
	}
	private JionTest() {
		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();									//启动线程A
		threadB = new Thread(new Runnable() {
			int count = 0;
			public void run() {
				while (true) {
					progressBar2.setValue(++count);			//设置进度条的当前值
					try {
						Thread.sleep(100);					//使线程B休眠100毫秒
						threadB.join();
					}catch(InterruptedException e) {
						e.printStackTrace();
					}
					if(count==100)							//当count变量增长为100时
						break;								//跳出循环
				}
			}
		});
		threadB.start();									//启动线程B
	}
}//例题20.4

运行结果如下:

线程的中断

以往有的时候会使用stop()方法停止线程,但当前版本的JDK早己废除了stop()方法,不建议使用stop()方法来停止一个线程的运行。现在提倡在run()方法中使用无限循环的形式,然后使用一个布尔型标记控制循环的停止。

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

下面的实例演示了某个线程使用interrupted()方法,同时程序抛出了InterruptedException异常,在异常处理时结束了while 循环。在项目中,经常在这里执行关闭数据库连接和关闭Socket连接等操作。

例题20.5

java 复制代码
import java.awt.BorderLayout;
import java.awt.event.*;
 
import javax.swing.*;
 
public class InterruptedSwing extends JFrame{
	private 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);										//使线程A休眠100毫秒				
				}catch(InterruptedException e) {							//捕捉InterruptedException异常
					System.out.println("当前线程序被中断");
					break;
				}
			}
		}
	});
	
	button.addActionListener(new ActionListener() {
 
		@Override
		public 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);
	}
}//例题20.5

运行结果如下:

线程的礼让

Thread 类中提供了一种礼让方法,使用yield()方法表示,它只是给当前正处于运行状态的线程个提醒,告知它可以将资源礼让给其他线程,但这仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。

yield()方法使具有同样优先级的线程有进入可执行状态的机会,在当前线程放弃执行权时会再度回到就绪状体。对于支持多任务的操作系统来说,不需要调用yield()方法,因为操作系统会为线程自动分配CPU时间片来执行。

线程的优先级

每个线程都具有各自的优先级,线程的优先级可以表明在程序中该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态。但这并不意味着低优先级的线程得不到运行,而只是它运行的概率比较小,如垃圾回收线程的优先级就较低。

Thread类中包含的成员变量代表了线程的某些优先级,如Thread.MIN_PRIORITY(常数1)、Thread.MAXPRIORITY(常数10)、Thread.NORM PRIORITY(常数5)。其中,每个线程的优先级都在 Thread.MIN_PRIORITY~Thread.MAX_PRIORITY,在默认情况下其优先级都是Thread.NORM PRIORITY。每个新产生的线程都继承了父线程的优先级。

在多任务操作系统中,每个线程都会得到一小段CPU时间片运行,在时间结束时,将轮换另一个线程进入运行状态,这时系统会选择与当前线程优先级相同的线程予以运行。系统始终选择就绪状态下优先级较高的线程进入运行状态。处于各个优先级状态下的线程的运行顺序如图20.8所示。

在图20.8中,优先级为5的线程A首先得到CPU时间片;当该时间结束后,轮换到与线程A相同优先级的线程B;当线程 B的运行时间结束后,会继续轮换到线程A,直到线程A与线程B都执行完毕,才会轮换到线程C;当线程C结束后,才会轮换到线程D。

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

例题20.6

java 复制代码
 
public class PriorityTest implements Runnable{
	String name;
	
	public PriorityTest(String name) {
		this.name = name;
	}
 
	@Override
	public 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 PriorityTest("A"));
		a.setPriority(1);									//A线程优先级最小
		Thread b = new Thread(new PriorityTest("B"));
		b.setPriority(3);
		Thread c = new Thread(new PriorityTest("C"));
		c.setPriority(7);
		Thread d = new Thread(new PriorityTest("D"));		
		d.setPriority(10);									//D线程优先级最大
		a.start();
		b.start();
		c.start();
		d.start();
	}
 
}//例题20.6

运行结果如下:

线程同步

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

线程安全

例如,在项目中创建ThreadSafeTest类,该类实现了Rummable接口,在未考虑到线程安全问题的基础上,模拟火车站售票系统的功能的代码如下:

java 复制代码
 
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--);	//票数减一
		}
	}
}
	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();
	}
	}

运行结果如下:

线程同步机制

1.同步块

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

java 复制代码
synchronized(Object) {

}

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

例题20.7

java 复制代码
 
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();
					}
					System.out.println(Thread.currentThread().getName()+"----票数"+num--);//票数减一
				}
			}
		}
}
 
	public static void main(String[] args) {		
		SynchronizedTest t = new SynchronizedTest();	//实例化类对象
		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();
	}
}

运行结果如下:

2.同步方法

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

synchronized void f(){}

当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被

执行。必须将每个能访问共享资源的方法修饰为synchronized,否则就会出错。

修改例题20.7的代码,将共享资源操作放置在用同一个同步方法中

java 复制代码
 int num = 10;		//设置当前总票数
	public synchronized void doit() {		//定义同步方法
		if(num>0){
			try{
				Thread.sleep(10);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"---票数"+num--);
		}
	}
				public void run(){
 
				while(true){
 
				doit();
				}
				}//再run()方法中调用该同步方法	
相关推荐
爱上语文2 分钟前
Springboot三层架构
java·开发语言·spring boot·spring·架构
loveLifeLoveCoding18 分钟前
Java List sort() 排序
java·开发语言
草履虫·24 分钟前
【Java集合】LinkedList
java
AngeliaXue26 分钟前
Java集合(List篇)
java·开发语言·list·集合
世俗ˊ27 分钟前
Java中ArrayList和LinkedList的比较
java·开发语言
zhouyiddd32 分钟前
Maven Helper 插件
java·maven·intellij idea
攸攸太上40 分钟前
Docker学习
java·网络·学习·docker·容器
Milo_K1 小时前
项目文件配置
java·开发语言
程序员大金1 小时前
基于SpringBoot+Vue+MySQL的养老院管理系统
java·vue.js·spring boot·vscode·后端·mysql·vim
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS网上购物商城(JAVA毕业设计)
java·vue.js·spring boot·后端·开源