目录直通车
2、 JDK中用Thread.State枚举表示线程的五种状态
一、 多线程的创建与使用
1、 继承于Thread类
直接举个例子,简单粗暴,讲解在代码的注释里面。
// 创建多线程的方式一:继承与Thread类
public class TestThread {
public static void main(String[] args) {
PrintNum p1 = new PrintNum("线程1");
PrintNum p2 = new PrintNum("线程2");
// 设置优先级
p1.setPriority(Thread.MAX_PRIORITY);// 10
p2.setPriority(Thread.MIN_PRIORITY);// 1
p1.start();
p2.start();
}
}
class PrintNum extends Thread{
public void run(){
for (int i=0;i<101;i++){
if (i % 2 == 0)
System.out.println(Thread.currentThread().getName()+":"+i);
}
System.out.println("----------------");
}
/* 通过构造函数给线程命名,在官方手册里面搜Thread
就可以看到这个使用方法了
*/
public PrintNum(String name){
super(name);
}
}
2、 通过Runnable实现
窗口售票的例子
总共有三个窗口,在销售仅有的100张票。
先讲一下按照第一种继承Thread的方式实现如下:
// 模拟售票,开启三个窗口售100张票
public class TestThread2 {
public static void main(String[] args) {
window window1 = new window("窗口1");
window window2 = new window("窗口2");
window window3 = new window("窗口3");
window1.start();
window2.start();
window3.start();
}
}
class window extends Thread{
static int ticket = 100;
public window(String name){
super(name);
}
public void run(){
while (true){
if (ticket >0){
// 调用yield()使结果更明显
currentThread().yield();
System.out.println(Thread.currentThread().getName()+":"+ ticket--);
}else{
break;
}
}
}
}
第二种就是通过Runnable实现的方式。
下面举一个例子了解一下Runnable如何创建多线程,后面在给出窗口取票的代码。
// 1. 创建一个实现了Runnable接口的类
class PrintNum2 implements Runnable {
// 子线程执行的代码
public void run() {
for (int i = 0; i < 101; i++) {
if (i % 2 == 0)
System.out.println(Thread.currentThread().getName() + ":" + i);
}
System.out.println("----------------");
}
}
public class TestThread3 {
public static void main(String[] args) {
PrintNum2 p = new PrintNum2();
/*
这里使用w.start();是错误的,但是要想启动线程,
怎么办呢?调用w.run()?
使用w.run()确实能够执行,但是这就不是多线程了。
要想一个多线程,必须使用start()方法。请往下看:
*/
Thread t1 = new Thread(p);
t1.start();
Thread t2 = new Thread(p);
t2.start();
}
}
使用runnable实现的方式好处是什么?
避免了java单继承的局限性
如果多个线程要操作同一份数据(资源),此方式十分适合!
通过Runnable实现窗口售票
class Window implements Runnable{
int ticket = 100;
public void run(){
while(true){
if (ticket > 0 ){
System.out.println(Thread.currentThread().getName()+"售票,余票为:"+ticket--);
}else {
break;
}
}
}
}
public class TestThreadRunnable {
public static void main(String[] args){
Window window = new Window();
Thread window1 = new Thread(window);
Thread window2 = new Thread(window);
Thread window3 = new Thread(window);
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
// 调用yield()使结果更明显
window1.currentThread().yield();
window2.start();
window3.start();
}
}
Intellij的话,线程抢CPU不是很明显。用yield尝试让线程更明显。
运行的时候,不知道你们是否出现过这样的错误,票数里面出现重票、0票和-1票。如果没有出现这样的问题,不能说明你的程序没有问题而是有这样的问题存在这个程序里面,出现这样的原因是什么?请看下面的生命周期,解答这个问题。
二、线程的生命周期
1、 补充
Java中的线程分为两类:守护线程、用户线程(默认)。
它们几乎在每个方面都是相同的,唯一的区别是判断JVM何时离开。意思就是,只要当前JVM实例中尚存任何一个非守护线程没有结束,守护线程就全部工作
守护线程是用来服务用户线程的,通过在start() 方法前调用Thread.setDaemon(true) 可以把一个用户线程变成一个守护线程。
2、 JDK中用Thread.State枚举表示线程的五种状态
(1) 新建
当一个Thread类或子类的对象被声明并创建的时候,这个新的线程就处于新建状态。
(2) 就绪
处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行条件。
(3) 运行
当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能。
(4) 阻塞
在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时暂停执行,进入阻塞状态。
(5) 死亡
线程完成了它的全部工作或线程被提前强制性地终止。比如说,正常执行完、Error、Exception、stop()。
执行流程图
解决窗口售票的问题
问题:票数里面出现重票、0票和-1票。
大家肯定想到了一个解决方案:将票数存到另外一个数组里面,输出之前判断票数是否大于等于1,是否与数组里面的每一个元素相同。
先分析一下出现BUG的原因:
三个窗口(一号、二号、三号)线程同时操作同一个票数这个数据的时候,一号线程在操作这个数据的时候,还没有执行完,二号线程就已经开始执行了,形成了一种“抢”的状态,于是出现了数据共享的安全问题。
解决方案就是你想的那样:就如同上公共厕位大便一样,这个人解决完了,另外一个人才能进去。
代码有两种调整方式为线程的同步:同步代码块、同步方法。请继续阅读下文。
三、 线程的同步
内容有点多,分了一篇出来:https://blog.csdn.net/qq_41647999/article/details/88368663
四、 线程的通信
进入线程通信之前,先了解一下什么是死锁:https://blog.csdn.net/qq_41647999/article/details/88542529
具体应该如何通过线程通信来解决死锁:https://blog.csdn.net/qq_41647999/article/details/88541889