1 线程的生命周期
- 在JDK中用Thread.State枚举定义了线程的几种状态。


public enum State { /** * Thread state for a thread which has not yet started. */ NEW, /** * Thread state for a runnable thread. A thread in the runnable * state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system * such as processor. */ RUNNABLE, /** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. */ BLOCKED, /** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * <ul> * <li>{@link Object#wait() Object.wait} with no timeout</li> * <li>{@link #join() Thread.join} with no timeout</li> * <li>{@link LockSupport#park() LockSupport.park}</li> * </ul> * * <p>A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called <tt>Object.wait()</tt> * on an object is waiting for another thread to call * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on * that object. A thread that has called <tt>Thread.join()</tt> * is waiting for a specified thread to terminate. */ 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: * <ul> * <li>{@link #sleep Thread.sleep}</li> * <li>{@link Object#wait(long) Object.wait} with timeout</li> * <li>{@link #join(long) Thread.join} with timeout</li> * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> * </ul> */ TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED; }
- 要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
- 新建:当一个Thread类及其子类的对象被声明并创建的时候,新生的线程对象处于新创建状态。
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已经具备了运行的条件,只是没有分配到CPU的资源。
- 运行:当就绪的线程被调度并获取CPU的资源的时候,就进入运行状态,run()方法中定义了线程的操作和功能。
- 阻塞:在某种特殊的情况下,被认为挂起或执行输入输出操作的时候,让CPU临时中止自己的执行,进入阻塞状态。
- 死亡:线程完成它的全部工作或线程被提前强制性中止或出现异常导致结束。
2 线程的同步
2.1 线程安全问题出现的原因
- 多线程环境。
- 多个线程操作共享数据(当多条语句在操作同一线程共享数据的时候,一个线程对多条语句执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误)。
2.2 线程安全问题的解决之道
- 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他的线程不可以参与执行(加锁)。
2.3 Java中解决线程安全问题
- Java对于多线程的安全问题体用了专业的解决方式:同步机制。
- 同步代码块:
synchronized(对象){ //需要被同步的代码 }
- 同步方法:synchronized还可以放在方法的声明处,表示整个方法为同步方法。
public synchronized void show(){ //需要被同步的代码 }
2.4 同步机制中的锁
2.4.1 同步锁机制
- 在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。防止这种冲突的方法就是当资源被一个任务使用的时候,在其上加上锁。第一个访问某项资源的任务必须锁定这项资源,使得其他任务在其解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。
2.4.2 synchronized的锁是什么?
- 任何对象都可以作为同步锁。所有对象都自动含有单一的锁。
- 同步方法的锁:静态方法(类名.class)、非静态方法(this)。
- 同步代码块的锁:自己指定,很多时候呀也是指定this或类名.class。
2.4.3 注意
- 必须确保使用同一资源的多个线程共用同一把锁,这个非常重要,否则就无法保证共享资源的安全。
- 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块指定需要谨慎。
2.5 解决线程安全的应用示例
- 示例:
package day18; public class SellTicket implements Runnable { private int num = 100; @Override public void run() { for (; ; ) { synchronized (this) { if (num > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "当前的票数是:" + num--); } else { return; } } } } }
package day18; public class ThreadTest { public static void main(String[] args) { SellTicket sellTicket = new SellTicket(); Thread t1 = new Thread(sellTicket); Thread t2 = new Thread(sellTicket); t1.start(); t2.start(); } }
2.6 线程安全的单例模式之懒汉式
- 示例:
package day18; public class Singleton { private static Singleton singleton; private Singleton() { } public static synchronized Singleton getInstance() { if (null == singleton) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } return singleton = new Singleton(); } return singleton; } }
package day18; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadTest { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(50); for (int i = 0; i < 10; i++) { executorService.execute(new Runnable() { @Override public void run() { Singleton singleton = Singleton.getInstance(); System.out.println(singleton); } }); } } }
3 线程的通信
3.1 线程通信的引入
- 示例:使用两个线程交替打印1到100,线程1和线程2交替执行。
package day18; public class Number implements Runnable { private int num = 1; @Override public void run() { while (true) { synchronized (this) { this.notifyAll(); if (num <= 100) { System.out.println(Thread.currentThread().getName() + ":" + num); num++; try { //使得线程进入阻塞状态 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } } }
package day18; public class ThreadTest { public static void main(String[] args) { Number number = new Number(); Thread t1 = new Thread(number); t1.setName("线程1"); Thread t2 = new Thread(number); t2.setName("线程2"); t1.start(); t2.start(); } }
3.2 线程通信的方法
- wait()方法:令当前线程挂起并放弃CPU的执行权以及同步资源等等,使得别的线程可以访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyall()方法唤醒后,唤醒后等待重新获得对CPU的执行权才能继续执行。
- notify()方法:唤醒正在排队等待同步资源的线程中优先级最高者结束等待。
- notifyall()方法:唤醒正在排队等待资源的所有线程结束等待。
- 这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会抛出java.lang.IllegalMonitorStateException异常。因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法定义在Object类中。
3.3 sleep()和wait()方法的异同
- 相同点:一旦执行方法,都可以使得当前线程进入阻塞状态。
- 不同点:
- 两个方法声明的位置不同。Thread类中声明sleep()方法,Object类中声明wait()方法。
- 调用的要求不同:sleep()方法可以在任何需要的场景下调用。wait()必须在同步代码块或同步方法中调用。
- 关于释放同步监视器(锁):如果两个方法都使用在同步代码块或同步方法中,sleep()方法不会释放锁,wait()方法会释放锁。
4 创建线程的方式三
4.1 实现Callable接口
- 和使用Runnale相比,Callable功能更强大些:
- ①相比run()方法,可以有返回值。
- ②方法可以抛出异常。
- ③支持泛型的返回值。
- ④需要借助FutureTask类,比如获取返回结果。
- Future接口:
- ①可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
- ②FutureTask是Future接口的唯一的实现类。
- ③FutureTask同时实现了Runnable、Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
4.2 使用实现Callable接口创建线程
- 示例:
package day18; import java.util.concurrent.Callable; public class Number implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < 100; i++) { sum += i; } return sum; } }
package day18; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class ThreadTest { public static void main(String[] args) { Number number = new Number(); FutureTask<Integer> futureTask = new FutureTask<>(number); new Thread(futureTask).start(); try { Integer sum = futureTask.get(); System.out.println("sum:" + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
5 创建线程的方式四(使用线程池)
5.1 背景
- 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
5.2 思路
- 提前创建好多个线程,放入线程池中,使用的时候直接获取,使用完放回池中。可以避免频繁的创建销毁,实现重复利用。
5.3 好处
- 提高响应速度(减少了创建新线程的时间)。
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)。
- 便于线程管理(可以设置线程池参数)。
5.4 线程池相关API
- JDK5.0提供了线程池的相关API:ExecutorService和Executors。
- ExecutorService:真正的线程池接口。常见的子类ThreadPoolExecutor。
void execute(Runnable command);//执行任务、命令,没有返回值吗,一般用来执行Runnable
<T> Future<T> submit(Callable<T> task);//执行任务,有返回值,一般用来执行Callable
void shutdown();//关闭连接池
- Executors:工具类、线程池的工厂类,用来创建并返回不同类型的线程池。
public static ExecutorService newCachedThreadPool();//创建一个可根据需要创建新线程的线程池
public static ExecutorService newFixedThreadPool(int nThreads);//创建一个可重用固定线程数的线程池
public static ExecutorService newSingleThreadExecutor();//创建一个只有一个线程的线程池
public static ScheduledExecutorService newSingleThreadScheduledExecutor();//创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
5.5 使用线程池创建线程
- 示例:
package day18; import java.util.concurrent.*; public class ThreadTest { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(50); Future<Integer> future = executorService.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < 100; i++) { sum += i; } return sum; } }); new Thread((FutureTask)future).start(); Integer sum = future.get(); System.out.println("sum:" + sum); executorService.shutdown(); } }