Java线程的一生

本文详细介绍了Java中线程的创建与启动,包括继承Thread类、实现Runnable和Callable接口的方式。此外,还探讨了线程的结束、中断机制、守护线程以及线程的就绪、运行和阻塞状态。重点讲解了start()、join()、yield()、interrupt()等方法的作用和线程状态转换。
摘要由CSDN通过智能技术生成

Java中的线程

线程的新建启动

Java程序在运行时,除业务所用的线程外,还有后台守护线程,用于JVM执行资源关闭垃圾回收监听机制等等。所以Java中的程序天生便是多线程的。
本章,将讲述Java中线程的使用。所谓"万物皆对象",在Java中,Thread类就是对线程的抽象。
那么首先,在Java中是如何启动线程的呢?有2种方法:①继承Thread类;②实现Runnable接口;
jdk1.5引入了第三种方式:实现Callable接口。

继承Thread类

首先编写一个对象,继承Thread类,代表一个线程。重写run()方法,编写该线程具体任务。

public class NewThread1 extends Thread{
	@Override
	public void run() {
        System.out.println("NewThread1 extends Thread run...");
	}
}

然后 main方法,初始化对象,调用start()方法,运行线程。

public class Main{
	public static void main(String[] args){
   		 NewThread1 newThread1 = new NewThread1();
  	     newThread1.start();
	}
}

实现Runnable接口

首先编写一个对象,实现Runnable接口。重写run()方法,编写需要线程具体执行的任务。

public class NewThread2 implements Runnable{
	@Override
	public void run() {
        System.out.println("NewThread21 implements Runnable run...");
	}
}

然后 main方法,初始化上面编写好的需要任务对象,以编写好的任务对象构造一个线程对象,调用start()方法,运行线程。

public class Main{
	public static void main(String[] args){
   		 NewThread2 newThread2 = new NewThread2();
  	     new Thread(newThread2).start();
	}
}

实现Callable接口

我们可以看到上述两种方式,启动线程的方法大致方向就是重写run()方法,new一个Thread类或其子类,调用start()方法。
但Callable的启动线程的方式与众不同。

  1. 它虽然也要重写方法,但重写的不是run(),是call();
  2. Callable的call()方法有泛型返回值,而Runnable与Thread的子类重写的run()方法不能有返回值;
  3. 上述两种方式只需要初始化自身或者自身构造一个Thread类,调用start()即可启动线程,但Callable创建线程需要FutureTask的支持。
	public class NewCallable implements Callable<Integer>{
		@Override
		public Integer call() {
			System.out.println("NewCallable implements Callable<Integer> call...");   
	        return 1; 
		}
	}
public class Main{
	public static void main(String[] args){
   		 NewCallable newca = new NewCallable();
   		 //用FutureTask包装Callable
   		 FutureTask<Integer> task = new FutureTask<>(newca);
  	     new Thread(task).start();
	}
}

疑惑

为什么要重写run()方法? 查看Thread源码,无论从注释还是从run()代码都能发现,当线程是Thread的子类时,run()方法并不会做任何操作;而Runnable是一个函数式接口,run()是抽象方法,依赖于实现类。

public class Thread implements Runnable{
 /* What will be run. */
 private Runnable target;
    /**
     * If this thread was constructed using a separate
     * <code>Runnable</code> run object, then that
     * <code>Runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of <code>Thread</code> should override this method.
     *
     * @see     #start()
     * @see     #stop()
     * @see     #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    
}

使用start()启动线程?
new一个Thread,此时只是单纯的在JVM创建一个Thread对象,并没有与实际操作系统进程中的线程挂钩,当调用start()方法,运行start0()这个本地方法,才与线程真正挂钩。

  /**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>
     * The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * <code>start</code> method) and the other thread (which executes its
     * <code>run</code> method).
     * <p>
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */
    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 */
            }
        }
    }
   
   private native void start0();

此时,有一个问题:start()能否多次调用? 答案是不能的,根据上述源码可以看到threadStatus != 0的异常抛出。当线程调用一次后,当前threadStatus也就不再等于0,再次调用就会抛出异常。

Callable与FutureTask
FutureTask可通过Callable构造。而FutureTask实现RunnableFuture接口;RunnableFuture接口继承Runnable接口,有抽象方法run();根据源码可以看到在FuntureTask中实现的run()方法内调用了call()方法。也就是说,Callable方式的实现,其本质仍是重写run()方法,调用start()启动线程。
start0()如何使Thread与线程真正挂钩
threadStatus启动线程后如何变化

待补充

public class FutureTask<V> implements RunnableFuture<V> {
    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
    
}

线程的结束中止

讨论完线程的启动,那么,如何结束一个线程?又或者如何中止一个线程?Java中提供了一系列的方法。结束有stop()关闭,destroy(),suspend()挂起,resume();但查看源码能够发现系统给这些结束方法打上了@Deprecated注解,不推荐使用。其原因很简单:如suspend挂起方法,它的调用会将线程挂起,但并不释放该线程占用的资源,这可能引发死锁。stop()终结时会强制结束线程,并不关心线程占用的资源。
取代结束的是中断机制,interrupt()方法,正常停止线程使用。interrupted()与isInterrupted()判断当前线程是否被中断。

interrupte()、interrupted()、isInterrupted()

interrupte()方法的实质是修改线程的中断标志位,只是告诉线程这个地方有人要求中断。这也就是说明jdk线程是协作式,而不是抢占式
interrupted()、isInterrupted()都是用来判定当前是否要求中断。其区别在于isInterrupted()方法是static方法,同时调用后会修改中断标志位为false。查看源码可以发现interrupted()与isInterrupted()的实质是调用本地方法isInterrupted()。ClearInterrupted入参确定是否重置中断状态,这也就是为什么isInterrupted()会修改中断标志位为false。

    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
    
    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

守护线程

守护线程是一种支持型线程,用于程序后台中调度及支持性工作。当非守护线程,也就是用户线程全部停止时,守护线程随之结束。setDaemon() 可将线程设置为守护线程。守护线程中若有finally,其内容不一定会执行,也就是不能依靠finally来确保执行关闭或清理资源。

线程的就绪运行与阻塞

线程从启动到结束的中间过程是怎样的?当线程所占有的CPU时间片到期时是如何等待下一个时间片?在线程运行中可能引发死锁等线程安全问题,可能被挂起等等,在这些情况下当前线程并不会释放占用的资源,此时线程是什么状态?线程的生命周期有新建,就绪,运行,阻塞,死亡5种状态。他们的联系如下:
线程生命周期
CPU时间片轮转,即使启用start(),当操作系统分配的CPU时间片未到的时候,线程也不会进入运行状态,而是就绪状态,等待时间片到达,获得执行权,才是运行状态。join() 方法可获取执行权,yield() 方法则会让出时间片。而sleep()wait() 会使线程进入阻塞状态。

线程优先级
优先级范围1~10,优先级越高,被分配CPU时间片的可能越高。setPriority() 可设置线程运行优先级。

yield()、join()

yield() 方法会让出时间片,将线程从运行状态转为就绪状态,等待时间片执行权。操作系统在选中下一个时间片分配对象时,仍可能选中该线程。调用yield()方法的线程并不会释放锁。
join() 方法会将调用的线程对象加入到当前线程,挂起当前线程,至加入的线程执行完后继续,即在线程B中调用了线程A的join()方法,那么,直到线程A执行完毕后,才会继续执行线程B。也就是将两个交替执行的线程合并为顺序执行

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值