多线程,说白了就是多条执行路径,原来是一条路径,就主路径(main),现在是多条路径。
Java源程序和字节码文件被称为“程序” ( Program ),是一个静态的概念。
执行中的程序叫做进程(Process),是一个动态的概念。为了使计算机程序得以运行,计算机需要加载代 码,同时也要加载数据。
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程 并行执行不同的任务。
区别 | 进程 | 线程 |
---|---|---|
根本区别 | 作为资源分配的单位 | 调度和执行的单位 |
开销 | 每个进程都有独立的代码和数据空间,进程间的切换有较大开销 | 线程可以看做轻量级的进程,同一类线程共享 代码和数据空间,每个线程有独立运行栈和程 序计数器(PC),线程切换的开销小 |
所处环境 | 在操作系统中能同时运行多个任务(程序) | 在同一个应用程序中有多个顺序流同时进行 |
分配内存 | 系统在运行的时候会为每个进程分配不 同的内存区域 | 线程间共享进程的所有资源,每个线程只有有 自己的堆栈和局部变量。线程由CPU独立调度 执行,在多CPU环境下就允许多个线程同时运 行 |
包含关系 | 没有线程的进程可以看作单线程,如果 一个进程拥有多个线程,则执行过程不 是一条线的,而是多条线(线程)共同 完成的 | 线程是进程的一部分,所以线程有的时候会被 称为是轻量级进程或轻权进程 |
注意:有的多线程是模拟出来的,真正的多线程是指有多个 cpu,即多核,如服务器。如果是模拟出来的 多线程,即一个 cpu 的情况下,在同一个时间点,cpu 只能执行一个代码, 因为切换的很快,所以就有同时执行的错觉。
优点
资源利用率更好;程序设计在某些情况下更简单;程序响应更快
缺点
//继承多线程父类 public class ThreadDemo extends Thread { //重写run()方法 @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("吃饭"); //只能抓取异常,父类没有抛出异常 try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { ThreadDemo td = new ThreadDemo(); Sleep s = new Sleep(); //开始多线程 td.start(); s.start(); for (int i = 0; i < 20; i++) { System.out.println("打豆豆"); try { Thread.sleep(2);//延迟执行,放大问题 } catch (InterruptedException e) { e.printStackTrace(); } } } }
这种方式的特点:那就是如果我们的类已经从一个类继承,则无法再 继承 Thread 类,异常只能捕获。
public class ThreadInterfaceDemo implements Runnable { public static void main(String[] args) { //真实角色 ThreadInterfaceDemo tid = new ThreadInterfaceDemo(); //创建代理线程 Thread td = new Thread(tid); //开始线程 td.start(); for (int i = 0; i < 100; i++) { System.out.println("Thread"); } } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("Runnable"); } } }
优点:接口可以多实现,类只能单继承,实现资源共享
public class CallAbleTest { public static void main(String[] args) throws Exception{ MyCallable callable = new MyCallable(); // 将Callable包装成FutureTask,FutureTask也是一种Runnable FutureTask<Integer> futureTask = new FutureTask<>(callable); // 将FutureTask包装成Thread new Thread(futureTask).start(); System.out.println(futureTask.isDone()); System.out.println(futureTask.get()); } } class MyCallable implements Callable<Integer>{ @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i <= 100000; i++) { sum += i; } return sum; } }
优点:
call方法可以抛出异常可以定义返回值
缺点:
使用复杂麻烦
线程池
就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系 统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任 务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
线程池的工作机制
在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任 务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。 一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
使用
public class ThreadPoolTest { public static void main(String[] args)throws Exception { Thread.currentThread().setName("主线程"); System.out.println(Thread.currentThread().getName() + ": 输出的结果" ); // 通过线程池工厂创建线程数量为2的线程池 ExecutorService service = Executors.newFixedThreadPool(2); //执行线程,execute()适用于实现Runnable接口创建的线程 service.execute(new MyThread()); //submit()适用于实现Callable接口创建的线程 Future<Integer> task = service.submit(new MyCallable()); System.out.println(task.get()); // 关闭线程池 service.shutdown(); } }
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持 这个状态直到程序 start() 这个线程。
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要 等待JVM里线程调度器的调度。
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态 的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就 从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。