Java 线程浅析

一、什么是线程
要理解什么线程,我么得先知道什么是进程。现代操作系统在运行一个程序时,会为其创建一个进程。例如启动eclipse.exe其实就是启动了win系统的一个进程。现代操作系统调度的最小单元就是线程,也叫轻量级进程,在一个进程里面包含多个线程,这些线程都有各自的计数器、堆栈等,并且能够共享内存变量。例如我们启动了一个eclipse进程,我们运行在其中的程序就可以理解为线程。
二、为什么要使用线程
(1)更多的处理器核心(可以运行更多的线程)。
(2)更快的响应时间(线程可以并行执行)。
(3)更好的编程模型。
三、线程的状态
Java线程在运行的生命周期中有6中不同的状态,在给定的一个时刻,线程只能处于其中一个状态。如下图所示。

状态名称 说明
NEW 初始状态,线程被创建,但是还没有调用start方法。
RUNNABLE 运行状态,Java线程将操作系统中的就绪和运行两种状态统称地称作“运行中”
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
TIME_WAITING 超时状态,该状态不同于WAITING,它是可以在指定时间自行返回的
TERMINATED 停止状态,表示当前线程已经执行完毕

四、线程的调度(状态的变化)
我们先来看一张图:线程的调度对线程状态的影响

1)NEW(状态)线程创建未启动时的状态。如下代码示例:

Thread thread = new Thread(new ThreadTest());
        System.out.println(thread.getState());

输出结果:
NEW

Process finished with exit code 0
2)NEW-RUNNABLE线程调用start方法。如下代码示例:

Thread thread = new Thread(new ThreadTest());
        thread.start();
        System.out.println(thread.getState());

输出结果:
RUNNABLE
线程调用yield()方法,yield方法的作用就是让出CPU,当前线程从运行中变为可运行状态(READY),让和它同级或者更高级别的线程运行,但是不能保证运行的线程立马变成可运行状态(不确定的)。看如下代码示例:
代码设置了线程的优先级,但是测试了几次的测试结果都不相同。

**
 * 测试yield方法
 */
public class ThreadYieldTest {

    public static void main (String[] args) {
        Thread threadone = new Thread(new ThreadTestOne(),"ThreadTestOne");
        Thread threadtwo = new Thread(new ThreadTestTwo(),"ThreadTestTwo");
        threadone.setPriority(Thread.MIN_PRIORITY);
        threadtwo.setPriority(Thread.MAX_PRIORITY);
        threadone.start();
        threadtwo.start();
    }

    static class ThreadTestOne implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("ThreadTestOne----MIN_PRIORITY-----"+i);
                Thread.yield();
            }
        }
    }

    static class ThreadTestTwo implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("ThreadTestTwo-----MAX_PRIORITY----"+i);
                Thread.yield();
            }
        }
    }
}

结果一:
ThreadTestOne----MIN_PRIORITY-----0
ThreadTestTwo-----MAX_PRIORITY----0
ThreadTestOne----MIN_PRIORITY-----1
ThreadTestTwo-----MAX_PRIORITY----1
结果二:
ThreadTestTwo-----MAX_PRIORITY----0
ThreadTestOne----MIN_PRIORITY-----0
ThreadTestTwo-----MAX_PRIORITY----1
ThreadTestOne----MIN_PRIORITY-----1
ThreadTestTwo-----MAX_PRIORITY----2
ThreadTestTwo-----MAX_PRIORITY----3

3)RUNNABLE-WAITING(调用wait、join等方法)
(1)、wait方法可以让一个线程的状态变为WAITING或者TIME_WAITING,wait方法的作用是让当前线程进入等待队列,让出CPU的执行权,线程的状态变化为等待状态,执行wait方法的前提就是获取到对象锁,因为执行线程需要知道进入谁的等待队列,之后才能被谁唤醒。看如下代码示例:(jps 看java的进程id,jstack 看java线程的信息)

static class Parent implements Runnable {

