Java中的线程理解

程序:一段静态的代码,一组指令的有序集合,它本身没有任何运行的含义,它只是一个静态的实体,是应用软件执行的蓝本。

进程:是程序的一次动态执行,它对应着从代码加载,执行至执行完毕的一个完整的过程**,是一个动态的实体,它有自己的生命周期。它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消。反映了一个程序在

线程: 可以理解为进程的多条执行线索,每条线索又对应着各自独立的生命周期

一个java程序运行起来,只会有一个进程,但是可以有多个线程
**

运行一个最简单java程序,我们看一下会创建多少个线程

  1. import java.lang.management.ManagementFactory;
  2. import java.lang.management.ThreadInfo;
  3. /**
  4. * Hello world!
  5. *
  6. */
  7. public class App
  8. {
  9. public static void main( String[] args ){
  10. // 获取java线程管理器MXBean,dumpAllThreads方法参数:
  11. // @param1: lockedMonitors参数表示是否获取同步的monitor信息,
  12. // @param2:lockedSynchronizers表示是否获取同步的synchronizer
  13. ThreadInfo[] threadInfos = ManagementFactory.getThreadMXBean().dumpAllThreads(false, false);
  14. for (ThreadInfo threadInfo : threadInfos) {
  15. System.out.println("线程号=" + threadInfo.getThreadId() + ",线程名=" + threadInfo.getThreadName());
  16. }
  17. }
  18. }

输出如下:

线程号=6,线程名=Monitor Ctrl-Break
线程号=5,线程名=Attach Listener
线程号=4,线程名=Signal Dispatcher
线程号=3,线程名=Finalizer
线程号=2,线程名=Reference Handler
线程号=1,线程名=main

普通的java程序通常包含5个线程:

6:Monitor Ctrl-Break
监听中断信号(idea中特有的线程)

5:Attach Listener **
该线程是负责接收到外部的命令,执行该命令,并且把结果返回给发送者。

4:Signal Dispatcher
当Attach Listener线程命令接收成功后,会交给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。

3:Finalizer
调用对象的finalizer 方法,**JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer对象的引用置为null,由垃圾收集器来回收

2:Reference Handler
清除Reference队列,它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题。

1:main
程序的主入口,用于执行我们编写的java程序的main方法

守护线程与用户线程

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
User线程就是我们平常创建的普通线程。
Daemon线程是系统的守护者,在后台默默提供服务,用来服务于用户线程;不需要上层逻辑介入。作用是为其他线程的运行提供服务,比如说GC线程以及上述的Attach Listener、Signal Dispatcher 、Finalizer、Reference Handler 线程(除了main线程)都是守护线程。

证明是否守护线程主要看Thread对象的isDaemon方法:
image.png**

import java.util.Set;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args ){

        Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
        for(Thread thread: threadSet) {
            System.out.println("线程id="+thread.getId()+",线程名=" + thread.getName()
                    + ",线程组=" + thread.getThreadGroup()
                    + ",是否守护线程=" + thread.isDaemon());
        }
    }
}

输出如下:可以看到,在这个程序中只有main以及IDEA特有的线程不是守护线程

线程id=5,线程名=Attach Listener,所属线程组=system,是否守护线程=true
线程id=1,线程名=main,所属线程组=main,是否守护线程=false
线程id=4,线程名=Signal Dispatcher,所属线程组=system,是否守护线程=true
线程id=2,线程名=Reference Handler,所属线程组=system,是否守护线程=true
线程id=3,线程名=Finalizer,所属线程组=system,是否守护线程=true
线程id=6,线程名=Monitor Ctrl-Break,所属线程组=main,是否守护线程=true


image.png

如果用户线程全部结束,则意味着这个程序无事可做。守护线程要守护的对象已经不存在了,那么整个应用程序就应该结束。因此,当一个Java应用内只有守护线程时,Java虚拟机自然退出。

可以通过Thread.setDaemon将手动创建的线程设置守护线程。

简单线程使用

为了更好的理解线程和下面相关的内容,我们先来简单了解一下线程开发。创建线程有三种方式,一种是继承java.lang.Thread类,另一种是实现java.lang.Runnable接口,还有一种是使用Callable和Future接口创建线程(实际上,Thread类就是Runnable接口的一个是实现类)

继承Thread类创建与启动线程

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread(); // 创建线程
        myThread.start(); // 启动线程
    }

    /**
     * 继承Thread的线程类
     */
    static class MyThread extends Thread{
        // 重写线程的执行方法(也就是当前线程到底要做些什么)
        @Override
        public void run() {
            System.out.println("我是继承Thread的线程");
        }
    }
}

