深入理解JAVA线程、并发和等待通知机制

本文详细阐述了Java中线程的定义、创建方式(继承Thread和实现Runnable)、启动与终止的区别(start()vsrun()),以及如何安全地中断线程。特别强调了JVM内置线程和操作系统线程的关系,以及不推荐的线程终止方法如stop()、suspend()和resume()。
摘要由CSDN通过智能技术生成

*前言

         单从线程的定义来讲,线程是操作系统调度的最小单位,而JAVA的线程一般指的是Thread(万物皆对象)。每一个Thread的实例调用start()方法就是用于启动该线程。只要在运行JAVA程序,那么线程一定是存在的(有些语言可以只有进程没有线程),以一个最简单的JAVA程序为例,程序将打印所有线程的ID和名称:

public static void main(String[] args) {
        //Java 虚拟机线程系统的管理接口
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // ,仅仅获取不需要获取同步的monitor和synchronizer信息线程和线程堆栈信息
        ThreadInfo[] threadInfos =
                threadMXBean.dumpAllThreads(false, false);
        // 遍历线程信息,仅打印线程ID和线程名称信息
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "] "
                    + threadInfo.getThreadName());
        }
    }
[6] Monitor Ctrl-Break //监控 Ctrl-Break 中断信号的
[5] Attach Listener //内存 dump,线程 dump,类信息统计,获取系统属性等
[4] Signal Dispatcher // 分发处理发送给 JVM 信号的线程
[3] Finalizer // 调用对象 finalize 方法的线程
[2] Reference Handler//清除 Reference 的线程
[1] main //main 线程,用户程序入口
        以上是在java8版本下的结果,也就是说,尽管程序员不去新开线程,JVM本生就有很多线程,这些线程负责各自的业务(随着你对JVM更深入的学习,你会对这些线程非常熟悉)。
        【注意】上面所打印出来的信息是JAVA层面的线程信息,也就是JVM中一个个Thread对象的属性,这并不等同于操作系统的线程。这就体现了JVM的作用之处,程序员只需要站在JAVA层面使用多线程,一套代码在不同的操作系统中作用是一样的,至于JAVA的线程和操作系统线程之间的映射关系,或是说如何调用操作系统线程来实现JAVA线程,这是JDK的本地方法实现(C++代码,这里涉及到跨语言调用),建议在对JVM有清除认识后再去了解。

1、 线程的启动和终止

新建线程

        在前言中所看到的是JVM自己的线程,程序员要新建线程,无非就是实例化一个Thread或其派生类的对象。“新建线程的方法有多少种?”这个问题众说纷纭,下面给出的注释是源码中作者的解释:

/**
There are two ways to create a new thread of execution. One is to declare a
class to be a subclass of Thread. This subclass should override the run 
method of class Thread.An instance of the subclass can then be allocated 
and started. For example,a thread that computes primes larger than a stated 
value could be written as follows:
*/
class PrimeThread extends Thread {
           long minPrime;
           PrimeThread(long minPrime) {
               this.minPrime = minPrime;
           }
  
           public void run() {
               // compute primes larger than minPrime
                . . .
           }
       }
————————————————————————————————————————————————————————————————————————————
// The following code would then create a thread and start it running:
       PrimeThread p = new PrimeThread(143);
       p.start();

        作者说有两种方法去创建线程,第一种是派生Thread类,并重写Thread的run方法(注意这是直接继承了Thread,也就是程序员自定义了自己的线程类)。新建线程可以直接new一个自定义的线程类的实例。

         这种方法就是调用了Thread类的午餐构造方法,源码如下,可见给参数target的是一个null(作者源码中target的解释为:target – the object whose run() method gets called)

/**
Allocates a new Thread object. This constructor has the 
same effect as Thread (null, null, gname), where gname is 
a newly generated name. Automatically generated names are 
of the form "Thread-"+n, where n is an integer.
*/
public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

————————————————————————————————————————————————————————————————————————————
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

            我们可以在自定义的线程类中实现自定义的构造线程的逻辑,例如:

public class CustomThread extends Thread{

