线程是什么?
线程的本质一个轻量级的过程,它是代码序列以及所有数据支持结构(例如,打开的资源,内存映射,堆栈等)的执行。如果要并行运行代码,将使编程变得容易。它受益于多CPU的体系结构,它还可以运行多个进程或在一个进程中运行多个线程。
Java中的线程
线程状态
我们看到 Java 源代码里面,线程状态的枚举有如下 6 个:
public enum State { /** * Thread state for a thread which has not yet started. */ NEW, /** * Thread state for a runnable thread. */ RUNABLE, /** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock */ BLOCKED, /** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: *
* {@link Object#wait() Object.wait} with no timeout * {@link #join() Thread.join} with no timeout * {@link LockSupport#park() LockSupport.park} * */ WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: *
* {@link #sleep Thread.sleep} * {@link Object#wait(long) Object.wait} with timeout * {@link #join(long) Thread.join} with timeout * {@link LockSupport#parkNanos LockSupport.parkNanos} * {@link LockSupport#parkUntil LockSupport.parkUntil} * */ TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED; }
下面给这 6 个状态一一做下解释。
NEW:新建状态。在创建完 Thread ,还没执行 start() 之前,线程的状态一直是 NEW。
RUNNABLE:运行状态。线程对象调用 start() 之后,就进入 RUNNABLE 状态,对应操作环境中的RUNNABLE和RUNNING两种状态。
BLOCKED:阻塞状态。线程在等待获取对象锁,线程处于“Entry Set”区域。
WAITING:等待状态。线程在这个状态的时候,不会被分配 CPU,而且需要被显示地唤醒,否则会一直等待下去。
TIMED_WAITING:超时等待状态。这个状态的线程也一样不会被分配 CPU,但是它不会无限等待下去,有时间限制,时间一到就停止等待。
TERMINATED:终止状态。线程执行完成结束,但不代表这个对象已经没有了,对象可能还是存在的,只是线程不存在了。
线程既然有这么多个状态,那肯定就有状态机,也就是在什么情况下 A 状态会变成 B 状态。下面就来简单描述一下。
结合下图,我们 new 出线程类的时候,就是 NEW 状态,调用 start() 方法,就进入了 RUNNABLE 状态,这时如果触发等待,则进入了 WAITING 状态,如果触发超时等待,则进入 TIMED_WAITING 状态,当访问需要同步的资源时,则只有一个线程能访问,其他线程就进入 BLOCKED 状态,当线程执行完后,进入 TERMINATED 状态。
1、Java 中实现多线程有几种方法
(1)继承 Thread 类;
(2)实现 Runnable 接口;
(3)实现 Callable 接口通过 FutureTask 包装器来创建 Thread 线程;
(4)使用 ExecutorService、Callable、Future 实现有返回结果的多线程(也就是使用了 ExecutorService 来管理前面的三种方式)。
2、sleep()、wait()、join()、yield()、interrupt()方法功效
1) sleep()
线程中的方法,不会释放lock,休眠时间到之后自动退出Blocked。代码如下:
public class TestSleep { private static void test() { try { System.out.println(Thread.currentThread().getName() + "正在执行"); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "休眠结束"); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Stream.of("线程1", "线程2").forEach(s -> new Thread(s) { public void run() { TestSleep.test(); } }.start()); }}
运行结果如下:
2) wait()
Object中的方法,会释放lock,需要notify之后才会退出Blocked。我们用存钱、取钱的代码举例:
public class TestWait { private static Object lock = new Object(); static int amount = 100; // 账户初始100元 public static void deposit(int cash) { // 存钱 synchronized (lock) { amount += cash; lock.notify(); try { Thread.sleep(30000); // 通知后却暂时不退出 } catch (InterruptedException e) { throw new RuntimeException(e); } } } public static void withdraw(int cash) { // 取钱 synchronized (lock) { while (cash > amount) { try { lock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } amount -= cash; } } public static void main(String[] args) throws Exception { Thread threadWithdraw = new Thread("取钱线程") { @Override public void run() { com.kanq.TestSleep.withdraw(200); } }; threadWithdraw.start(); Thread.sleep(1000); //确保取钱线程已经得到执行 System.out.println(threadWithdraw.getState()); Thread threadDeposit = new Thread("存钱线程") { @Override public void run() { com.kanq.TestSleep.deposit(100); } }; threadDeposit.start(); Thread.sleep(1000);//确保存钱线程已经得到执行 System.out.println(threadWithdraw.getState()); }}
运行结果如下:
取钱线程收到通知后,退出 WAITING 状态,但已经不持有锁,当试图重新进入(reenter)同步块以恢复执行时,因锁尚未被存钱线程释放,于是被阻塞(BLOCKED 状态)
3) join()
主线程会等待子线程执行结束,再继续执行。底层用wait实现。不会影响已经在执行的其他线程。
public class TestJoin { public static void deposit(int cash) { // 存钱 try { Thread.sleep(3000); // 通知后却暂时不退出 System.out.println(Thread.currentThread().getName() + "休眠結束"); } catch (InterruptedException e) { throw new RuntimeException(e); } } public static void main(String[] args) throws Exception { Thread threadDeposit = new Thread("存钱线程") { @Override public void run() { TestJoin.deposit(100); } }; threadDeposit.start(); threadDeposit.join(); System.out.println(Thread.currentThread().getName() + "执行了"); }}
运行结果如下:
4) yield()
放弃当前线程获取CPU的执行权,使当前线程重新回到可执行状态。执行yield()的线程有可能在进入到可执行状态后马上又被执行。
public class TestYield { public static void deposit(){ // 存钱 for (int i = 1; i <= 10; i++) { if (i==3) { Thread.yield(); } System.out.println("" + Thread.currentThread().getName() + "-----" + i); } } public static void main(String[] args){ new Thread("线程1") { @Override public void run() { TestYield.deposit(); } }.start(); new Thread("线程2") { @Override public void run() { TestYield.deposit(); } }.start(); }}
执行结果如下:
5) interrupt()
改变线程的中断状态,至于这个中断状态改变后带来的结果,无法确定。有时它更是让停止中的线程继续执行的唯一手段。不但不是让线程停止运行,反而是继续执行线程的手段。它存在的意义:我们可以根据中断标志的具体值,来决定线程下一步执行什么。
在一个线程对象上调用interrupt()方法,真正有影响的是wait,join,sleep方法。其他状态的线程,执行过后,并没有什么影响。
public class TestInterrupte { public static void deposit() { // 存钱 try { Thread.sleep(3000); // 通知后却暂时不退出 System.out.println(Thread.currentThread().getName() + "休眠結束"); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + "被中断了"); throw new RuntimeException(e); } } public static void main(String[] args) throws Exception { Thread threadDeposit = new Thread("存钱线程") { @Override public void run() { TestInterrupte.deposit(); } }; threadDeposit.start(); threadDeposit.interrupt(); }}
执行结果如下: