摘要: 此篇文字从进程和线程、线程的状态(生命周期),java中创建线程的三种方式说起,总体介绍线程,让大家对线程有一个初步的认识。
一 进程与线程
进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位,
简单来说进程就是你能看到的应用软件,而线程就是执行应用软件的具体执行部分。
区别 :
线程不能脱离进程而单独存在,线程依赖于进程,进程应当包含至少一个线程.
进程开销大,线程开销小。
系统会为每一个进程分配内存,而线程的内存则由进程分配,如果超过了则会出现内存溢出。
二 线程的状态
点击JDK的Thread 类,在枚举类 state中可以看到线程的这样几个状态
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
- 线程新建状态
即我们刚刚启动线程,线程在等待启动的状态.
- 线程运行状态
这个运行状态可以又分2个状态,即就绪和运行状态,就绪就是指 还未运行的线程或者阻塞的线程 马上可以运行,但是还没有运行的状态,运行状态就是指线程运行时的状态.
- 线程的阻塞状态
即线程受 synchronized 等方法的影响,运行的方法被锁住,等待其他锁释放后才能进入此方法中。
- 线程等待状态
即线程受到 wait ,notify 方法的影响,等待另外一个方法的通知,然后继续执行的状态。
- 线程等待超时状态
即线程收到sleep(xxx)等方法的影响,等待规定的时候,然后再次执行的状态.
- 线程结束状态
即线程执行完成,释放资源的状态。
各种状态转换如下图:
三 java中创建线程的方式
- 继承Thread类创建线程,
- 继承Thread,并且重写run方法
- 创建Thread子类的实例,
- 调用线程对象的start()方法
public class ThreadDemo extends Thread { @Override public void run() { for (int i=0;i<100;i++){ System.out.println(getName()+" "+i); } } public static void main(String[] args) { new ThreadDemo().start(); } }
程序可以通过setName为线程设置名字,也可以通过getName方法返回指定线程的名字,在默认情况下,主线程的名字为main,用户启动的多个线程的名字依次为Thread-0,Thread-1,Thread-n等
- 实现Runnable 接口创建线程类
- 定义Runnable接口的实现类,并且重写run方法,该run方法的方法体是该线程的执行体
- 初始化实现类,并且作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象.
public class RunnableDemo implements Runnable{ public static void main(String[] args) { RunnableDemo demo = new RunnableDemo(); new Thread(demo,"name").start(); } @Override public void run() { for (int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+" "+i); } } }
- 使用Callable和future创建对象
- 创建Callable接口的实现类,并且实现call方法,该call方法将作为线程执行体,且该call方法有返回值,再创建Callable实现类的实例
- 使用Future类来包装Callable对象,该Feture对象封装了该Callable对象的call方法的返回值
- 使用FutureTask对象作为Thread对象的target创建并启动新线程
- 调用FutureTask对象的get方法来获取子线程的执行结束的返回值
public class CallableDemo { public static void main(String[] args) { FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>) ()->{ int i = 0 ; for (;i<100;i++){ System.out.println(Thread.currentThread().getName()+"循环变量的值:"+i); } return i; }); new Thread(task, "name").start(); try { System.out.println("子线程返回的值为"+task.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
四 线程的中断和复位
在很多的业务处理中,我们常常因为某些原因需要中断一些线程,那么我们如何优雅的中断线程呢?
在Thread中提供了stop方法,但是已经被打了删除线,demo.stop(),下面我们可以通过自己的方式优雅的关闭线程.
public class VoliDemo extends Thread{ @Override public void run() { int i= 0; while (!isStop){ i++; System.out.println(i); } System.out.println(i); } private volatile static boolean isStop = false; public static void main(String[] args) throws InterruptedException { VoliDemo demo = new VoliDemo(); demo.start(); demo.stop(); TimeUnit.SECONDS.sleep(1); isStop = true; System.out.println("线程结束"); } }
上面这段代码,我们定义了一个可见的属性,通过控制这个属性的值,让这个线程中止。
那么,java中是否还有可以中断线程的方法呢?
- 线程中断
java中可以通过interrupt 方法来实现线程的中断
public class InterruptedDemo { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ while (true){ if (Thread.currentThread().isInterrupted()){ //默认是false,当线程中断的时候进入 System.out.println("before:"+Thread.currentThread().isInterrupted()); Thread.currentThread().interrupted();//线程复位,变为false System.out.println("after:"+Thread.currentThread().isInterrupted()); } } }); thread.start(); TimeUnit.SECONDS.sleep(1); thread.interrupt();//线程中断,属性变成true } }
interrupted 属性实际返回1/0,转换后返回的是 false/true,默认是false
跟踪thread.interrupt()线程中断方法可知道 调用private native void interrupt0()方法,再次跟踪源码可以看到
interrupt0调用 JVM_Interrupt 方法,然后再次调用Thread::interrupt(thr)方法,代码如下
void Thread::interrupt(Thread* thread) { trace("interrupt", thread); debug_only(check_for_dangling_thread_pointer(thread);) os::interrupt(thread); }
方法到 os::interrupt 这个地方之后,就会根据不同操作系统,然后调用不同的方法,最终的目的都是将interrupted,设置成true,线程就会终止。
- 线程的复位
上面实例通过interrupted方法进行线程复位,跟踪源码,interrupted 方法调用 private native boolean isInterrupted(boolean ClearInterrupted),ClearInterrupted 值为true,标示线程进行复位,
底层通过Thread::is_interrupted方法调用各个系统自己的方法,如linux,将线程的标示设置为false。
if (interrupted && clear_interrupted) { osthread->set_interrupted(false); // consider thread->_SleepEvent->reset() ... optional optimization }
另外通过InterruptedException 对线程进行复位操作.
由于线程处于睡眠状态或者等待状态,当线程发起中断请求的时候,线程会抛出一个InterruptedException 异常,由人工选择中断或者继续执行。
public class InterruptedExceptionDemo { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ while (!Thread.currentThread().isInterrupted()){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { System.out.println("线程状态:"+Thread.currentThread().isInterrupted()); Thread.currentThread().interrupt();//手动结束, System.out.println("线程状态:"+Thread.currentThread().isInterrupted()); e.printStackTrace(); } } }); thread.start(); TimeUnit.SECONDS.sleep(1); thread.interrupt();//线程中断,属性变成true } }
ps:线程的复位是为了让外部知道,线程已经收到请求,但是不会立刻停止线程,需要外部来根据具体业务来进行控制。