        @Override
        public void run() {
            synchronized (lock){
                System.out.println("执行lock.wait");
                try {
                    lock.wait();
                    // 不会执行
                    System.out.println(Thread.currentThread().getName()+"----------"+Thread.currentThread().getState());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

通过命令看下线程信息如下图所示:可以看到Thread.State:WAITING

(2)、join方法也可以使线程以让一个线程的状态变为WAITING或者TIME_WAITING,join的方法的作用,字面意思加入、参加。我们可以这么理解join方法,一个线程加入一个正在运行的主线程中,并且使得正在运行的主线程等待加入的线程执行完毕才能继续执行。看如下代码示例:
1、我们在main方法启动两个线程,分别调用jion方法和不调用,看下执行结果一(不调用join方法):
Parent-----------------
Child-----------
结果顺序不确定
Child-----------
Parent-----------------

public static void main (String[] args) {
        Thread thread = new Thread(new Parent());
        thread.setName("Parent");

        Thread threadChild = new Thread(new Child());
        threadChild.setName("Child");

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

    /**
     * 父线程
     */
    static class Parent implements Runnable {

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"-----------------");
        }
    }

    /**
     * 子线程
     */
    static class Child  implements Runnable {

        @Override
        public void run() {
                System.out.println(Thread.currentThread().getName()+"-----------");
        }
    }

结果二(其中一个调用join方法)
(多次运行结果顺序不变)
Parent-----------------
Child-----------

public static void main (String[] args) {
        Thread thread = new Thread(new Parent());
        thread.setName("Parent");

        Thread threadChild = new Thread(new Child());
        threadChild.setName("Child");

        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadChild.start();
    }

2、join方法源码
可以看出join方法是调用的wait方法,所以线程的状态会变成WAITING或者TIME_WAITING。
我们来分析下是哪个线程加入等待队列,可以看出join方法是个synchronized方法,也就是说锁对象就是调用者,然后拥有锁对象的线程调用wait方法进入等待队列。我们通过上面的main方法来分析到底是谁进入等待队列,主角有main线程、Parent线程、Child线程,我们在main线程里面new了Parent线程和Child线程,然后在Child线程启动前面调用了Parent线程的join方法,也就是说是Parent线程调用了join方法,所以锁对象就是Parent线程实例,我们再来分析是那个线程拥有这个锁对象,答案是main线程,所以main线程调用wait方法进入等待队列Parent线程执行完毕;join方法结束时会唤醒主线程。

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

4)RUNNABLE-TIME_WAITING(调用sleep、wait、join等方法)
sleep(long)方法就是让线程睡眠一定的时间在执行,不过这个是有时间限制的,到了时间就会又变成RUNNABLE状态。如下代码示例:

static class Parent implements Runnable {

        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                System.out.println("-----------------------");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

结果

join(long)和wait(long)方法和WAITING状态的方式类似,只是加入时间后线程可以自动唤醒,自动从等待队列加入同步队列,获取到锁变成RUNNABLE状态。
sleep和wait的区别:
相同点:sleep和wait都可以使线程等待特定的时间;都可以使用interrupt()后调用阻塞方法中断线程;
不同点:sleep是Thread方法,wait是Object的方法;slepp到了时间自动唤醒,而wait没有规定时间时需要手动唤醒;在synchronized关键字修饰的方法或者块中,sleep不会释放锁,wait会释放锁。
4)TIME_WAITING or WAITING-RUNNABLE(调用notify()、notifyAll(),sleep时间到了)
sleep时间到了线程就会进入RUNNABLE状态(但是可能是RUNNING or READY状态)。
notify()是唤醒一个线程,进入同步队列,没有获取到锁就是BLOCKED状态,获取到锁就是RUNNABLE状态。
notifyAll()是唤醒等待队列的所有线程进入同步队列。
5)RUNNABLE-BLOCKED(线程获取锁失败,进入同步队列)
如下代码示例:

static class Parent implements Runnable {

