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

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

目录直通车

一、 多线程的创建与使用

1、 继承于Thread类

2、 通过Runnable实现

窗口售票的例子

通过Runnable实现窗口售票

二、线程的生命周期

1、 补充

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

(1) 新建

(2) 就绪

扫描二维码关注公众号,回复: 5741718 查看本文章

(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