【java】第十六章、多线程

本文详细介绍了Java中的多线程,包括线程的生命周期、Thread类、Runnable接口、线程同步(同步方法、同步代码块)以及线程的休眠、加入、中断和优先级。通过实例展示了线程的并发执行和资源管理,强调了线程同步的重要性。
摘要由CSDN通过智能技术生成

线程的简介

在这里插入图片描述
在这里插入图片描述
一个进程可以同时拥有多个线程
系统把资源交给进程,然后让线程执行所有的逻辑
在这里插入图片描述
不同的进程甚至可以访问同一块内存区域(用眼睛看、用耳朵听、用嘴笑 这些动作发生在同一个人身上)

Thread类

在这里插入图片描述
使用start()方法才能实现线程的并发效果
run()方法中执行的代码就是我们线程要执行的代码
数字和字母同时输出,这就是线程的一个并发效果
线程的执行顺序和它的执行时间并不是由代码来控制,而是由CPU来控制的(线程a和线程b虽然创建时间有先后,但是它们是同时执行的)

package basic_thread;

public class Demo {
	public static void main(String[] args) {
		Thread a = new ThreadA();
		a.start();
		Thread b = new ThreadB();
		b.start();
	}
}

class ThreadA extends Thread{               //线程A
	@Override
	public void run() {
		for(int i = 0; i < 100; i++) {
			System.out.println(i);
			try {
				Thread.sleep(1000);         //休眠1秒(停顿1秒)
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

class ThreadB extends Thread{               //线程B
	@Override
	public void run() {
		for(char i = 'a'; i < 'z'; i++) {
			System.out.println(i);
			try {
				Thread.sleep(1000);         //休眠1秒(停顿1秒)
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

Runnable接口

与Thread同在java.lang包下,还有一个可以代表并发线程的接口Runnable,它只有一个抽象方法:run()
在这里插入图片描述
存在隐患:这个线程类只能继承Thread类,无法继承其他类,这种设计模式是不符合实际应用的,所以java提供了并发接口Runnable
线程运行的逻辑就是Runnable中重写的run()方法的逻辑
在这里插入图片描述
用Runnable接口实现线程的效果(优点:任何一个类都可以实现线程的功能):

package runnable_interface;

import java.awt.Container;
import java.net.URL;
import javax.swing.*;

public class Swing_and_Thread extends JFrame implements Runnable{
	private JLabel jl;                                   //声明JLabel对象
	private Container container = getContentPane() ;  //获取窗体容器
	
	public Swing_and_Thread() {
		jl = new JLabel();
		setBounds(400, 200, 500, 350);     //绝对定位窗体的大小与位置
		container.setLayout(null);         //窗体不使用任何布局管理器
		try {
			URL url = Swing_and_Thread.class.getResource("../imags/1.gif");
			System.out.println(url);
			Icon icon = new ImageIcon(url);   //实例化一个Icon
			jl.setIcon(icon);                 //将图标放置在标签中
		} catch (NullPointerException e) {
			System.out.println("图片不存在,请将1.gif拷贝到当前目录下!");
			return;
		}
		
		//设置图片在标签的最左方
		jl.setHorizontalAlignment(SwingConstants.LEFT);
		jl.setBounds(10, 10, 400, 250);      //设置标签的位置和大小
		jl.setOpaque(true);
		
		container.add(jl);                  //将标签添加到容器中
		setVisible(true);                   //使窗体可见
		//设置窗体的关闭方式
		setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
	}
	
	public static void main(String[] args) {
		//实例化一个Swing_and_Thread对象
		Swing_and_Thread frame = new Swing_and_Thread();
		Thread t = new Thread(frame);
		t.start();
	}

	@Override
	public void run() {
		int count = 10;
		while(true) {
			jl.setBounds(count, 10, 400, 250);
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			count += 5;
			if(count >= 200) {
				count = 10;
			}
		}
		
	}
}

线程的生命周期

在这里插入图片描述
就绪状态(可执行状态):等待CPU为线程分配时间片
当获得系统资源之后,也就是当CPU来执行它的时候,线程就进入运行状态。一旦进入运行状态,它就会在就绪与运行状态之间来回转换
也可能进入暂停状态:与就绪状态不同,暂停状态下的线程是持有系统资源的,只不过是没有做任何操作
暂停——就绪:此时需要检查CPU是否有剩余的资源来执行这个线程

线程的休眠

在这里插入图片描述
休眠时不会释放资源,休眠结束之后线程会恢复执行

线程的加入

在这里插入图片描述
理论上这两个线程互相独立,是并发执行的,所以这两个线程的执行进度也是分开的
若线程B在线程A的run()方法中调用了join()方法,那么线程B就会加入到线程A中,线程A会中断当前的执行进度,优先执行线程B

线程的中断

由于线程突然中断可能导致死锁的问题,所以调用这个方法之后肯定会抛出一个InterruptedException异常,我们在代码里则必须捕捉这个异常,这样可以让我们在线程中断之后做一些资源释放的操作,例如:断开数据库、关闭数据流
在这里插入图片描述

package interrupt_test;

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JProgressBar;

public class InterruptedSwing extends JFrame {
	final JProgressBar progressBar = new JProgressBar(); // 创建进度条
	Thread thread;

	public InterruptedSwing() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关闭窗体后停止程序
		setSize(200, 100); // 设定窗体宽高
		setVisible(true); // 窗体可见
		// 将进度条放在窗体合适的位置
		getContentPane().add(progressBar, BorderLayout.NORTH);
		progressBar.setStringPainted(true); // 设置进度条显示数字字符

		// 使用匿名内部类形式 创建线程对象
		thread = new Thread(new Runnable() {
			@Override
			public void run() { // 重写run()方法
				for(int i = 0; i <= 100; i++) {
					progressBar.setValue(i);    //设置进度条的当前值
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		});
		thread.start(); // 启动线程
	}

	public static void main(String[] args) {
		new InterruptedSwing();
	}

}

这里的this是指匿名内部类

package interrupt_test;

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JProgressBar;

public class InterruptedSwing extends JFrame {
	private static final long serialVersionUID = 1L;
	final JProgressBar progressBar = new JProgressBar(); // 创建进度条
	Thread thread;

	public InterruptedSwing() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关闭窗体后停止程序
		setSize(200, 100); // 设定窗体宽高
		setVisible(true); // 窗体可见
		// 将进度条放在窗体合适的位置
		getContentPane().add(progressBar, BorderLayout.NORTH);
		progressBar.setStringPainted(true); // 设置进度条显示数字字符

		// 使用匿名内部类形式 创建线程对象
		thread = new Thread() {
			@Override
			public void run() { // 重写run()方法
				try {
					for(int i = 0; i <= 100; i++) {
						progressBar.setValue(i);    //设置进度条的当前值
						if(i == 50) {
							this.interrupt();     //这里的this是指匿名内部类
						}
						Thread.sleep(100);
					}
				} catch (InterruptedException e) {
					System.out.println("当前线程被中断");
				}
			}
		};
		thread.start(); // 启动线程
	}

	public static void main(String[] args) {
		new InterruptedSwing();
	}

}

在这里插入图片描述

线程的优先级

在这里插入图片描述
在这里插入图片描述
设置线程优先级后,但是效果并不明显(它并不是按照理论的执行顺序来的,具体是看CPU如何执行)

package priority;

public class PriorityTest {
	public static void main(String[] args) {
		for(int i = 0; i < 10; i++) {
			MyThread t1 = new MyThread("加", "+");
			t1.setPriority(Thread.MIN_PRIORITY);      //设定线程优先级
			MyThread t2 = new MyThread("减", "-");
			t2.setPriority(3);
			MyThread t3 = new MyThread("乘", "×");
			t3.setPriority(8);
			MyThread t4 = new MyThread("除", "÷");
			t4.setPriority(Thread.MAX_PRIORITY);
			t1.start();
			t2.start();
			t3.start();
			t4.start();
		}
	}
}

class MyThread extends Thread{
	String name;
	String output;
	
	public MyThread(String name, String output) {
		this.name = name;
		this.output = output;
	}
	
	@Override
	public void run() {
		System.out.println(name + ": " + output);
	}
}

在这里插入图片描述

线程的同步

同一进程下,不同的线程是共享同样的资源的,若两个线程都想过独木桥,必然会发生资源的抢用。
这样可能就会发生脏数据、死锁等问题
在这里插入图片描述
互相礼让解决资源抢占的问题:每次桥上只留一个人,其他的人都让开,这样一个一个过桥,问题就解决了
在这里插入图片描述
在这里插入图片描述
synchronized关键字在java中有两种使用场景:

同步方法

在同一个时间内,只允许被一个线程所调用,这样的话,这个方法就是线程安全的了

同步代码块

代码块中有一个参数Object,它可以是任意对象,每个对象都存在一个可以记录线程加锁的标志位
一个线程运行到同步代码块时,首先会来检查这个对象它的标志是否锁住。若锁住,表明这个同步代码块已经有其他线程在执行了,这时候这个线程会处于就绪状态,等其他线程释放这一块的资源
这样可保证同步代码块在同一时间之内只有一个线程在调用
在这里插入图片描述
在这里插入图片描述

线程不同步

package synchronized_test;

public class Demo implements Runnable{
	int num = 10;           //票池
	
	public static void main(String[] args) {
		Demo d = new Demo();
		Thread t1 = new Thread(d, "线程一");
		Thread t2 = new Thread(d, "线程二");
		Thread t3 = new Thread(d, "线程三");
		Thread t4 = new Thread(d, "线程四");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

	@Override
	public void run() {
		while(true) {
			if(num > 0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("票数:" + num--);
			}
		}
	}
}

在这里插入图片描述

线程同步

同步代码块中,所有的代码在同一时间 只能被同一个线程所运行

package synchronized_test;

public class Demo implements Runnable{
	int num = 10;           //票池
	
	public static void main(String[] args) {
		Demo d = new Demo();
		Thread t1 = new Thread(d, "线程一");
		Thread t2 = new Thread(d, "线程二");
		Thread t3 = new Thread(d, "线程三");
		Thread t4 = new Thread(d, "线程四");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

	@Override
	public void run() {
		while(true) {
			synchronized (this) {    //同步代码块,加锁的对象是这个类本身
				if(num > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("票数:" + num--);
				}
			}
		}
	}
}

在这里插入图片描述

package synchronized_test;

public class Demo implements Runnable{
	int num = 10;           //票池
	
	public static void main(String[] args) {
		Demo d = new Demo();
		Thread t1 = new Thread(d, "线程一");
		Thread t2 = new Thread(d, "线程二");
		Thread t3 = new Thread(d, "线程三");
		Thread t4 = new Thread(d, "线程四");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

	private synchronized void sell() {    //线程同步的方法
		if(num > 0) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("票数:" + num--);
		}
	}
	
	@Override
	public void run() {
		while(true) {
			sell();
		}
	}
}

在这里插入图片描述
当我们看成多线程并发程序的时候,把这些代码写成同步方法,用同步方法控制线程并发产生的脏数据

线程的暂停与恢复

在这里插入图片描述
在同步代码块,执行wait()方法;并且在前面加上线程同步的方法

package suspend_and_resume;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

import javax.swing.*;

public class ThreadSuspendFrame extends JFrame{
	private JLabel label;        //显示数字的标签
	private JButton btn;         //创建按钮
	String phoneNums[] = {"13610780204", "13847928544", "18457839454"};
	private Container c = getContentPane();  //获取窗体容器
	MyThread t;
	
	public ThreadSuspendFrame(){
		setTitle("手机号码抽奖");      //窗口标题
		setDefaultCloseOperation(EXIT_ON_CLOSE); //窗口关闭规则:窗口关闭则停止程序
		setBounds(200, 200, 300, 150);    //设置窗口坐标和大小
		
		label = new JLabel("0");          //实例化标签,初始值为0
		label.setHorizontalAlignment(SwingConstants.CENTER); //标签文字居中
		label.setFont(new Font("宋体", Font.PLAIN, 42));
		c.add(label, BorderLayout.CENTER);     //标签放入窗口容器
		
		btn = new JButton("暂停");           //创建暂停按钮
		c.add(btn, BorderLayout.SOUTH);    //按钮放入窗口容器
		
		t = new MyThread();
		t.start();
		
		btn.addActionListener(new ActionListener() {    //按钮添加事件监听
			@Override
			public void actionPerformed(ActionEvent e) {
				String btnText = btn.getText();    //获取按钮文本
				if(btnText.equals("暂停")) {
					btn.setText("继续");
					t.toSuspend();             //使线程暂停
				}
				else {
					t.toResume();             //使线程恢复
					btn.setText("暂停");
				}
			}
		});
		setVisible(true);         //设置窗体可见
	}

	class MyThread extends Thread{
		private boolean suspend = false;    //暂停标志
		
		public synchronized void toSuspend() {
			suspend = true;
		}
		
		public synchronized void toResume() {
			suspend = false;
			notify();         //使当前等待的线程继续执行(唤醒线程)
		}
		
		@Override
		public void run() {
			while(true) {
				synchronized (this) {
					while(suspend) {
						try {
							wait();        //使线程进入等待状态
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
				
				int randomIndex = new Random().nextInt(phoneNums.length);//获取数组随机索引
				String phoneNum = phoneNums[randomIndex];    //获得随机号码
				label.setText(phoneNum);           //修改标签的值
			}
		}
	}
	
	public static void main(String[] args) {
		new ThreadSuspendFrame();
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值