线程的简介
一个进程可以同时拥有多个线程
系统把资源交给进程,然后让线程执行所有的逻辑
不同的进程甚至可以访问同一块内存区域(用眼睛看、用耳朵听、用嘴笑 这些动作发生在同一个人身上)
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();
}
}