实现Runnable接口创建与启动线程

public class ThreadDemo {
    public static void main(String[] args) {
        Thread myRunnableThread = new Thread(new MyRunnableThread()); // 创建线程
        myRunnableThread.start(); // 启动线程
    }

    /**
     * 实现Runnable的线程类
     */
    static class MyRunnableThread implements Runnable{
        // 重写线程的执行方法(也就是当前线程到底要做些什么)
        @Override
        public void run() {
            System.out.println("我是实现Runnable接口的线程");
        }
    }
}

使用Callable和Future接口创建线程(较少用)

具体是创建Callable接口的实现类,并实现call()方法。使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。

这种方式创建线程可以根据FutureTask的get方法获取线程执行情况(底层判断线程状态并返回)

package com.java;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        MyCallable myCallable = new MyCallable(); // 创建MyCallable对象
        FutureTask future = new FutureTask(myCallable); // 使用FutureTask来包装MyCallable对象
        Thread myCallableThread = new Thread(future); // FutureTask对象作为Thread对象的target创建新的线程
        myCallableThread.start(); // 线程进入到就绪状态 
        System.out.println(future.get()); // 获取线程执行完毕返回内容
    }

    /**
     * 实现Callable接口的线程类
     */
    static class MyCallable implements Callable<String> {

        // 与run()方法不同的是,call()方法具有返回值
        @Override
        public String call() {
            System.out.println("我是实现Callable接口的线程");
            return "线程执行完啦";
        }
    }
}

三种方式的比较

实现 Runnable 接口相比继承 Thread 类有如下优势

1)可以避免由于 Java 的单继承特性而带来的局限
2)增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的
3)线程池只能放入实现 Runable 或 Callable 类线程,不能直接放入继承 Thread 的类

现 实现 Runnable 接口和实现 Callable 接口的区别

1)Runnable 是自从 java1.1 就有了,而 Callable 是 1.5 之后才加上去的
2)实现 Callable 接口的任务线程能返回执行结果,而实现 Runnable 接口的任务线程不能返回结果
3)Callable 接口的 call()方法允许抛出异常,而 Runnable 接口的 run()方法的异常只能在内部消化,不能继
续上抛
4)加入线程池运行,Runnable 使用 ExecutorService 的 execute 方法,Callable 使用 submit 方法
注:Callable 接口支持返回执行结果,此时需要调用 FutureTask.get()方法实现,此方法会阻塞主线程直到获
取返回结果,当不调用此方法时,主线程不会阻塞

具体源码后面的文章会讲,此处不再赘述。

线程的状态

在java.lang.Thread类中,使用枚举类State为Java线程定义了六种状态,代码如下:

public enum State {

    NEW,

    RUNNABLE,

    BLOCKED,

    WAITING,

    TIMED_WAITING,

    TERMINATED;
}

1、NEW-新建状态

使用new创建一个线程对象,仅仅在堆中分配内存空间,在调用start方法之前的线程所处的状态;在此状态下,线程还没启动,只是创建了一个线程对象存储在堆中

2、RUNNABLE-可运行状态

又可以细分成两种状态,readyrunning,分别表示就绪状态和运行状态。

就绪状态:线程对象调用start方法之后等待JVM的调度(此时该线程并没有运行),还未开始运行;

运行状态:线程对象已获得JVM调度,处在运行中;如果存在多个CPU核心,那么允许多个线程并行运行;

3、BLOCKED-阻塞状态

处于运行中的线程因为某些原因放弃CPU时间片,暂时停止运行,就会进入阻塞状态;此时JVM不会给线程分配CPU时间片,直到线程重新进入就绪状态(ready),才有可能转到运行状态;

阻塞状态发生的两种情况:
1、当A线程处于运行中,试图获取同步锁时,但同步锁却被B线程获取,此时JVM会把A线程存到共享资源对象的锁池中,A线程进入阻塞状态;
2、当线程处于运行状态,发出了IO请求时,该线程会进入阻塞状态;

4、WAITING-等待状态

等待状态,JVM会把该线程储存到共享资源的对象等待池中,该线程进入等待状态;处于该状态中的线程只能被其他线程唤醒.

调用以下方法可能会导致一个线程处于等待状态:

  • Object.wait()
  • Thread.join()
  • LockSupport.park


例如:对于wait()方法,一个线程处于等待状态,通常是在等待其他线程完成某个操作。本线程调用某个对象的wait()方法,其他线程处于完成之后,调用同一个对象的
notify或者notifyAll()**方法。Object.wait()方法只能够在同步代码块中调用。调用了wait()方法后,会释放锁。

