实现线程的方式、线程状态、JMM模型、Runnable与Callable的区别、线程池

Java 多线程

实现线程的方式

Runnable与Callable的区别

image01

线程池ExcutorService中的excutor和submit方法的区别

ExecutorCompletionService

当我们用线程池submit提交一组多线程任务,当我们需要每个任务完成时并获取它的返回值;有两种方式可以采取;第一种使用List获取保存返回值,第二种使用ExecutorCompletionService获取保存返回值。

示例代码List与ExecutorCompletionService比对

package com.yeming.spring.example.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * @author yeming.gao
 * @Description: ExecutorCompletionService获取一组任务示例
 * @date 2020/10/21 14:23
 */
public class ExecutorCompletionServiceTest {

    static class CallableTask implements Callable<String> {
        private int i;

        public CallableTask(int i) {
            this.i = i;
        }

        @Override
        public String call() throws Exception {
            Thread.sleep(20000);
            return Thread.currentThread().getName() + "执行完任务:" + i;
        }
    }


    public static void main(String[] args) {
        int taskSize = 5;

        System.out.println("ExecutorService start...");
        long begin01 = System.currentTimeMillis();
        // 创建一个线程池
        ExecutorService pool01 = Executors.newFixedThreadPool(taskSize);
        ExecutorCompletionService<String> completionService = new ExecutorCompletionService<>(pool01);
        try {
            for (int i = 1; i <= taskSize; i++) {
                CallableTask callableTask = new CallableTask(i);
                completionService.submit(callableTask);
            }

            for (int i = 1; i <= taskSize; i++) {
                System.out.println(completionService.take().get());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 关闭线程池
        pool01.shutdown();

        long end01 = System.currentTimeMillis();
        System.out.println("ExecutorService end..excute time:" + (end01 - begin01) + "ms");

        System.out.println("-------------------------------------------------------");

        System.out.println("List start...");
        long begin02 = System.currentTimeMillis();
        // 创建一个线程池
        ExecutorService pool02 = Executors.newFixedThreadPool(taskSize);
        // 创建多个返回值的任务
        List<Future<String>> list = new ArrayList<>();
        try {
            for (int i = 1; i <= taskSize; i++) {
                CallableTask callableTask = new CallableTask(i);
                Future<String> future = pool02.submit(callableTask);
                list.add(future);
            }
            for (Future<String> aList : list) {
                System.out.println(aList.get());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 关闭线程池
        pool02.shutdown();

        long end02 = System.currentTimeMillis();
        System.out.println("List end..excute time:" + (end02 - begin02) + "ms");
    }
}

/**
* 程序运行结果:
*
ExecutorService start...
pool-1-thread-3执行完任务:3
pool-1-thread-4执行完任务:4
pool-1-thread-5执行完任务:5
pool-1-thread-2执行完任务:2
pool-1-thread-1执行完任务:1
ExecutorService end..excute time:20011ms
-------------------------------------------------------
List start...
pool-2-thread-1执行完任务:1
pool-2-thread-2执行完任务:2
pool-2-thread-3执行完任务:3
pool-2-thread-4执行完任务:4
pool-2-thread-5执行完任务:5
List end..excute time:20002ms
**/

这里使用了线程池提高了整体的执行效率,但遍历这些Future,调用Future接口实现类的get方法是阻塞的,也就是和当前这个Future关联的计算任务真正执行完成的时候,get方法才返回结果,如果当前计算任务没有执行完成,而有其它Future关联的计算任务已经执行完成了,就会白白浪费很多等待的时间,所以最好是遍历的时候谁先执行完成就先获取哪个结果,这样就节省了很多持续等待的时间。

而ExecutorCompletionService可以实现这样的效果,它的内部有一个先进先出的阻塞队列,用于保存已经执行完成的Future,通过调用它的take方法或poll方法可以获取到一个已经执行完成的Future,进而通过调用Future接口实现类的get方法获取最终的结果。

ExecutorCompletionService方法解析

public class ExecutorCompletionService<V> implements CompletionService<V> {
	...
}
public interface CompletionService<V> {

    Future<V> submit(Callable<V> task);

    Future<V> submit(Runnable task, V result);

    Future<V> take() throws InterruptedException;

    Future<V> poll();

    Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;
}

ExecutorCompletionService实现了CompletionService接口,在CompletionService接口中定义了如下这些方法:

线程状态

线程一共有六种状态,分别为New、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED;具体状态信息可以看Thread类中的State枚举类。 同一时刻只有一种状态,通过线程的getState方法可以获取线程的状态。

public enum State {
    /**
     * 当线程被创建出来还没有被调用start()时候的状态
     */
    NEW,

    /**
     * 当线程被调用了start(),且处于等待操作系统分配资源(如CPU)、等待IO连接、正在运行状态,
	 * 即表示Running状态和Ready状态。
     * 
     * 注:不一定被调用了start()立刻会改变状态,还有一些准备工作,这个时候的状态是不确定的。
     * 
     */
    RUNNABLE,

    /**
     * 等待监视锁,这个时候线程被操作系统挂起。当进入synchronized块/方法或者在调用wait()被唤醒/超时之后重新进入synchronized块/方法,
	 * 锁被其它线程占有,这个时候被操作系统挂起,状态为阻塞状态。
     * 
     * 阻塞状态的线程,即使调用interrupt()方法也不会改变其状态。
     * 
     */
    BLOCKED,

    /**
     * 无条件等待,当线程调用wait()/join()/LockSupport.park()不加超时时间的方法之后所处的状态,如果没有被唤醒或等待的线程没有结束,
	 * 那么将一直等待,当前状态的线程不会被分配CPU资源和持有锁。
     * 
     */
    WAITING,

    /**
     * 有条件的等待,当线程调用sleep(睡眠时间)/wait(等待时间)/join(等待时间)/ LockSupport.parkNanos(等待时间)/LockSupport.parkUntil(等待时间)
	 * 方法之后所处的状态,在指定的时间没有被唤醒或者等待线程没有结束,会被系统自动唤醒,正常退出。
     * 
     */
    TIMED_WAITING,

    /**
	 * 终止线程的线程状态。
	 *
	 * 执行完了run()方法。线程已完成执行;其实这只是Java语言级别的一种状态,在操作系统内部可能已经注销了相应的线程,
	 * 或者将它复用给其他需要使用线程的请求,而在Java语言级别只是通过Java代码看到的线程状态而已。
     * 
     */
    TERMINATED;
}

各种状态之间切换图

image02

状态切换代码演示

NEW—>RUNNABLE

public class MyThread extends Thread {
    private String threadName;

    private MyThread(String threadName) {
        this.threadName = threadName;
    }

    private String getThreadName() {
        return threadName;
    }

    @Override
    public void run() {
        System.out.println("调用线程run方法-->开始");
        try {
            //业务逻辑处理
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // 主动中断线程
            Thread.currentThread().interrupt();
        }
        System.out.println("调用线程run方法-->结束");

    }

    public static void main(String[] args) {
        MyThread myThread01 = new MyThread("myThread01");
        System.out.println(myThread01.getThreadName() + "调用start()之前 state is : " + myThread01.getState());
        myThread01.start();
        System.out.println(myThread01.getThreadName() + "调用start()之后 state is : " + myThread01.getState());
    }
}

RUNNABLE—>BLOCKED

public class MyThread extends Thread {
    private String threadName;
    private final Object objectLock;

    private MyThread(String threadName, Object objectLock) {
        this.threadName = threadName;
        this.objectLock = objectLock;
    }


    private String getThreadName() {
        return threadName;
    }

    @Override
    public void run() {
        System.out.println("调用线程run方法-->开始");
        synchronized (this.objectLock) {
            System.out.println(this.threadName+"获取synchronized锁");
            try {
                //业务逻辑处理
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                // 主动中断线程
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("调用线程run方法-->结束");

    }

    public static void main(String[] args) throws InterruptedException {
        Object objectLock =new Object();
        MyThread myThread01 = new MyThread("myThread01",objectLock);
        MyThread myThread02 = new MyThread("myThread02",objectLock);
        myThread01.start();
        myThread02.start();
        System.out.println(myThread01.getThreadName() + "调用start()之后 state is : " + myThread01.getState());
        Thread.sleep(100);
        System.out.println(myThread02.getThreadName() + "调用start()之后 state is : " + myThread02.getState());
    }
}

RUNNABLE—>WAITING/TIMED_WAITING

public class MyThread extends Thread {
    private String threadName;
    private final Object objectLock;
    private String type;
    private List<String> products = new ArrayList<>();

    private MyThread(String threadName, Object objectLock, String type) {
        this.threadName = threadName;
        this.objectLock = objectLock;
        this.type = type;
    }


    private String getThreadName() {
        return threadName;
    }

    @Override
    public void run() {
        System.out.println(this.threadName + "调用线程run方法-->开始");
        synchronized (this.objectLock) {
            System.out.println(this.threadName + "获取objectLock的synchronized锁");
            //生产者
            if ("Producer".equals(this.type)) {
                this.products.add(this.threadName);
                System.out.println(this.threadName + "生产出一个物品,通知消费者消费");
                this.objectLock.notify();
                System.out.println(this.threadName + "成功通知了消费者");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // 主动中断线程
                    Thread.currentThread().interrupt();
                }
            }
            //消费者
            if ("Consumer".equals(this.type)) {
                try {
                    System.out.println(this.threadName + "检查不存在物品,等待生产者生产");
                    this.objectLock.wait();
                    System.out.println(this.threadName + "收到消费了一个物品");
                } catch (InterruptedException e) {
                    // 主动中断线程
                    Thread.currentThread().interrupt();
                }
            }
        }
        System.out.println(this.threadName + "调用线程run方法-->结束");

    }

    public static void main(String[] args) throws InterruptedException {
        Object objectLock = new Object();
        MyThread myThread01 = new MyThread("myThread01", objectLock, "Consumer");
        MyThread myThread02 = new MyThread("myThread02", objectLock, "Producer");
        myThread01.start();
        System.out.println(myThread01.getThreadName() + "调用start()之后 state is : " + myThread01.getState());
        Thread.sleep(100);
        System.out.println(myThread01.getThreadName() + "调用objectLock.wait()之后 state is : " + myThread01.getState());
        myThread02.start();
        System.out.println(myThread02.getThreadName() + "调用start()之后 state is : " + myThread02.getState());
        Thread.sleep(100);
        System.out.println(myThread02.getThreadName() + "调用objectLock.notify()之后 state is : " + myThread02.getState());
    }
}

注意:这是一个简单的生产者消费者模式;在这里wait/notify必须写在synchronized 保护的代码块中,在synchronized使用wait方法,而在执行 wait 方法之前,必须先持有对象的 monitor 锁,也就是通常所说的 synchronized 锁。原因就是为了保证代码先进行wait再进行notify;否则会发生死等

各种状态切换方法说明

wait/notify/notifyAll

wait方法分两种,一种无参的wait()一种有参的wait(timeout);一种是让线程进入WAITING;另一种是让线程进入TIMED_WAITING;带参的是让线程进入一个有效期的等待,超过有效期则会自动唤醒;无参的会让线程进入一个死等的状态。

这些方法只能由同一对象锁的持有者线程调用,也就是写在同步块里面,否则会抛出IllegalMonitorStateException异常。    * wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。    * notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。    注意: 虽然会wait自动解锁,但是对顺序有要求,如果在notify被调用之后,才开始wait方法的调用,则线程会永远处于waiting状态。 深入了解:

notify和notifyAll的区别

notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或者生产者,以判断程序是否可以继续往下执行。

notify的例子前面已经给过一个简单的生产者消费者模式;notifyAll的例子其实就是多增加一个消费者线程继续持有对象锁。然后进行全部唤醒

public class MyThread extends Thread {
    private String threadName;
    private final Object objectLock;
    private String type;
    private List<String> products = new ArrayList<>();

    private MyThread(String threadName, Object objectLock, String type) {
        this.threadName = threadName;
        this.objectLock = objectLock;
        this.type = type;
    }


    private String getThreadName() {
        return threadName;
    }

    @Override
    public void run() {
        System.out.println(this.threadName + "调用线程run方法-->开始");
        synchronized (this.objectLock) {
            System.out.println(this.threadName + "获取objectLock的synchronized锁");
            //生产者
            if ("Producer".equals(this.type)) {
                this.products.add(this.threadName);
                System.out.println(this.threadName + "生产出一个物品,通知消费者消费");
                this.objectLock.notifyAll();
                System.out.println(this.threadName + "成功通知了消费者");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // 主动中断线程
                    Thread.currentThread().interrupt();
                }
            }
            //消费者
            if ("Consumer".equals(this.type)) {
                try {
                    System.out.println(this.threadName + "检查不存在物品,等待生产者生产");
                    this.objectLock.wait();
                    System.out.println(this.threadName + "收到消费了一个物品");
                } catch (InterruptedException e) {
                    // 主动中断线程
                    Thread.currentThread().interrupt();
                }
            }
        }
        System.out.println(this.threadName + "调用线程run方法-->结束");

    }

    public static void main(String[] args) throws InterruptedException {
        Object objectLock = new Object();
        MyThread myThread01 = new MyThread("myThread01", objectLock, "Consumer");
        MyThread myThread03 = new MyThread("myThread03", objectLock, "Consumer");

        MyThread myThread02 = new MyThread("myThread02", objectLock, "Producer");
        myThread01.start();
        myThread03.start();
        System.out.println(myThread01.getThreadName() + "调用start()之后 state is : " + myThread01.getState());
        System.out.println(myThread03.getThreadName() + "调用start()之后 state is : " + myThread03.getState());
        Thread.sleep(100);
        System.out.println(myThread01.getThreadName() + "调用objectLock.wait()之后 state is : " + myThread01.getState());
        System.out.println(myThread03.getThreadName() + "调用objectLock.wait()之后 state is : " + myThread03.getState());
        myThread02.start();
        System.out.println(myThread02.getThreadName() + "调用start()之后 state is : " + myThread02.getState());
        Thread.sleep(100);
        System.out.println(myThread02.getThreadName() + "调用objectLock.notify()之后 state is : " + myThread02.getState());
    }
}

wait,notify来实现等待唤醒功能有两个缺点

为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?

主要有两点原因:

wait 和 sleep 方法的异同?

相同点

不同点

LockSupport

park()/unpark(thread) park()阻塞当前线程; unpark(thread)解锁指定thred线程;

public class MyThread extends Thread {
    private String threadName;

    private MyThread(String threadName) {
        this.threadName = threadName;
    }


    private String getThreadName() {
        return threadName;
    }

    @Override
    public void run() {
        System.out.println(this.threadName + "线程阻塞-->开始");

        LockSupport.park();

        System.out.println(this.threadName + "线程阻塞-->结束");

    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread01 = new MyThread("myThread01");
        myThread01.start();
        Thread.sleep(100);
        System.out.println(myThread01.getThreadName() + "线程唤醒-->开始");
        LockSupport.unpark(myThread01);
        System.out.println(myThread01.getThreadName() + "线程唤醒-->结束");

    }
}
// 程序输出
myThread01线程阻塞-->开始
myThread01线程唤醒-->开始
myThread01线程唤醒-->结束
myThread01线程阻塞-->结束

下面我们看一下如果唤醒unpark先于阻塞park的情况

public class MyThread extends Thread {
    private String threadName;

    private MyThread(String threadName) {
        this.threadName = threadName;
    }


    private String getThreadName() {
        return threadName;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println(this.threadName + "线程阻塞-->开始");

        LockSupport.park();

        System.out.println(this.threadName + "线程阻塞-->结束");

    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread01 = new MyThread("myThread01");
        myThread01.start();
        Thread.sleep(100);
        System.out.println(myThread01.getThreadName() + "线程唤醒-->开始");
        LockSupport.unpark(myThread01);
        System.out.println(myThread01.getThreadName() + "线程唤醒-->结束");

    }
}
//程序输出
myThread01线程唤醒-->开始
myThread01线程唤醒-->结束
myThread01线程阻塞-->开始
myThread01线程阻塞-->结束

我们可以看出先唤醒指定线程,然后阻塞该线程,线程并没有真正被阻塞并且可以正常执行完后退出。

我们下面我们看一下如果唤醒unpark两次都先于阻塞park两次的情况

public class MyThread extends Thread {
    private String threadName;

    private MyThread(String threadName) {
        this.threadName = threadName;
    }


    private String getThreadName() {
        return threadName;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        for(int i=0;i<2;i++) {
            System.out.println(this.threadName + "线程阻塞-->开始");

            LockSupport.park();

            System.out.println(this.threadName + "线程阻塞-->结束");
        }

    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread01 = new MyThread("myThread01");
        myThread01.start();
        Thread.sleep(100);
        for(int i=0;i<2;i++) {
            System.out.println(myThread01.getThreadName() + "线程唤醒-->开始");
            LockSupport.unpark(myThread01);
            System.out.println(myThread01.getThreadName() + "线程唤醒-->结束");
        }

    }
}
//程序输出
myThread01线程唤醒-->开始
myThread01线程唤醒-->结束
myThread01线程唤醒-->开始
myThread01线程唤醒-->结束
myThread01线程阻塞-->开始
myThread01线程阻塞-->结束
myThread01线程阻塞-->开始
并且程序一直处于阻塞状态

由此我们可以看出:先唤醒线程,在阻塞线程,线程不会真的阻塞;但是先唤醒线程两次再阻塞两次时就会导致线程真的阻塞。

park()/unpark(thread)线程阻塞唤醒机制

LockSupport是通过控制一个变量_counter来对线程阻塞唤醒进行控制的。

所以就可以得出: 为什么可以先唤醒线程后阻塞线程?

因为:先unpark(thread)唤醒线程,_counter=1;当park()阻塞线程,_counter_old>=1则不进行阻塞,直接退出; 为什么唤醒两次后阻塞两次会阻塞线程? 因为:先连续unpark(thread)唤醒线程两次(不管多少次),_counter=1;后面连续两次park()阻塞线程,第一次判断_counter_old>=1则不进行阻塞,直接退出;后面会直接设置_counter=0;第二次判断_counter_old>=1就不满足,所以会进行线程阻塞。

总结:LockSupport是JDK中用来实现线程阻塞和唤醒的工具。使用它可以在任何场合使线程阻塞,可以指定任何线程进行唤醒,并且不用担心阻塞和唤醒操作的顺序,但要注意连续多次唤醒的效果和一次唤醒是一样的。LockSupport真实的实现均在 unsafe

parkNanos(nanos)/parkUntil(deadline)

在Java6中,LockSupport增加了3个方法:

public static void park(Object blocker)
public static void parkNanos(Object blocker, long nanos)
public static void parkUntil(Object blocker, long deadline)
参数
			blocker用来标识当前线程正在等待的对象(即阻塞对象)主要用于排查问题和系统监控
	
		在线程dump时带blocker参数的park()方法比不带blocker参数的park()方法多出以下内容(即指出了阻塞对象的类型)
			- parking to wait for <0x000...>  com.jxn.test.TestLockSupport
			
		说明
			1)当线程(因使用synchronized关键字)阻塞在一个对象上时通过线程dump能够查看到该线程的阻塞对象从而可以方便地定位问题
			2)使用LockSupport中不带blocker参数的park()方法来阻塞对象时通过线程dump无法看到该线程的阻塞对象故在java6中提供了带blocker参数的park()方法来解决这一问题
join

join方法是实现线程同步,可以将原本并行执行的多线程方法变成串行执行的;

join方法使用在线程start方法之后。放在start方法之前基本无作用,但是不会报错;

join也有两个方法,一个是无参的join()另一个是有参的join(millis);无参的表示线程使用join方法之后在执行完成后才执行下一个线程。有参的表示线程使用join方法之后在执行指定时间之后才执行下一个线程。如果线程在指定时间之内执行完,则线程执行完毕之后继续下一个线程无需等待指定时间;如果线程在指定时间之内未执行完,当时间到期时就执行下一个线程。 示例

public class MyThread extends Thread {
    private String threadName;

    private MyThread(String threadName) {
        this.threadName = threadName;
    }


    private String getThreadName() {
        return threadName;
    }

    @Override
    public void run() {
        System.out.println(this.threadName + "调用线程run方法-->开始");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // 主动中断线程
            Thread.currentThread().interrupt();
        }

        System.out.println(this.threadName + "调用线程run方法-->结束");

    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread01 = new MyThread("myThread01");
        MyThread myThread02 = new MyThread("myThread02");
        myThread01.start();
        myThread01.join();
        myThread02.start();
        System.out.println(myThread01.getThreadName() + "调用join()之后 state is : " + myThread01.getState());
        System.out.println(myThread02.getThreadName() + "调用start()之后 state is : " + myThread02.getState());

    }
}
interrupt

interrupt()方法: 作用是中断线程。将会设置该线程的中断状态位,即设置为true,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程 

interrupt()方法只是改变中断状态,不会中断一个正在运行的线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出interruptedException的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。这一方法实际完成的是,给受阻塞的线程发出一个中断信号,这样受阻线程检查到中断标识,就得以退出阻塞的状态。

更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用 interrupt()将不起作用,直到执行到wait(),sleep(),join()时,才马上会抛出 InterruptedException。也可以通过thread.isInterrupted()来获取线程的中断状态。 示例

public class MyThread extends Thread {
    private String threadName;

