Java 线程详解(二)多线程创建、生命周期、线程同步、线程通信

版权声明:欢迎转载,转载请注明出处哦! https://blog.csdn.net/qq_41647999/article/details/88046196

目录直通车

一、 多线程的创建与使用

1、 继承于Thread类

2、 通过Runnable实现

窗口售票的例子

通过Runnable实现窗口售票

二、线程的生命周期

1、 补充

2、 JDK中用Thread.State枚举表示线程的五种状态

(1) 新建

(2) 就绪

(3) 运行

(4) 阻塞

(5) 死亡

执行流程图

 解决窗口售票的问题

三、 线程的同步

四、 线程的通信


一、 多线程的创建与使用

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

猜你喜欢

转载自blog.csdn.net/qq_41647999/article/details/88046196