5、TIMED_WAITING-计时等待状态

线程等待指定的时间,此状态下的线程不会释放同步锁/同步监听器。

对于以下方法的调用,可能会导致线程处于这个状态:

  • Thread.sleep**(long time)**
  • Object.wait**(long time)**
  • Thread.join**(long time)**
  • LockSupport.parkNanos
  • LockSupport.parkUntil

**

6、TERMINATED-终止状态

也可以称为死亡状态,表示线程终止,它的生命走到了尽头;线程一旦终止,就不能再重启启动,否则会发生IllegalThreadStateException;
有以下两种情况线程会进入终止状态:

1、正常执行完run方法而退出,寿终正寝,属于正常死亡;

2、线程执行遇到异常而退出,线程中断,属于意外死亡;

线程的生命周期

每个事物都有其生命周期,从生至死为生命周期。

在Java 中,给定的时间点上,一个线程只能处于一种状态。而线程的生命周期则是在这些状态间转化直至终止的过程,这些状态中NEW状态是开始,TERMINATED时销毁,在整个线程对象的运行过程中,这个两个状态只能出现一次。其他任何状态都可以出现多次,彼此之间可以相互转换。

Java中的线程 - 图3

Java线程生命周期中状态是如何切换的

**

NEW状态 —-> RUNNABLE状态

调用线程的start()方法。

RUNNABLE状态转换

就绪状态转换为运行状态:当此线程得到处理器资源;
运行状态转换为就绪状态:当此线程主动调用yield()方法在运行过程中失去处理器资源

RUNNABLE状态 <—-> 休眠状态


BLOCKED状态 <—-> RUNNABLE状态**

1、RUNNABLE状态 —-> BLOCKED状态
线程准备访问一个加锁资源,当线程获取锁失败时线程进入到BLOCKED状态;

2、BLOCKED状态 —-> RUNNABLE状态
当前面
已经获取了锁的线程调用了对象锁的notify(),notifyAll()方法时,对应锁队列的线程从BLOCKED状态切换RUNNABLE状态 再次尝试去获取锁**;

WAITING状态 **<—-> **RUNNABLE状态
1、处于RUNNABLE状态的线程调用LockSupport.park()方法由RUNNABLE切换到WAITING状态;再调用LockSupport.unpark()方法由WAITING切换到RUNNABLE;

2、线程A是获得synchronyzed锁的线程,调用锁对象的wait()方法后释放对象锁,线程A由RUNNBALE切换到BLOCKED状态;其他线程调对象锁的notify(),notifyAll()方法时线程A由WAITING切换到RUNNABLE状态。

3、线程A调用线程B的join()方法,线程A由RUNNABLE状态切换到WAITING状态,当线程B执行结束之后,线程A由WAITING状态切换到RUNNABLE状态;

TIME_WAITING状态 <—-> RUNNABLE状态
1、调用带参数的Thread.join(long millis)方法;
2、调用带参数的Thread.sleep(long millis)方法;
3、调用带参数的LockSupport.parkNanos(Object blocker, long nanos)方法;
4、调用带参数的LockSupport.parkUntil(long deadline)方法。
5、获得synchronized锁后,调用带参数的Object.wait(long timeout)方法;

RUNNABLE状态—->TERMINATED状态

1、线程执行完或者线程执行异常;
2、调用线程stop()方法强制线程结束;
3、线程B调用A的interupt()方法。

Thread类主要方法介绍

Thread类作为线程的主要体现,位于java.lang包下,实现了java.lang.Runnable接口

Thread常用方法
1.start():启动当前的线程,调用当前线程的run()
2.run():通常需要重写Thread类中的此方法,将线程要执行的操作声明在此方法中
3.yield():释放当前CPU的执行
4.join():在线程a中调用线程b的join()方法,此时线程a进入阻塞状态
5.interrupt():中断线程
6.sleep(long millitime):让当前进程睡眠指定的毫秒数,在指定时间内,线程是阻塞状态
7.isAlive():判断线程是否存活
8.currentThread():静态方法,返回代码执行的线程
9.getName():获取当前线程的名字
10.setName():设置当前线程的名字
11.setPriority():设置当前线程的优先级
12.getPriority():获取当前线程的优先级

1. Thread#start-启动

启动当前的线程,调用当前线程的run()
执行start方法使当前线程由NEW状态转换成RUNNABLE状态

/* Java thread status for tools,
 * initialized to indicate thread 'not yet started'
 */