        @Override
        public void run() {
            synchronized (lock) {
                    System.out.println(Thread.currentThread().getName()+"----------------------------------");
                try {
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 子线程
     */
    static class Child  implements Runnable {

        @Override
        public void run() {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName()+"----------------------------------");
            }
        }
    }

结果
6)RUNNABLE-TERMINATED(线程执行完毕)
五、如何优雅终止线程(手动)
我们知道线程提供了interrupt()、stop()方法;中断线程的三种方式;
1)stop方法,停止一个线程,现在已经是一个过期方法(不推荐使用)。
代码示例:

public static void main (String[] args) throws InterruptedException {
        Thread thread = new Thread(new Parent(),"Parent");
        thread.start();
        Thread.sleep(200);
        thread.stop();
    }

    /**
     * 父线程
     */
    static class Parent implements Runnable {

        @Override
        public void run() {
            while (true) {
                System.out.println("-----------------------");
            }
        }
    }

2) 使用interrupt()方法,只是给线程设置了一个中断标记,并不会中断线程,配合阻塞方法才能实现线程的中断。
如下代码示例:
中断了
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.ge.thread.method.ThreadStopTest$Parent.run(ThreadStopTest.java:30)
at java.lang.Thread.run(Thread.java:745)

public static void main (String[] args)  {
        Thread thread = new Thread(new Parent(),"Parent");
        thread.start();
        thread.interrupt();
    }

    /**
     * 父线程
     */
    static class Parent implements Runnable {

        @Override
        public void run() {
            try {
                while (true) {
                    /*System.out.println("------------");
                    // 测试 isInterrupted方法
                    System.out.println("0isInterrupted------"+Thread.currentThread().isInterrupted());
                    // 测试interrupted方法
                    System.out.println("1interrupted------"+  Thread.interrupted());
                    System.out.println("2interrupted------"+  Thread.interrupted());*/
                    Thread.sleep(500);

                }
            }catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("中断了");
            }
        }
    }

interrupt、interrupted和isInterrupted方法的区别,大家可以运行上面注释代码看运行结果。
interrupt:给线程一个中断标记,配合阻塞方法中断线程,抛出InterruptedException异常,并清除标记。
interrupted:Thread的静态方法,返回线程的中断标记状态,并且清理,所以第一次返回true或者false,第二次一定是false。
isInterrupted:返回线程的中断标记状态。
3)给线程的执行设置一个标记,满足就执行,不满足就结束线程。
如下代码示例:

 private static volatile boolean flag = true;

    public static void main (String[] args) {
        Thread thread = new Thread(new Parent());
        thread.setName("Parent");
        thread.start();
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 中断线程标记
        flag = false;
    }

    /**
     * 父线程
     */
    static class Parent implements Runnable {

        @Override
        public void run() {
            while (flag && !Thread.currentThread().isInterrupted()) {
                System.out.println("--------------");
            }
        }
    }

4)总结,stop方法就是强行结束线程,不推荐可能造成业务数据未知的错误,因为线程运行在那个过程时未知的;interrupt通过标记和阻塞方法一起中断线程,会抛出异常,但是要对异常做处理,例如处理线程为执行完成的任务或者回滚保证业务正确;推荐标记法,因为线程会走一个完整的过程,不会出现业务方面的未知错误,线程要么执行,要么不执行,不会执行一半就退出,所以就不会出现未知错误。
六、总结
本文针对线程的生命周期,线程的每个状态进行解释,以及线程执行过程的状态变化;线程调度对线程状态的影响,以及一些线程的基本方法;最后介绍了停止线程的三种方式;通过学习这些有助于我们理解线程的基础,为学习多线程打下基础,只有学习好了单线程,才能更好的学习多线程;希望与诸君共勉。

原文地址:https://blog.51cto.com/14220760/2380359

时间: 2024-10-17 21:21:12

Java 线程浅析的相关文章

浅析Java线程的正确停止