    public CustomThread(){
        super("customT");
    }


    public void run(){
        System.out.println(Thread.currentThread().getName());
    }

}

        每次new一个CustomThread(),用的不在是Thread的无参构造,而是用的Thread(String name)构造方法,同理,我们可以在自定义的线程类中实现很多自定义的逻辑。

/**
The other way to create a thread is to declare a class that implements the
Runnable interface. That class then implements the run method. An instance 
of the class can then be allocated, passed as an argument when creating 
Thread, and started. The same example in this other style looks like the 
following:
*/
class PrimeRun implements Runnable {
           long minPrime;
           PrimeRun(long minPrime) {
               this.minPrime = minPrime;
           }
  
           public void run() {
               // compute primes larger than minPrime
                . . .
           }
       }
 ————————————————————————————————————————————————————————————————————————————
// The following code would then create a thread and start it running:
       PrimeRun p = new PrimeRun(143);
       new Thread(p).start();

        另一种方法是声明一个实现Runnable接口的类,并实现run()方法,然后将该类的实例作为参数去new一个Thread类。这里和第一种方法的本质区别在于:方法1实际上是new了一个自定义线程类的实例,方法2实际上是new了一个JAVA原生的Thread类的实例,官方示例调用的是Thread众多构造方法中的一种。其实Thread有多个构造方法,如果想要更细致了解,可以查看源码或其他资料。

        注意这种方法和第一种方法在源码级别的不同,此处的Runnable其实就是target

/**
Allocates a new Thread object. This constructor has the same effect 
as Thread (null, target, gname), where gname is a newly generated 
name. Automatically generated names are of the form "Thread-"+n, 
where n is an integer.
Params:
target – the object whose run method is invoked when this thread is 
started. If null, this classes run method does nothing.
*/
public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

        关于其他新建线程的方法,如Callable、线程池下文有相应概括,这里重点是“创建线程有几种方法”,本文作者观点是两种(这也很可能是源码作者的看法):

  • 不传入target
  • 传入target

        而新建一个Thread类的实例是有很多种方法的,仅仅通过new来实例化,就有9种,还可以通过反射、反序列化、调用对象的clone()方法。

  • 关于通过Callable新建线程,其实现方法是通过FutureTask吧Callable包装成Runnale,然后使用如上文官方示例2的步骤去创建一个Thread实例,本质是没有区别的。其被设计出来的目的是使线程执行后可以返回一些信息(Thread的start()方法是一个void方法)。
  • 关于线程池,其本质就是引入了池化技术,目的是资源复用,如果要去学习线程池,应该站在其目的和作用的角度去思考和学习,而不是只把它当作新建线程的一种代码格式。

启动线程( 区分 start() 和 run() )

        启动某个线程一定是调用其start()方法,当start()方法被调用,JVM中的线程才真正的和操作系统的线程有了关系。该方法的作用是让线程进入就绪队列等待分配CPU,分配到CPU后才会调用run()方法。

        run()方法只是线程的业务逻辑,是可以直接通过实例调用的,但执行这段业务逻辑的线程是原来的线程,而不是新开的线程在执行。所以start()是不能重复调用的,而run()是可以重复调用的。

终止线程

  • 自燃终止:线程正常执行完,或抛出了没有处理的异常导致线程提前结束。
  • 不安全终止:Thread类有三个不建议使用的方法是stop()、suspend()、resume()。以suspend()为例,该方法会让线程睡眠并任然占有资源,这很容造成死锁。所以这三个方法是作者都不建议使用的。
  • 安全中断:通过调用interruput()方法通知该线程应该被中断,但是程序员可以通过代码选择什么时候停止线程,停止之前完成什么样的操作。通过isInterrupt()可以获取当前线程是否应该被中断,也可以调用静态方法Thread.isInterrupted()来判断是否应该被中断,但这个方法会在调用后将中断标识设置为false。      

        【注意】①程序不应该自己设置中断标志位(具体原因是阻塞线程操作在中断标志为true时会抛出interruptedException异常,自定义的中断标志会没有这个效果)。②死锁状态的线程是无法中断的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值