Java多线程基础知识(二)
一. Java线程具有6种状态
NEW | 初始状态,线程被创建,但是还没有调用start方法。 |
RUNNABLE | 运行状态,java线程将操作系统中的就绪和运行两种状态笼统的称作进行中。 |
BLOCKED | 阻塞状态,表示线程阻塞于锁。 |
WAITING | 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些其它动作 |
TIME_WAITING | 超时等待状态,该状态不同于WAITING,它是可以在指定的时间自动返回的 |
TERMINATED | 终止状态,表示当前线程已经执行完毕 |
二. 线程的优先级
线程通过一个整形变量priority来控制修改优先级,优先级的范围从1-10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认的优先级是5,理论上优先级别高的线程分配的时间片会多,在不同的JVM以及操作系统上,线程的规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。
线程优先级不能作为程序正确性的依赖,因为操作系统可以完全不用理会java线程优先级的设定。
三. Daemon线程
Daemon线程是一种支持型的线程,因为它主要被用作程序中后台调度以及支持性工作。
Daemon线程需要在启动线程之前设定,不能在启动之后设定。
在创建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。
package com.bochao.concurrency; public class Daemon { static class DaemonRunner implements Runnable { @Override public void run() { try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 在创建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。 System.out.println("DaemonThread finally run."); } } } public static void main(String[] args) { Thread thread = new Thread(new DaemonRunner(), "DaemonRunner"); //thread.setDaemon(true); // 守护线程 thread.start(); } }
四. 线程启动和终止
线程运行:线程在初始化完成后,调用start()方法就可以启动这个线程。(启动一个线程前,最好为这个线程设定一个线程名称,在使用jstack分析程序时会有帮助)
线程中断:中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作,其它线程通过调用interrupt()方法对其进行中断操作。线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。
五. 过期suspend, 恢复resume,停止stop
这三个方法分别可以对线程进行暂停、恢复、停止,但是API是过期的,也就是不建议使用。也就是这些方法存在着一定的弊端。
六. 安全的终止线程
Java中一种比较安全和优雅的方式使用一个boolean值来终止一个线程运行。
package com.bochao.concurrency; import java.util.concurrent.TimeUnit; public class Shutdown { private static class Runner implements Runnable{ private long i; private volatile boolean on = true; @Override public void run() { while(on && !Thread.currentThread().isInterrupted()){ i++; } System.out.println("Count i = " + i); } public void cancle(){ on = false; } } public static void main(String[] args) { Runner runner = new Runner(); Thread countThread = new Thread(runner, "CountThread"); countThread.start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } countThread.interrupt(); Runner runner2 = new Runner(); Thread countThread2 = new Thread(runner2, "CountThread"); countThread2.start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } runner2.cancle(); } }
七. 线程间通信
线程拥有自己的堆栈,线程间的通信机制,为java多线程带来了巨大的价值。
volatile和synchronized关键字
volatile关键字:可以用来修改字段或者变量,就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,他能保证所有线程对变量访问的可见性。但是过多的使用会造成程序执行效率低下。
synchronized关键字:可以修饰方法或以同步快的形式使用,它主要是保证多个线程在同一时刻,只能有一个线程处于方法或同步快中,它保证了线程对变量访问的可见性和排它性。
八. 等待和通知机制
wait, notify, notifyAll 等待, 通知,通知全部
等待和通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的nofity()或者notifyAll()方法,线程A收到通知后从对象的wait()方法返回,进而执行后续操作。
package com.bochao.concurrent; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; public class WaitNofity { private static boolean keepWait = true; private static Object lock = new Object(); // 等待 static class Wait implements Runnable{ @Override public void run() { // 上锁,获得lock的Monitor synchronized (lock) { // 判断条件 while(keepWait){ System.out.println(Thread.currentThread() + " keepWait is true. wait@" + " " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 条件满足,开始工作 System.out.println(Thread.currentThread() +" keepWait is false. running@" + " " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } } } // 通知 static class Notify implements Runnable{ @Override public void run() { // 上锁,获得lock的Monitor synchronized (lock) { // 获得lock的锁,然后进行通知,通知(notifyAll()后)不会立即施放lock的锁, // 直到当前线程施放lock,WaitThead才能从wait方法中返回 System.out.println(Thread.currentThread() + " hold lock. notify @ " + new SimpleDateFormat("HH:mm:ss").format(new Date())); lock.notifyAll(); keepWait = false; try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } // 再次加锁 synchronized (lock) { System.out.println(Thread.currentThread() + " hold lock again. sleep @ " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { // 等待 Thread waitThread = new Thread(new Wait(), "WaitThead"); waitThread.start(); TimeUnit.SECONDS.sleep(1); // 通知 Thread notifyThread = new Thread(new Notify(), "NotifyThread"); notifyThread.start(); } }
注意:
1. 使用wait, nofity, notifyAll需要先对调用对象加锁。
2. 调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。
3. notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifyAll()的线程施放锁之后,等待线程才有机会从wait()返回。
4. notify()方法将等待队列中一个等待的线程从等待队列中移到同步队列中,而notifyAll()方法是将等待队列中的所有线程全部移动到同步队列,被移动的线程从WAITING变为BLOCKED。
5. 从wait()方法返回的前提是获得了调用对象的锁。
等待通知经典范式
即生产者和消费者模式
1. 消费者遵循如下原则:
(1). 获取对象的锁
(2). 如果条件不满足,那么调用对象的wait()方法,被通知方仍要检查条件
(3). 条件满足则执行对象的逻辑
synchronized(对象){ while(条件不满足){ 对象.wait(); } // 对应的处理逻辑 }
2. 生产者遵循如下原则:
(1). 获得对象的锁
(2). 改变条件
(3). 通知所有等待在对象上的线程
synchronized(对象){ 改变条件 对象.notifyAll(); }