线程错误终止之destroy与stop方法 记得以前初学Java的时候,由于缺少对锁.同步.异步等这些线程的知识,想当然的以为destroy与stop方法都能正确的停止Java线程的执行.但是,后来随着工作的积累,以及对线程安全的一些理解,慢慢认识到这两个方法是有问题的,并且这两方法也早已在java doc上被指名是弃用的. destroy()这个方法其实根本没干什么事情,只是抛出了一个NoSuchMethodError,所以说该方法无法终止线程,因此不能望文生意: /** * Throws {

浅析java线程和OS线程的关系

探究java线程和OS线程之间的联系 一.准备工作 1.查看linux创建线程的方法    man pthread_create 根据man的配置可知,pthread_create会创建一个线程,这个函数是Linux的函数,可以通过C或者C++调用,该函数在pthread.h中 2.查看openjdk版本, rpm -qa | grep jdk 3.卸载原始openJDK版本 rpm -e --nodeps  xxxxx 4.准备oracle jdk7/jdk8,官网可自行下载对应的linux安

Android的线程浅析 补充

一.Looper的两点疑问 1) 问题一:Looper.loop()是处理消息,所有消息or部分消息? 2) 问题二:处理完消息后,结束or等待? Android官方示例文档代码:   class LooperThread extends Thread {       public Handler mHandler;       public void run() {           Looper.prepare();           mHandler = new Handler() {

Java NIO浅析 转至 美团技术团队

出处: Java NIO浅析 NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接.I/O处理问题的有效方式. 那么NIO的本质是什么样的呢?它是怎样与事件模型结合来解放线程.提高系统吞吐的呢? 本文会从传统的阻塞I/O和线程池模型面临的问题讲起,然后对比几种常见I/O模型,一步步分析NIO怎么利用事件模型处理I/O,解决线程池瓶颈处理海量连接,包括

java 线程详解

5月7号  周末看了一下线程方面的内容 ,边看视频边看书还附带着参考了很多人的博客,一天的收获,写下来整理一下:感觉收获还是挺多的:过段时间可能看完java  这几大块要去看一下关于spring boot  的内容顺便  也整理一下:附上我参考的 几本书: 关于java  线程,首先要了解一下线程和进程之间的关系.区别以及他们之间的概念: 首先是线程: 什么是线程? 线程是在程序执行过程中能够执行部分代码的一个执行单元,也看看做是一个轻量级的进程:线程是程序内的程序控制流只能使用程序内分配给程序

Java线程工作内存与主内存变量交换过程及volatile关键字理解

Java线程工作内存与主内存变量交换过程及volatile关键字理解 1. Java内存模型规定在多线程情况下,线程操作主内存变量,需要通过线程独有的工作内存拷贝主内存变量副本来进行.此处的所谓内存模型要区别于通常所说的虚拟机堆模型: 2. 线程独有的工作内存和进程内存(主内存)之间通过8中原子操作来实现,如下图所示: 原子操作的规则(部分): 1) read,load必须连续执行,但是不保证原子性. 2) store,write必须连续执行,但是不保证原子性. 3) 不能丢失变量最后一次ass

java线程

Java线程详解 1.操作系统中的线程和进程讲解: 现在的操作系统大都是多任务操作系统,多线程是多任务的一种. 进程是指操作系统中运行的一个程序,每个进程都有自己的一块内存空间,一个进程中可以启动多个线程. 线程是指进程中的一个执行流程,一个进程中可以运行多个线程.比如java.exe进程中可以运行很多线程.线程总是属于某个进程,进程中的多个线程共享进程的内存. “同时”执行是人的感觉,在线程之间实际上轮换执行. Java线程的两种具体实现方法: 第一种继承:具体代码实现如下: Public (

Java 线程第三版 第四章 Thread Notification 读书笔记

一.等待与通知 public final void wait() throws InterruptedException 等待条件的发生. public final void wait(long timeout) throws InterruptedException 等待条件的发生.如果通知没有在timeout指定的时间内发生,它还是会返回. public final void wait(long timeout, int nanos) throws InterruptedException

Java线程使用大全

1.线程实现 1.Thread类 构造方法: 案例代码: public class Ex10_1_CaseThread extends Thread {// 创建一个类继承(extend)Thread类 String studentName; public Ex10_1_CaseThread(String studentName) {// 定义类的构造函数,传递参数 System.out.println(studentName + "申请访问服务器"); this.studentNam