private volatile int threadStatus = 0;

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".
     */
    // 如果当前线程不是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方法,这里会创建一个新的线程,是一个 native 方法
        // 当线程创建完毕且初始化后,一旦得到CPU时间分配,JVM会根据这个回调run方法执行线程操作
        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 */
        }
    }
}

// native :就是本地方法,调用操作系统函数
private native void start0();

2.Thread#run-运行

通常需要重写Thread类中的此方法,将线程要执行的操作声明在此方法中

/* What will be run. */
private Runnable target;

@Override
public void run() {
    // 如果是以实现Runnable接口方式创建线程,则调用Runnable是实现类的run方法
    if (target != null) {
        target.run();
    }
}

3.Thread#yield-让步

执行此方法会向系统线程调度器(Schelduler)发出一个暗示,告诉其当前JAVA线程打算放弃对CPU的使用,但该暗示,有可能被调度器忽略。

效果是暂停当前正在执行的线程对象,让出当前线程CPU的时间片,使当前线程从执行状态变为就绪状态,并执行其他线程。

使用该方法,可以防止线程对CPU的过度使用,提高系统性能。

public static native void yield();
private static void yieldExcemple(){
        Thread threadA = new Thread("测试线程A"){
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println(this.getName()+"-"+i);
                    Thread.yield(); // 线程让步,将CPU资源让出
                }
            }
        };

        Thread threadB = new Thread("测试线程B"){
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println(this.getName()+"-"+i);
                    Thread.yield(); // 线程让步,将CPU资源让出
                }
            }
        };
        threadA.start();
        threadB.start();
    }

4.Thread#join-同步等待

在主线程(main线程)中调用子线程的join()方法,此时主线程进入**等待状态,等待子线程执行完成后继续执行主线程

那这个方法主要用来控制线程执行顺序,如下代码,没有使用join的时候,主线程反而比子线程跑的更快,有时候我们希望子线程先执行完,然后主线程再执行

public class MethodTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread("测试线程"){
            @Override
            public void run() {
                System.out.println("哈哈哈哈啊哈哈");
            }
        };
        thread.start();
        System.out.println("主线程结束咯。。。");
    }
}

输出:
主线程结束咯。。。
哈哈哈哈啊哈哈

使用join方法后,主线程总是在子线程执行完成后才执行

public class MethodTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread("测试线程"){
            @Override
            public void run() {
                System.out.println("哈哈哈哈啊哈哈");
            }
        };
        thread.start();
        thread.join(); // 调用子线程的join方法
        System.out.println("主线程结束咯。。。");
    }
}

输出:
哈哈哈哈啊哈哈
主线程结束咯。。。

join方法源码

public final void join() throws InterruptedException {
    join(0);
}

