Java高并发入门-线程初步(二)

Java高并发入门详细讲解

上期回顾及问题总结

上次说了创建线程的两种常用方式,第三种方式在后面的更新中会讲解到。这里对于上一节的内容做个回顾。

  1. 在上一节中说到了创建多线程的问题,分析了Thread的源码,提出了三种的线程实现的方式。
  2. 简单的分析了共享资源锁竞争的问题,同样也引出了锁竞争的问题。
    这次我们就来结合实践详细的说说关于创建线程的其他的注意点。
入门例子

首先你是一个非常爱音乐的人,在平时你也喜欢听歌。如果你想在看新闻的时候也想听歌,这样的话让我们用Java代码来实现一下。

public class TryConcurrency {
    public static void main(String[] args) {
        browsNews();
        enjoyMusic();
    }
    /**
     * 听音乐
     */
    private static void enjoyMusic() {
        for (;;){
            System.out.println("this is good Music");
            sleep(1);
        }
    }
    /**
     * 看新闻
     */
    private static void browsNews() {
        for (; ; ) {
            System.out.println("this is good News");
            sleep(1);
        }
    }
    private static void sleep(int i) {
        try {
            TimeUnit.SECONDS.sleep(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

this is good News
this is good News
this is good News
this is good News
this is good News
this is good News

以上是我们输出的结果,看样子并不是我们想要的结果,并没有实现一遍听歌一边看新闻的功能。这里我们将以上的代码做一个优化,当然这里使用的Java8编程。如果对Java8不是很熟悉的人可以熟悉一下Java8的新特性。
如果我们想实现一边听歌一边看新闻的话就要做如下的改变

public static void main(String[] args) {
        new Thread(()->{
            enjoyMusic();
        }).start();
        browsNews();
    }

使用我们上节课说过的多线程的知识来解决这个问题。

This is good Music
this is good News
this is good News
this is good Music
this is good News
this is good Music
this is good News

生命周期分析

这样我们就会看到如上的结果,在一边听歌的同时一边看看新闻。既然引入了多线程的概念,那么我们就来看看多线程的生命周期有哪些,从上节课的分析中我们可以知道,在使用多线程的时候,需要调用start方法

start方法

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

生命周期调用关系图

在调用了start方法之后又会发生什么样的变化,那么我们就来看看线程的生命周期

在这里插入图片描述

关系说明

这张图是从一本书上截图的,从图中可以看到线程的五种状态,首先是new,也就是实例化一个线程,通过start方法的调用进入到了Runnable状态,当然在这个状态值能保证它是可是执行的,也就是说当它拿到资源之后立即可以执行,Running状态,在这个状态中表示线程在实现,通过调用yield,表示谦让的意思。将资源让出来之后原本处于Runnable状态的线程就会有获取到资源进入到Running状态,如果在Runnable状态或者在Running状态的线程调用了sleep方法或者是wait方法就会进入到Blocked(阻塞)状态,进入阻塞状态的线程有两种结果,一种是资源不够用了直接stop,另一种是通过调用notify或者notifyAll方法唤醒执行。而正常执行结束的线程或者是被Stop的线程就会进入到Terminated状态(最终状态)在这个状态的线程将不会被切换到任何的状态,这也意味着线程的整个生命周期的结束。而以下的情况将会导致线程进入到最终状态,其实在之前也提到了

  1. 线程正常运行结束生命周期
  2. 线程出现意外
  3. JVM Crash导致所有的线程都结束

上面我们提到了Start方法源码,这里我们分析完线程的生命周期之后再次的进入到start源码中,其实在start源码中做的最主要的一件事情就是调用我们start0方法,这个方法是JNI方法,也就是在Java编程中调用底层C语言或者C++语言的代码。当我们执行start方法的时候就会执行run方法,从而执行strat0方法。而在上一节中我们提到了Thread的源码

Thread源码实例分析

首先Thread被构造后的new状态,这个是时候threadStatus这个内部属性为0,如果两次启动Thread,就会出现IllegalThreadStateException异常,线程启动之后就被加入到一个ThreadGroup线程组中,当一个线程结束,也就是到了Treminated状态,再次调用start方法是不允许的。也就是说了Treminated状态是没有办法会到Runnable/Running状态的。这个逻辑我们使用代码来模拟一下

第一种情况

@Test
public void testMain(){

    Thread thread = new Thread(){
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    thread.start();
    thread.start();
}

第二种情况

@Test
public void testMain1() throws InterruptedException {

    Thread thread =new Thread(){
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    thread.start();
    TimeUnit.SECONDS.sleep(3);
    thread.start();
}

总结

以上两段代码都会引起IllegalThreadStateException异常,但是两个异常抛出却是有着本质的区别,第一个是重复启动,只是第二次启动不允许,但是此时线程属于运行状态,第二次企图重新激活也抛出了非法状态的异常,此时没有线程,因为该线程的生命周期已经被终结。到这里我们就看到了整个线程的生命周期

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nihui123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值