关于进程的定义,其实有很多:
个人觉得比较好的定义是:
进程是具有一定独立功能的程序关于某个数据集合上的一次运行过程
说白了,进程就是CPU执行的一次任务,在单个CPU中一次只能运行一次任务,即CPU总是运行一个进程,其他进程处于非运行状态。但是现在的操作系统都是多核CPU,所以可以同时运行多个进程,执行多个任务。
线程实际上是一个进程中的"子任务",在一个进程中可以创建多个线程,打个比方,打开QQ就是执行了一个进程,而在QQ里与他人聊天,同时下载文件等操作就是线程。
多线程是指操作系统在单个进程内支持多个并发执行路径的能力。每个进程中只有一个线程在执行的传统方法称为 单线程方法 。但是单线程存在很多弊端,它并不能充分利用CPU资源,而且单线程在应对复杂业务时响应时间也是较差的,所以我们需要使用多线程来帮助我们。使用多线程有如下好处(原因):
那我们为什么去使用 多线程而不是使用多进程去进行并发程序的设计,是因为线程间的切换和调度的成本远小于 进程
首先有进程,其次才是线程,其实线程的生命周期及各种状态转换和进程类似,下面看一张 进程的状态转换图(图片摘自网络):
进程一般有三种基本的状态:
进程被创建后,加入就绪队列等待被调度执行,CPU调度器根据一定的调度算法调度就绪队列中的进程在处理机上执行。
上面简述了进程的基本状态及变化过程,线程也是类似的:
该图表示一个线程的生命周期状态流转图,很清楚的描绘了一个线程从创建到终止的一个过程。线程的所有状态在 Thread类中的State枚举定义,如下:
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; } 复制代码
注意:从New 状态出发后,线程不能再回到 New状态,同理,处于 Terminated 的线程也不能回到 Running状态
下面分别对线程的各种状态进行相关说明
新建线程有两种方式:
Thread类是在java.lang包中定义的,本来我们可以直接继承Thread,重载 run() 方法来自定义线程,但是Java 是单继承的,如果一个类已经继承了其他类,就无法再继承Thread,所以我们这时就可以实现 Runnable接口来实现
// 1. 继承Thread类 public class Thread1 extends Thread{ @Override public void run() { System.out.println("hello world"); } public static void main(String[] args) { Thread1 thread1 = new Thread1(); //调用start()运行线程,执行内部的run()方法 thread1.start(); } } //2. 实现Runnable接口 public class Thread2 implements Runnable{ @Override public void run() { System.out.println("hello world"); } public static void main(String[] args) { Thread thread = new Thread(new Thread2()); thread.start(); } } 复制代码
Thread有一个重要的构造方法:
public Thread(Runnable target) 复制代码
它传入一个Runnable 接口的实例,在start()方法调用时,新的线程就会执行 Runnable.run()方法,实际上,默认的Thread.run()就是这么做的:
public void run() { if (target != null) { target.run(); } } 复制代码
默认的Thread.run()就是直接调用 内部的 Runnable接口
一般来说,线程在执行完毕后就会结束,无须手动关闭,但是还是有些后台常驻线程可能不会自动关闭。
Thread提供了一个 stop()方法,该方法可以立即将一个线程终止,但是目前stop()已经被废弃,不推荐使用,原因是 stop()方法太过于简单粗暴,强行把执行到一半的线程终止,可能会引起一些数据不一致的问题。
线程中断是一种重要的线程协作机制,它并不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出。至于目标线程接到通知后如何处理,则完全由目标线程自行决定。
与线程中断有关的,有三个方法:
public void Thread.interrupt() //中断线程 public boolean Thread.isInterrupted() //判断是否被中断 public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态 复制代码
Thread.interrupt()
方法是一个实例方法, 通知目标线程中断,设置中断标志爱为,该标志位表明当前线程已经被中断了 Thread.isInterrupted()
方法也是实例方法, 判断当前线程是否有被中断(通过检查中断标志位) Thread.interrupted
也是用来判断当前线程的中断状态的,但 同时会清楚当前线程的中断标志位状态 举例说明:
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { while (true) { //通过中断标志位判断是否被中断 if (Thread.currentThread().isInterrupted()) { //中断逻辑处理 System.out.println("Interrupted!"); // break退出循环 break; } } }); //开启线程 thread.start(); Thread.sleep(2000); //设置中断标志位 thread.interrupt(); } 复制代码
Thread有个 sleep
方法,它会让当前线程休眠若干时间,它会抛出一个 InterruptedException
中断异常。 InterruptedException
不是运行时异常,也就是说程序必须捕获并且处理它,当线程在 sleep()休眠时,如果被中断,这个异常就产生了。
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread() { @Override public void run() { while (true) { try { Thread.sleep(2000); } catch (InterruptedException e) { System.out.println("Interrupted When Sleep"); //中断异常会清楚中断标记,重新设置中断状态 Thread.currentThread().interrupt(); } if (Thread.currentThread().isInterrupted()) { System.out.println("Interrupted!"); break; } } } }; thread.start(); Thread.sleep(2000); thread.interrupt(); } //output: Interrupted When Sleep Interrupted! 复制代码
Thread.sleep() 方法由于中断而抛出异常,此时,它会清楚中断标记,如果不加处理,那么在下一次循环开始时,就无法捕获这个中断,故在异常处理中,再次设置中断标志位
为了支持多线程之间的协作,JDK 提供了两个非常重要的接口线程等待 wait() 方法和通知 notify() 方法,这两个方法定义在 Object类中。
public final void wait(long timeout) throws InterruptedException; public final void notify(); 复制代码
当在一个对象实例上调用了object.wait() 方法,那么它就会进入object对象的等待队列进行等待,这个等待队列中,可能会有多个线程,因为系统运行多个线程同时等待某一个对象。 当object.notify()被调用时,它就会从 这个等待队列中,随机选择一个线程,并将其唤醒 。
下面看一个代码示例:
public class Example { public static void main(String[] args) { final Object object = new Object(); new Thread(() -> { System.out.println("thread A is waiting to get lock"); synchronized (object) { System.out.println("thread A already get lock"); try { TimeUnit.SECONDS.sleep(1); System.out.println("thread A do wait method"); object.wait(); System.out.println("thread A wait end"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(() -> { System.out.println("thread B is waiting to get lock"); synchronized (object) { System.out.println("thread B already get lock"); try { TimeUnit.SECONDS.sleep(5); System.out.println("thread B do wait method"); object.notify(); System.out.println("thread B do notify method"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } } 复制代码
执行结果:
thread A is waiting to get lock thread A already get lock thread B is waiting to get lock thread A do wait method thread B already get lock thread B do wait method thread B do notify method thread A wait end 复制代码
上述开启了两个线程A和B,A执行 wait()方法前,A先申请 object 的对象锁,在执行 object.wait()时,持有object的锁,执行后,A会进入等待,并释放 object的锁。
B在执行notify()之前也会先获得 object的对象锁,执行notify()之后,释放object的锁,然后A重新获得锁后继续执行
join方法使当前线程等待调用 join方法的线程结束后才能继续往下执行,下面是代码示例:
public class ThreadExample { public volatile static int i = 0; public static class Thread_ extends Thread{ @Override public void run() { for (i = 0; i < 1000000; i++) { } } } public static void main(String[] args) throws InterruptedException { Thread_ A = new Thread_(); A.start(); //让当前的主线程等待thread_线程执行完毕后才往下执行 A.join(); System.out.println(i); } } 复制代码
join()的本质实际上是让调用线程wait()在当前线程对象实例上,当线程执行完毕后,被等待线程退出前调用 notifyAll()通知所有的等待线程继续执行
以上面的例子说明,线程A调用join()使main线程 wait()在A对象实例上,等线程A执行完毕,线程A调用notifyAll(),main线程等到通知继续往下执行。
而Thread.yield()方法的定义如下:
public static native void yield(); 复制代码
它是静态方法,一旦执行,它会使当前调用该方法的线程让出CPU,但是让出CPU不代表 当前线程不执行,当前线程让出CPU后,还会进行CPU资源的争夺,但是是否能够被分配到,就不一定了。
Thread.yield()的调用就好像在说:
我已经完成了一些最重要的工作了,我应该是可以休息一下了,可以给其他线程一些工作机会了
守护线程是一种特殊的下线程,它是系统的守护者,在后台默默地完成一些系统性的服务,比如垃圾回收线程,JIT线程就可以理解为守护线程,与之对应的是用户线程,用户线程可以理解为系统的工作线程,它会完成这个程序应该要完成的业务操作。当一个Java应用内,只有守护线程时,Java虚拟机就会自然退出。
代码示例如下:
public class DaemonDemo { public static void main(String[] args) throws InterruptedException { Thread t = new Thread() { @Override public void run() { while (true) { System.out.println("I am alive"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; //将线程t设置为守护线程 t.setDaemon(true); t.start(); //主线程休眠10s Thread.sleep(10000); } } 复制代码
输出结果:
I am alive I am alive I am alive I am alive I am alive I am alive I am alive I am alive I am alive I am alive 复制代码
上面的例子,将t设置为守护线程,系统中只有主线程 main 为用户线程,在 main休眠10s后,守护线程退出,整个线程也退出。
Java 中的线程可以有自己的优先级,优先级高的线程在竞争资源时会更有优势,更可能抢占资源。线程的优先级调度和操作系统密切相关,当然高优先级可能也会有抢占失败的时候,但是大部分情况下,高优先级比低优先级对于抢占资源来说更有优势。
代码示例如下:
public class PriorityDemo { private static int count = 0; public static void main(String[] args) { Thread A = new Thread() { @Override public void run() { while (true) { synchronized (PriorityDemo.class) { count++; if (count > 100000) { System.out.println("HighPriority is complete"); break; } } } } }; Thread B = new Thread() { @Override public void run() { while (true) { synchronized (PriorityDemo.class) { count++; if (count > 100000) { System.out.println("LowPriority is complete"); break; } } } } }; //设置线程A为高优先级 A.setPriority(Thread.MAX_PRIORITY); //设置线程B为低优先级 B.setPriority(Thread.MIN_PRIORITY); //启动线程B B.start(); //启动线程A A.start(); } } 复制代码
运行结果:
HighPriority is complete LowPriority is complete 复制代码
由上可以初步得出,高优先级的线程在大部分情况下会首先完成任务。
上面简单总结了一部分Java并发编程所涉及到知识点,算是入门Java并发的一个开端。