// 成员方法加了synchronized说明是synchronized(this),this是谁啊?
// this就是子线程对象本身。也就是说,主线程持有了子线程这个对象的锁。
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");
    }

    // 当阻塞时间==0的时候,只要当前线程还有效,则无限期等待,也就是阻塞状态
    if (millis == 0) {
        while (isAlive()) {
            wait(0); // wait()方法是Object类中的方法
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }

            // 计时等待,如果这段时间内子线程没有执行完也不理会
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

join() 是一个synchronized方法, 里面调用了wait(),这个过程的目的是让持有这个同步锁的线程进入等待。
因为主线程调用了thread.join()方法,相当于在thread.join()代码这块写了一个同步代码块,谁去执行了这段代码呢,是主线程,所以主线程被wait()了

那么,主线程又是怎么知道子线程执行完了呢?

子线程thread执行完毕之后,JVM会调用lock.notify_all(thread);**唤醒持有thread这个对象锁的线程**,也就是主线程,会继续执行

5.Thread#interrupt-中断标识

① 如果线程处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。

② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,并不会被中断,当然线程内部可以写逻辑判断是否需要中断

/* The object in which this thread is blocked in an interruptible I/O
 * operation, if any.  The blocker's interrupt method should be invoked
 * after setting this thread's interrupt status.
 */
private volatile Interruptible blocker;
private final Object blockerLock = new Object();

public void interrupt() {
    // 如果不是当前线程,抛出SecurityException异常
    if (this != Thread.currentThread())
        checkAccess();

    // 对blockerLock对象加锁  private final Object blockerLock = new Object(); 
    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // 调用interrupt0 这个native的方法 :Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

private native void interrupt0();

案例如下:

private static void interruptExcemple(){
    Thread thread = new Thread("测试线程"){
        @Override
        public void run() {
            for(int i=0;i<1000;i++){
                // 判断interrupt标识是否为true
                if(Thread.currentThread().isInterrupted()){
                    System.out.println("收到线程中断的标识,准备退出循环");
                    break;
                }
                System.out.println(this.getName()+"-"+i);
            }
        }
    };

    thread.start();

    for(int i = 0 ;i<100;i++){
        if(i==50){
            thread.interrupt(); // 通知中断
        }
        System.out.println(Thread.currentThread().getName()+"-"+i);
    }
}

6.Thread#sleep-休眠

让当前进程睡眠指定的毫秒数,在指定时间内,线程是阻塞状态,但是并没有让出CPU资源。

是一个本地方法,通过系统调用暂停当前线程,而不是java自己实现的

public static native void sleep(long millis) throws InterruptedException;

// 支持纳秒传参
public static void sleep(long millis, int nanos)
    throws InterruptedException {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
            "nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }

    sleep(millis);
}

案例:

private static void sleepExample(){
    Thread thread = new Thread("测试线程"){
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                try {
                    if(i==50)
                        sleep(5000); // 休眠5秒
                    System.out.println(this.getName()+"-"+new Date());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };
    thread.start();
}

7.Thread#isAlive-是否存活

本地方法,判断线程是否存活

public final native boolean isAlive();

8.Thread#currentThread-当前线程

本地方法,获取当前线程对象

public static native Thread currentThread();

9.Thread#get/setName-线程名称

获取/设置当前线程的名字

private volatile String name;

public final String getName() {
    return name;
}

public final synchronized void setName(String name) {
    checkAccess();
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;
    if (threadStatus != 0) {
        setNativeName(name);
    }
}

10.Thread#get/setPriority-优先级

获取/设置当前线程的优先级

Java线程可以有优先级的设定,高优先级的线程比低优先级的线程有更高的几率得到执行(不一定)

  1. 当线程的优先级没有指定时,所有线程都携带普通优先级。
  2. 优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。
  3. 优先级最高的线程在执行时被给予优先。但是不能保证线程在启动时就进入运行状态。
  4. 与在线程池中等待运行机会的线程相比,当前正在运行的线程可能总是拥有更高的优先级。
  5. 由调度程序决定哪一个线程被执行。
  6. 线程的优先级应该在线程start方法被调用之前设定。
  7. 可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY来设定优先级。 ```java private int priority;

/**

  • The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1;

/**

  • The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5;

/**

  • The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10;

public final int getPriority() { return priority; }

public final void setPriority(int newPriority) { ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) != null) { if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriority0(priority = newPriority); } }

private native void setPriority0(int newPriority);

案例:
```java
private static void priorityExample(){
    Thread threadA = new Thread("测试线程A"){
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    };

    Thread threadB = new Thread("测试线程B"){
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    };
    threadA.setPriority(Thread.MIN_PRIORITY);
    threadB.setPriority(Thread.MAX_PRIORITY);
    threadA.start();
    threadB.start();
}

另外几个比较重要的方法

关于线程,还有几个比较重要的方法,因为涉及到锁机制,大部分内容放在下一篇文章。这里列举一下,wait( ) notifynotifyAll这几个方法都是继承自根类Object中。

wait

void wait():
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,当前线程持续等待。

void wait(long timeout):
计时等待,在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,当前线程等待。**如果时间当时间到来了,线程还是没有被唤醒或中断,那么就会自动唤醒;

void wait(long timeout, int nanos):
参数timeout的单位是毫秒,参数nanos的单位是纳秒,即等待时间精确到纳秒。其他特性与wait(long timeout)一样。

**

notify和notifyAll

void notify():
唤醒在此对象监视器(也就是临界对象/共享对象)上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;

void notifyAll():
唤醒在此对象监视器上等待的所有线程。
**

几个易混淆的方法

1、wait( )方法:

  • 先释放锁,再进入等待状态
  • 使当前线程暂停运行,让出CPU,不再参与CPU的调度,直到被唤醒为止;

2、sleep()方法:

  • 在睡眠过程中,不释放锁
  • 使当前线程暂停运行,让出CPU,不再参与CPU的调度,直到睡眠时间到来;

3、yield( )方法:

  • 不释放锁
  • 使当前线程暂停运行,给同等优先级或高优先级的线程让出CPU。但有可能让出CPU失败,因为调用yield( )方法后,当前线程由运行状态,变成就绪状态,会马上参与CPU的调度,也就说可能再次被调度,当前线程依旧占用着CPU。


看不懂没关系,下一篇文章肯定可以补全理解**