Java线程在Java中是一个极为重要的部分,任何功能的实现,都离不开线程的支持。

线程的状态

  • NEW
    初始状态,线程被构建,但是还没有调用start()方法

  • RUNNABLE
    运行状态,Java线程将操作系统中的就绪和运行两种状态笼统的称为"运行中"

  • BLOCKED
    阻塞状态,表示线程阻塞于锁

  • WAITING
    等待状态,表示线程进入等待状态,进入该状态表示该线程需要等待其他线程做出一些特定动作(通知或中断)

  • TIME_WAITING
    超时等待,和WAITING状态略微不同,它是指可以在特定时间自行返回

  • TERMINATED
    终止状态,表示当前线程已经执行完毕

  • 线程状态转换图:
    163159b8a740b329.png
    线程创建之后它将处于 NEW(新建) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态的线程获得了 cpu 时间片(timeslice)后就处于 RUNNING(运行) 状态,当线程执行 wait()方法之后,线程进入 WAITING(等待) 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞) 状态。线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止) 状态。

优先级

  • 线程优先级取值
    Java线程优先级使用1-10整数表示:
    1.最低优先级1:Thread.MIN_PRIORITY
    2.最高优先级10:Thread.MAX_PRIORITY
    3.默认优先级5:Thread.NORM_PRIORITY
  • 线程优先级设置
    Java 使用 setPriority 方法设置线程优先级,方法签名
    public final void setPriority(int newPriority)

线程调度

线程调度是指系统为线程分配处理器使用权的过程。

  • 协同式线程调度
    协同式线程调度,线程的执行时间由线程本身控制。 协同式线程调度,线程执行时间由线程本身来控制,线程把自己的工作执行完之后,要主动通知系统切换到另外一个线程上。
    优点:实现简单,且切换操作对线程自己是可知的,没啥线程同步问题。
    缺点:线程执行时间不可控制,如果一个线程有问题,可能一直阻塞在那里。
  • 抢占式线程调度
    抢占式调度,每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定
    Java中,Thread.yield()可以让出执行时间,但无法获取执行时间。
    优点:线程执行时间系统可控,也不会有一个线程导致整个进程阻塞。

创建线程的多种方式

  • 继承Thread类
public class MyThread extends Thread {
	@Override
	public void run() {
		super.run();
		System.out.println("Test");
	}
}
  • 实现Runnable接口(常用)
public class MyRunnable implements Runnable {
	@Override
	public void run() {
		System.out.println("Test");
	}
}
  • 匿名内部类的方式创建线程
public class CreateThreadDemo6_Anonymous {

    public static void main(String[] args) {
        // 基于子类的方式
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("Test");
                }
            }
        }.start();

        // 基于接口的实现
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("Test");
                }
            }
        }).start();
    }
}
  • 带返回值的线程实现方式
public class CreateThreadDemo11_Callable {

    public static void main(String[] args) throws Exception {

        // 创建线程任务
        Callable<Integer> call = () -> {
            System.out.println("线程任务开始执行了....");
            Thread.sleep(2000);
            return 1;
        };

        // 将任务封装为FutureTask
        FutureTask<Integer> task = new FutureTask<>(call);

        // 开启线程,执行线程任务
        new Thread(task).start();

        // ====================
        // 这里是在线程启动之后,线程结果返回之前
        System.out.println("这里可以为所欲为....");
        // ====================

        // 为所欲为完毕之后,拿到线程的执行结果
        Integer result = task.get();
        System.out.println("主线程中拿到异步任务执行的结果为:" + result);
    }
}
  • 基于线程池的方式
public class CreateThreadDemo12_ThreadPool {

    public static void main(String[] args) throws Exception {

        // 创建固定大小的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(10);

        while (true) {
            // 提交多个线程任务,并执行
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Test");
                }
            });
        }
    }
}

守护线程

  • 什么是守护线程
    用来服务于用户线程;不需要上层逻辑介入。
    通过一个栗子来区分一下它们与JVM的关系。
class DaemonRunner implements Runnable {
   @Override
   public void run() {
       while (true) {
           for (int i = 1; i <= 100; i++) {
               System.out.println("daemon thread:"+i);
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }
   }
}

Thread daemonThread = new Thread(new DaemonRunner());
daemonThread.setDaemon(true);
daemonThread.start();
System.out.println("isDaemon? = " + daemonThread.isDaemon());
Scanner scanner = new Scanner(System.in);
scanner.next();
Runtime.getRuntime().addShutdownHook(new Thread() {
   @Override
   public void run() {
       System.out.println("JVM Exit!");
   }
});

我们分析结果,可以得出结论:当线程只剩下守护线程的时候,JVM就会退出;补充一点如果还有其他的任意一个用户线程还在,JVM就不会退出。

  • 使用场景
    当主线程结束时,结束其余的子线程(守护线程)自动关闭,就免去了还要继续关闭子线程的麻烦。如:Java垃圾回收线程就是一个典型的守护线程;内存资源或者线程的管理,但是非守护线程也可以。
  • 使用注意
    1.thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
    2.在Daemon线程中产生的新线程也是Daemon的。
    3.守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。
    4.Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线程就不能用Java的线程池。

线程与进程的区别

  • 线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
  • 程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
  • 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。