    private MyThread(String threadName) {
        this.threadName = threadName;
    }


    private String getThreadName() {
        return threadName;
    }

    @Override
    public void run() {
        System.out.println(this.threadName + "调用线程run方法-->开始");
        int i = 0;
        while (true) {
            i++;
            if (i > 100000000) {
                break;
            }
        }
        try {
            System.out.println(this.threadName + " i=" + i);
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // 主动中断线程
            System.out.println(this.threadName + " 线程中断");
            Thread.currentThread().interrupt();
        }

        System.out.println(this.threadName + "调用线程run方法-->结束");

    }

    public static void main(String[] args) {
        MyThread myThread01 = new MyThread("myThread01");
        myThread01.start();
        myThread01.interrupt();
        System.out.println(myThread01.getThreadName() + "调用interrupt()之后 state is : " + myThread01.getState());
    }
}
yield()
public static native void yield();

这个方法是一个静态本地方法,是“让步”的意思,主要是让出CPU资源;一旦执行,它会使当前线程让出CPU,即由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权。但是要注意的是让出CPU并不代表当前线程不执行了。当前线程让出CPU后,还是会进行CPU资源的争夺,但是能不能再次被分配到,就不一定了。因此yeild()方法的调用好像就是在说:我已经完成一些最终要的工作了,应该可以休息一下了,可以给其他线程一些工作机会了!

如果你觉得一个线程不那么重要,或者优先级比较低,但是又不想它占用过多的CPU资源,就可以在适当的时候使用Thread.yield(),给予其他重要线程更多的机会。

守护线程

java中线程主要分为两大类:一种是用户线程(这个是我们平常开发中用到的);另一个就是守护线程,守护线程我们可以简单的认为就是为了守护用户线程的,当然功能远远不止于此;JVM最典型的守护线程就是GC(垃圾回收器)

在Thread类里面提供有如下的守护线程的操作方法:

注意: 1、thread.setDaemon(true)必须在线程启动之前,即thread.start()方法之前设置,否则会跑出一个IllegalThreadStateException异常。因为正在运行的线程我们不能改变它的性质 2、在守护线程中新建其它线程也是守护线程 3、守护线程会随着所有用户线程的用户线程的结束而结束;用户线程结束那么JVM也会停止工作,即使守护线程正在工作也会直接结束,所以有些重要的必须完成的逻辑处理不应该放入守护线程

示例

public class MyThread extends Thread {
    private String threadName;

