一、进程与线程
1、DOS系统【单进程系统】
最早的时候DOS有一个特点:只要电脑有病毒,那么电脑就死机了。
原因:传统的DOS系统属于单进程系统,即:在同一时间段内只允许有一个程序运行。
2、Windows系统【多进程多线程】
电脑中毒也可以运行,但是会变慢
原因:因为在一个cpu、一块资源的情况下,程序利用一些轮转算法,可以让一个资源在一个时间段可以同时处理多个程序(进程),但是在一个时间点上只允许一个进程去执行。
windows:任务管理器
linux:ps
在每一个进程上可以划分出若干个线程,那么线程的操作一定是要比进程更快。多线程操作要超多进程操作。但是所有的线程都一定是要在进程的基础上进行划分。
所以进程一旦消失,那么线程一定消失
线程依附于进程存在。
进程:一个进程就是一个“执行中的程序”,是程序在计算机上的一次运行活动。程序要运行,系统就在内存中为该程序分配一块独立的内存空间,载入程序代码和资源进行执行。程序运行期间该内存空间不能被其他进程直接访问。系统以进程为基本单位进行系统资源的调度和分配。
线程:程序的执行具体是通过线程来完成的,所以一个进程中至少有一个线程。回忆一下 HelloWrold 程序中main方法的执行,其实这时候,Java虚拟机会开启一个名为“main”的线程来执行程序代码。一个进程可以包含多个线程,这些线程共享数据空间和资源,但又分别拥有各自的执行堆栈和程序计数器。线程是CPU调度的基本单位。
二、java的线程介绍【线程状态、优先级、用户线程和守护线程】
1、java线程的生命周期【线程状态】
java线程在他的生命周期内有几种不同的状态:线程初始化,启动,运行和死亡。
上图所示的状态解释如下:【可通过thread dump查看】
● new 是指线程被初始化,但是还没有调用其start方法,还没有开始执行
每一个线程,在堆内存中都有一个对应的Thread对象。Thread t = new Thread();当刚刚在堆内存中创建Thread对象,还没有调用t.start()方法之前,线程就处在NEW状态。在这个状态上,线程与普通的java对象没有什么区别,就仅仅是一个堆内存中的对象。
● runnable 调用线程的start方法之后,线程开始执行其任务,这时候线程是运行状态
该状态表示线程具备所有运行条件,在运行队列中准备操作系统的调度,或者正在运行。 这个状态的线程比较正常,但如果线程长时间停留在在这个状态就不正常了,这说明线程运行的时间很长(存在性能问题),或者是线程一直得不得执行的机会(存在线程饥饿的问题)。
● waiting 有时候线程需要等待另外一个线程执行完毕之后再执行,这时候线程处于等待状态,处于等待状态的线程需要其他线程notify之后才能恢复到运行状态
处在该线程的状态,正在等待某个事件的发生,只有特定的条件满足,才能获得执行机会。而产生这个特定的事件,通常都是另一个线程。也就是说,如果不发生特定的事件,那么处在该状态的线程一直等待,不能获取执行的机会。比如说,A线程调用了obj对象的obj.wait()方法,如果没有线程调用obj.notify或obj.notifyAll,那么A线程就没有办法恢复运行;如果A线程调用了LockSupport.park(),没有别的线程调用LockSupport.unpark(A),那么A没有办法恢复运行。
● timed waiting 运行中的线程可以进入到定时等待的状态,这时候线程间隔指定的时间间隔之后就会恢复到运行状态
J.U.C中很多与线程相关类,都提供了限时版本和不限时版本的API。TIMED_WAITING意味着线程调用了限时版本的API,正在等待时间流逝;当等待时间过去后,线程一样可以恢复运行。如果线程进入了WAITING状态,一定要特定的事件发生才能恢复运行;而处在TIMED_WAITING的线程,如果特定的事件发生或者是时间流逝完毕,都会恢复运行。
● terminated 当线程任务执行完毕或者被abort的时候线程处于终止状态
线程执行完毕,执行完run方法正常返回,或者抛出了运行时异常而结束,线程都会停留在这个状态。这个时候线程只剩下Thread对象了,没有什么用了。
线程的状态在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; }
2、java线程的优先级
每一个java线程都有一个优先级,操作系统可以通过线程的优先级决定决定将cpu分配给哪个线程。优先级越高的线程越可能得到cpu资源。
java线程优先级的值在1-10之间,1是常量MIN_PRIORITY,10是常量MAX_PRIORITY 。默认情况下java的线程的优先级是NORM_PRIORITY 即5.
高优先级的线程通常更重要,更有可能获得cpu时间资源,但是并不能保证绝对可以获得cpu。
3、用户线程和守护线程
java中线程分为两种类型:用户线程和守护线程。通过Thread.setDaemon(false)设置为用户线程;通过Thread.setDaemon(true)设置为守护线程。如果不设置此属性,默认为用户线程。
用户线程和守护线程的区别:【用户线程存活程序存活,其他则结束】
1. 主线程结束后用户线程还会继续运行,JVM存活;主线程结束后守护线程和JVM的状态又下面第2条确定。
2.如果没有用户线程,都是守护线程,那么JVM结束(随之而来的是所有的一切烟消云散,包括所有的守护线程)。
main方法说明
1.Main线程是个非守护线程,不能设置成守护线程。
main线程是由java虚拟机在启动的时候创建的,进入程序的入口。main方法开始执行的时候,main用户线程已经创建好并在运行了。对于运行中的线程,不可以调用Thread.setDaemon(),调用会抛出异常Exception in thread "main"
2.Main线程结束,如果还有其他用户线程则继续运行,如果都是守护线程则直接结束
主线程,只是个普通的非守护线程,用来启动应用程序,不能设置成守护线程;除此之外,它跟其他非守护线程没有什么不同。主线程执行结束,其他用户线程一样可以正常执行。
按照操作系统的理论,进程是资源分配的基本单位,线程是CPU调度的基本单位。对于CPU来说,其实并不存在java的主线程和子线程之分,都只是个普通的线程。进程的资源是线程共享的,只要进程还在,线程就可以正常执行,换句话说线程是强依赖于进程的。也就是说,线程其实并不存在互相依赖的关系,一个线程的死亡从理论上来说,不会对其他线程有什么影响。
java虚拟机(相当于进程)退出的时机是:虚拟机中所有存活的线程都是守护线程。只要还有存活的非守护线程虚拟机就不会退出,而是等待非守护线程执行完毕;反之,如果虚拟机中的线程都是守护线程,那么不管这些线程的死活java虚拟机都会退出。
补充说明:
定义:守护线程--也称“服务线程”,在没有用户线程可服务时会自动离开。
优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
设置:通过setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为守护线程的方式是在线程启动用线程对象的setDaemon方法。
示例: 垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
生命周期:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。那Java的守护线程是什么样子的呢。当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则JVM不会退出
例子程序:
thread = new Thread(this);
thread.setDaemon(true);
thread.start();
当java虚拟机中没有非守护线程在运行的时候,java虚拟机会关闭。当所有常规线程运行完毕以后,守护线程不管运行到哪里,虚拟机都会退出运行。所以你的守护线程最好不要写一些会影响程序的业务逻辑。否则无法预料程序到底 会出现什么问题。
示例:
public static void main(String[] args) throws Exception { Runnable r = new Runnable() { @Override public void run() { String daemon = Thread.currentThread().isDaemon() ? "daemon" : "not daemon"; while (true) { System.out.println("Im is running " + daemon + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread thread = new Thread(r); thread.setDaemon(true); thread.start(); Thread.sleep(3000); System.out.println("main is over"); }
运行上面程序,输出如下内容后程序就退出了。
Im is running daemon14:14:48
Im is running daemon14:14:49
Im is running daemon14:14:50
main is over
可以看到在主线程退出之后,deamon线程也就被终止了,同时程序也就退出了。
我们对上面程序稍作改动,将t.setDaemon(true)注释掉,再看下运行结果。
Im is running not daemon14:15:29
Im is running not daemon14:15:30
Im is running not daemon14:15:31
main is over
Im is running not daemon14:15:32
Im is running not daemon14:15:33
……
可以看到在主线程退出之后,t线程还在继续执行,这是因为线程t默认情况下是非守护线程,尽管主线程退出了,他还是在继续执行着。
需要注意设置线程是否为守护线程必须在其执行之前进行设置,否则会抛出异常IllegalThreadStateException。这一点可以从Thread类的setDaemon(boolean)的源码中得到求证。如下源码:
public final void setDaemon(boolean on) { checkAccess(); if (isAlive()) { throw new IllegalThreadStateException(); } daemon = on; }
可以看到如果线程在live状态调用setDaemon会抛出异常。
3、java多线程实现的两种方式
1. 使用Runnable接口实现多线程
使用Runnable接口实现多线程需要两个步骤,首先实现Runnable接口类,然后声明Thread实例,调用thread实例的start方法,开始执行。
2. 从Thread类继承实现java的多线程
从java的Thread类继承实现多线程,也是实现其run方法,然后声明实例,并调用实例的start方法启动线程。
4、java Thread类的主要方法介绍
Thread的实例方法:
方法定义 |
方法说明 |
public void start() |
最常用的方法,顾名思义启动线程,即开始执行线程的run方法 |
public void run() |
如果线程重写了run方法,那么执行重写的方法,否则执行线程的Runnable接口中定义的run方法 |
public final void setName(String) |
设置线程的名称 |
public final void setPriority(int) |
设置线程的优先级(范围在1-10包含1,10) |
public final void setDeamon(boolean) |
设置线程是否是后台线程 |
public final void join(long) |
在另外一个线程中调用当前线程的join方法,会导致当前线程阻塞,直到另一线程执行完毕,或者超过参数指定毫秒数 |
public void interrupt() |
中断线程 |
public final boolean isAlive() |
线程是否处于存活状态,线程在启动和结束之前都处于存活状态 |
Thread类的常用静态方法:
方法定义 |
方法说明 |
public static void yield() |
使当前运行线程相同优先级的线程获得执行机会,类似sleep,但是只会将cpu让给相同优先级的线程 |
public static void sleep(long) |
使当前线程休眠指定毫秒的时间 |
public static boolean holdsLock(Object x) |
判断当前线程是否拥有对象的锁 |
public static Thread currentThread() |
获得当前线程实例 |
public static void dumpStack() |
打印当前线程的执行堆栈,这对多线程程序的调试很有帮助 |