玩转java并发-从创建线程开始

创建并启动线程

  1. 创建一个线程,并重写其run()方法
  2. 启动新的线程,调用Thread的start方法,才代表派生了一个新的线程,否则Thread和其他的java对象没什么区别,start()是一个立即返回的方法,不会让线程陷入阻塞。

注意:当启动JVM时,从操作系统就会创建一个新的进程(JVM进程),JVM进程会派生或者创建其他线程,其中main方法就是主线程。JVM启动后,实际上由多个线程,至少有一个非守护线程。

	public static void main(String[] args) {
		
		//内部类
		Thread t1 = new Thread() {
			@Override
			public void run() {
				for(int i=0;i<100;i++) {
					System.out.println("thread:"+i);
				}
			}
		};
		
		t1.start();
		
		for(int i=0;i<100;i++) {
			System.out.println("main:"+i);
		}
	}
	//程序结果:交叉运行

线程生命周期

玩转java并发-从创建线程开始

  1. NEW
  2. RUNNABLE
  3. RUNNING
  4. BLOCKED
  5. TERMINATED

线程的NEW状态

当我们new一个Thread时,只是创建了一个Thread对象。在没有调用start()之前,该线程根本不存在。这个Thread对象和普通的java对象没什么区别

线程的RUNNABLE状态

线程对象进入RUNNABLE状态必须调用start()方法,此时才是真正地在JVM创建了一个线程,线程一经启动就可以立即执行吗?答案是否定的。线程的运行与否需要听从CPU的调度,这个中间状态叫做可执行状态。此时具备可执行的条件,但是在等待CPU的调度。由于存在RUNNING状态,处于RUNNABLE状态的线程不会直接进入BLOCKED状态和TERMINATED状态。即使在线程的执行逻辑中调用了wait,sleep或者其他的block的IO操作,也必须先取得CPU的执行权。RUNNABLE状态只能进入RUNNING状态或者意外终止。

线程的RUNNING状态

一旦CPU通过轮询或者其他方式从可执行队列中选中了线程,这个线程此时才可以真正地执行自己的逻辑代码。在RUNNING状态下的线程可以进入如下的状态:

  1. 直接进入TERMINATED状态,比如调用JDK已经不推荐的stop方法
  2. 进入BLOCKED状态,调用sleep或者wait方法而加入waitSet中
  3. 进行某个阻塞IO的操作,比如因网络数据的读写而进入了BLOCKED状态
  4. 获取某个锁的资源,从而加入到该锁的阻塞队列中而进入BLOCKED状态
  5. 由于CPU的调度器轮询使线程放弃执行,进入RUNNABLE状态
  6. 线程主动放弃使用权,调用yield方法,进入RUNNABLE状态

线程的BLOCKED状态

在BLOCKED状态下的线程可以进入如下的状态转换:

  1. 直接进入TERMINATED状态,比如调用JDK已经不推荐的stop方法
  2. 线程阻塞的操作结束,比如获得了想要的数据字节,从而进入了RUNNABLE状态
  3. 线程完成了指定的休眠,从而进入了RUNNABLE状态
  4. wait中的线程被其他线程notify/notifyall唤醒,从而进入RUNNABLE状态
  5. 线程获取到了某个锁的资源,从而进入了RUNNABLE状态
  6. 线程在阻塞过程中被打断,从而进入了RUNNABLE状态

线程的TERMINATED状态

线程进入TERMINATED状态意味着线程生命周期的结束。下列情况会进入此状态:

  1. 线程运行正常结束,结束生命周期。
  2. 线程运行出错意外结束
  3. JVM CRASH 所有线程结束

线程start方法剖析

先看下start()的API文档解释
玩转java并发-从创建线程开始这里说到调用start()方法后,有两个线程并发执行。我们可以这么理解:是主线程(main)调用了start()方法,然后由我们自己创建的线程调用run()方法。

public static void main(String[] args) {
		//内部类
		Thread t1 = new Thread("MyThread") {
			@Override
			public void run() {
				//打印结果为:MyThread
				System.out.println(Thread.currentThread().getName());	
			}
		};
		t1.start();
	}

当我们直接调用Thread的run()方法,可以发现run()的调用者就为main了

public static void main(String[] args) {
		//内部类
		Thread t1 = new Thread("MyThread") {
			@Override
			public void run() {
				//打印结果为:main
				System.out.println(Thread.currentThread().getName());	
			}
		};
		t1.run();
	}

start()源码

 public synchronized void start() {
 
		//同一个Thread不能调用start两次,否则抛出异常
	   if (threadStatus != 0)
            throw new IllegalThreadStateException();
            
        group.add(this);
        boolean started = false;
        try {
        	//native的C++方法,里面调用了run()
        	//native表示和C/C++互操作的,告诉编译器该方法在外部定义
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

实际上start()和run()的区别就是:start()方法实现了多线程,run()没有实现多线程。
start()和run()之间的关系涉及到模板设计模式的思想。run()只是一个类的普通方法,程序中依然只有主线程这一线程,还是要顺序执行,还是要等run方法执行完毕之后再执行其他方法。start()真正实现了多线程,无需等run()执行完,就可以继续执行下面的代码。run()可以看作模板设计模式中用户自定义的方法,而start()中固定的部分是创建新线程等部分,run()只是提供给用户实现业务逻辑的如果用户脱离start(),而直接运行run(),也就意味着脱离了模板,那么此时run()方法只是一个普通的方法,并没有实现多线程


RUNNABLE接口的引入

很多地方会讲到创建线程有两种方式,一种是基础Thread,另一种是实现Runnnable接口,这种是不严谨的。在JDK中代表线程的只有Thread这个类。
创建线程只有一种方式就是构造Thread类,而实现线程的执行单元(run方法)有两种方式,第一种是重写Thread的run(),第二种是实现Runnable接口的run()方法,并且将Runnable实例用作构造Thread的参数。

public class TickRunnable implements Runnable {
	
	private final static int max=50;
	private int index=1;
	
	@Override
	public void run() {
		while(index<=max) {
			System.out.println("当前柜台"+Thread.currentThread().getName()+"号码"+index);
			index++;
		}
	}
}
		//将实现Runnable接口的实例用作构造Thread的参数
		//实现业务逻辑和构造Thread的分离
		final TickRunnable tickRunnable = new TickRunnable();
		Thread TickWindow1=new Thread(tickRunnable,"一号柜台");

策略模式在Thread的应用

无论是Runnable中的run()方法,还是Thread中的run()方法,都是想将线程的控制本身和业务逻辑的运行分离开来,达到职责分明,功能单一的原则,这涉及到策略模式的设计思想。
关于策略模式,可以看这篇文章传送门
策略模式简单地说就是将代码中动态可变的部分封装成策略类(接口形式),动态注入实现类。