多线程
线程简介
-
多个活动同时进行在Java中被称为并发,而将并发完成的每一件事情称为线程。
-
Java语言提供了并发机制,程序员可以在程序中执行多个线程,每一个线程完成一个功能,并与其他线程并发执行,这种机制被成为多线程。
-
Java中的多线程在每个操作系统中的运行方式存在差异。
-
如果需要一个进程同时完成多段代码的操作,就需要产生多线程
注: 1.进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。 进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。 2.线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位 2、一个线程只能属于一个进程,但是一个进程可以拥有多个线程。 多线程处理就是允许一个进程中在同一时刻执行多个任务。
来源于:https://baijiahao.baidu.com/s?id=1611925141861592999&wfr=spider&for=pc
实现线程的两种方式
1.继承java.lang.Thread类
2.实现java.lang.Runnable接口
继承Thread类
Thread类中实例化的对象代表线程,程序员启动一个新县城需要建立Thread实例。语法如下:
public class ThreadTest extends Thread{
}
- 当一个类继承Thread类后,就可以在该类中覆盖run()方法,将实现该线程功能的代码写入run() 方法中,然后同时调用Thread类中的 start() 方法执行线程,也就是调用run()方法
- Thread对象需要一个任务来执行,任务是指线程在启动时执行的工作。
- 如果start()方法调用一个已经启动的线程,系统将抛出IllegalThreadStateException异常
- 主方法线程启动由Java虚拟机 负责,程序猿负责启动自己的线程。
例:
public class ThreadTest extends Thread { // 指定类继承Thread类
private int count = 10;
public void run() { // 重写run()方法
while (true) {
System.out.print(count+" "); // 打印count变量
if (--count == 0) { // 使count变量自减,当自减为0时,退出循环
return;
}
}
}
public static void main(String[] args) {
new ThreadTest().start();
}
}
- 通常在run()方法中使用无限循环的形式,使得线程一直运行下去,所以要指定一个跳出循环的条件。
- 如果不调用start()方法,线程永远不会启动,在主方法没有调用start()方法之前,Thread对象只是一个实例 而不是一个真正的 线程。
实现Runnable接口
- 如果需要继承其他类且还要是当前类实现多线程,那么可以通过Runnable接口来实现。
- 实现Runnable接口的程序会创建一个Thread对象,并将Runnable对象与Thread对象相关联。Thread类有一下两个构造方法:
-
public Thread(Runnable target)
-
public Thread(Runnable target,String name)
使用以上构造方法就可以将Runnable实例与Thread实例相关联 -
使用Runnable接口启动新的线程步骤:
-
(1)建立Runnable对象。
(2)使用参数为Runnable对象的构造方法创建Tread实例
(3)调用start()方法启动线程。
例:
t = new Thread(new Runnable(){ //定义匿名内部类,该类实现Runnable接口
public void run(){ //重写run()方法
for(int i = 0;i<10;i++){
System.out.println(i);
try{
Thread.sleep(1000); //使线程休眠1000毫秒
}catch(Exception e){
e.printStackTrace();
}
}
}
});
t.start();
本实例中,在创建该实例的同时,需要Runnable对象作为Tread类构造方法的参数,然后使用内部类形式实现run()方法。
在这里用是内部类的方法,没有用implements关键字,可以尝试下。
线程的生命周期
线程具有生命周期,其中包含7种状态,分别为出生状态 、就绪状态 、运行状态、等待状态、休眠状态、阻塞状态和死亡状态。
线程处于就绪状态有一下几种方法:
- 调用sleep()方法。
- 调用wait()方法
- 等待输入/输出完成。
当线程处于就绪状态后,可以用一下几种方法使线程再次进入运行状态。 - 线程调用notify()方法。
- 线程调用notifyAll()方法。
- 线程调用interrupt()方法。
- 线程的休眠时间结束。
- 输入/输出结束
操作线程的方法
操作线程有很多方法,这些方法可以使线程从某一种状态过渡到另一种状态。
线程的休眠
sleep() 方法需要一个参数用于指定该线程休眠的时间,该时间以毫秒 为单位,通常是在run() 方法内的循环体中被使用。语法如下:
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
- 虽然使用了sleep()方法的线程在一段时间内会醒来,但是并不能保证它醒来后进入运行状态,只能保证它进入就绪状态。
线程的加入
如果当前某程序为多线程程序,假如存在一个线程A,现在需要插入线程B,并要求线程B先执行完毕,然后再继续执行线程A,此时可以使用Thread类中的join() 方法来完成。
当某个线程使用join()方法加入到另外一个线程时,另一个线程会等待该线程执行完毕后再继续执行。
例:
import java.awt.*;
import javax.swing.*;
public class JoinTest extends JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
private Thread threadA; // 定义两个线程
private Thread threadB;
final JProgressBar progressBar = new JProgressBar(); // 定义两个进度条组件
final JProgressBar progressBar2 = new JProgressBar();
int count = 0;
public static void main(String[] args) {
init(new JoinTest(), 500, 500);
}
public JoinTest() {
super();
// 将进度条设置在窗体最北面
getContentPane().add(progressBar, BorderLayout.NORTH);
// 将进度条设置在窗体最南面
getContentPane().add(progressBar2, BorderLayout.SOUTH);
progressBar.setStringPainted(true); // 设置进度条显示数字字符
progressBar2.setStringPainted(true);
// 使用匿名内部类形式初始化Thread实例子
threadA = new Thread(new Runnable() {
int count = 0;
public void run() { // 重写run()方法
while (true) {
progressBar.setValue(++count); // 设置进度条的当前值
try {
Thread.sleep(100); // 使线程A休眠100毫秒
threadB.join(); // 使线程B调用join()方法
} catch (Exception 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毫秒
} catch (Exception e) {
e.printStackTrace();
}
if (count == 100) // 当count变量增长为100时
break; // 跳出循环
}
}
});
threadB.start(); // 启动线程B
}
// 设置窗体各种属性方法
public static void init(JFrame frame, int width, int height) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setVisible(true);
}
}
- 注意上面代码中,加入线程B的线程B也需要start()方法执行,尝试了注释掉线程B.start()方法,结果进度条只有A动,然后只注释B.join()方法,发现两个内存条是同时动的,所以说这里的线程应该没有先后顺序,是同时运行的。而join()的作用,应该就是在这之中先让B优先运行完,再让A运行。
- 用debug在A.start()和B.start(),以及两个sleep处断点,试了下顺序,发现运行顺序居然是A.start()-B.start()-A_run_sleep-B_run_sleep-A_run_B.join()-B_run,由此可见,一开始是两个同时run()的,当Arun()中遇到B.join()后,暂停A_run(),继续B_run();
- 而如果没有join(),那么AB两个是轮流run,A一次B一次交替来,这应该就是Windows的多线程了。
线程的中断
- 之前用stop()【现在不用】,现在提倡在run()方法中使用无限循环的形式,然后使用一个布尔型标记控制循环的停止。
- 如果线程是因为是用了sleep()或wait()方法进入了就绪状态,可以使用Thread类中的interrupt() 方法使线程离开run()方法,同时结束线程,但程序会抛出InterruptException异常,用户可以在处理该异常时完成线程的中断业务处理,如终止while循环。
线程的礼让
Thread类中提供了一种礼让方法,使用yield() 方法表示,它只是给当前正处于运行状态的线程一个提醒,告知它可以讲资源礼让给其他线程,但这仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。
yield()方法使具有同样优先级的线程有进入可执行状态的机会,当 当前线程放弃执行权时会再度回到就绪状态。对于支持多任务的操作系统来说,不需要调用yield()方法,因为操作系统会为线程自动分配CPU时间片来执行。
线程的优先级
- 系统根据优先级来决定首先使哪个线程计入运行状态。
- 低优先级运行几率比较小,并不是得不到运行
- Thread类中包含的成员变量代表了线程的某些优先级,如Thread.MIN_PRIORITY(1)、Thread.MAX_PRIORITY(10)、Thread.NORM_PRIORITY(5)。其中每个线程的优先级都在(1~10)之间,在默认情况下,其优先级都是5
- 每个新产生的线程都继承了父线程的优先级。
- 线程的优先级可以使用setPriority() 方法调整,如果使用该方法设置的优先级不再1~10之内,将产生IllegalArgumentException异常。
线程同步
Java提供了线程同步的机制来防止资源访问的冲突。
线程安全
实质上线程安全问题来源于两个线程同时存取单一对象的数据。
线程同步机制
给定时间只允许一个线程访问共享资源
同步块
在Java中提供了同步机制,可以有效地防止资源冲突。同步机制使用synchroniazed关键字。
例:
public class ThreadSafeTest implements Runnable {
int num = 10;
public void run() {
while (true) {
synchronized ("") {
if (num > 0) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("tickets" + --num);
}
}
}
}
public static void main(String[] args) {
ThreadSafeTest t = new ThreadSafeTest();
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();
}
}
同步块也被成为临界区,它使用synchronize关键字简历,语法如下:
synchronized(Object){
}
Object为任意一个对象,每个对象都存在一个标志位,并具有两个值,分别为0和1。一个线程运行到同步块时首先检查该对象的标志位,如果为0状态,表明此同步块中存在其他线程在运行,这时该线程处于就绪状态,直到处于同步块中的线程执行完同步块中的代码为止。这时该对象的标志位被设置为1,该线程才能执行同步块中的代码,并将Object对象的标志位设置为0,防止其他线程执行同步块中的代码。
同步方法
同步方法就是在方法面前修饰synchronized关键字。
当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行。必须将每个能访问共享资源的方法修饰为synchronized,否则就会出错
例:
public synchronized void doit(){ //定义同步方法
if(num>0){
try{
Thread.sleep(100);
}catch(Exception e){
e.printStackTrace();
}
System.out.println("tickets"+ --num);
}
}
public void run(){
while(true){
doit();
}
}
实践与练习
- 定义一个继承Thread类的类,并覆盖run()方法,在run()方法中每隔100ms打印一句话
- 开发一个窗体,在窗体中有两个按钮,一个是“开始”按钮,另一个是“结束”按钮。当用户单击“开始”按钮时,在控制台中持续打印一段话;当用户单击“停止”按钮时,控制台结束打印
- 开放一个窗体,在窗体中设计一个进度条,使进度条每次递增滚动