    private MyThread(String threadName) {
        this.threadName = threadName;
    }

    public static void main(String[] args) {
        MyThread myThread01 = new MyThread("myThread01");
        myThread01.setDaemon(true);
        myThread01.start();
        System.out.println("用户线程结束");

    }

    private String getThreadName() {
        return threadName;
    }

    @Override
    public void run() {
        try {
            //守护线程阻塞2秒后运行
            Thread.sleep(2000);
            File f = new File("C:/Users/yeming.gao/Desktop/test.txt");
            FileOutputStream os = new FileOutputStream(f, true);
            os.write("test".getBytes());
            os.close();
            System.out.println(this.threadName + "守护线程所有业务处理结束");
        } catch (IOException | InterruptedException e1) {
            Thread.currentThread().interrupt();
        }
    }
}
//当我们没有设置为守护线程的时候程序运行的结果为:(并且指定路径会出现test.txt文件说明myThread01线程执行完成)
用户线程结束
myThread01守护线程所有业务处理结束
//当我们设置为守护线程程序运行结果为:(并且指定路径没有出现文件,说明myThread01没有执行完成就已经结束)
用户线程结束

所以我们可以理解为守护线程的目的是为了守护用户线程,当用户线程结束之后,守护线程就没有存在的必要,所以守护线程也就可以直接结束;虽然守护线程实际的应用不多,但是针对自己的业务情况或者做一些监控什么的还是可以用得到的。

Java的内存模型

image03

Java内存模型-操作规范:

(当涉及共享变量的时候这个时候就会引出线程安全的问题)

Java内存模型-同步交互协议,规定了8种原子操作:

锁机制有如下两种特性

锁优化方法

锁这块也只是先了解这些概念,后续再学习更新