概述
并发和并行是即相似又有区别:
并行: 指两个或多个事件在同一时刻发生;
并发: 指两个或多个事件在同一时间段内发生.
进程是指一个内存中运行中的应用程序. 每个进程都有自己独立的一块内存空间, 一个应用程序可以同时启动多个进程. 比如在 Windows 系统中, 一个运行的 abc.exe 就是一个进程.
那么我们此时就可以处理同时玩游戏和听音乐的问题了, 我们可以设计成两个程序, 一个专门负责玩游戏, 一个专门负责听音乐.
但是问题来了, 要是要设计一个植物大战僵尸游戏, 我得开 N 个进程才能完成多个功能, 这样的设计显然是不合理的.
更何况大多数操作系统都不需要一个进程访问其他进程的内存空间, 也就是说进程之间的通信很不方便.
此时我们得引入 "线程" 这门技术, 来解决这个问题.
线程是指进程中的一个执行任务(控制单元), 一个进程可以同时并发运行多个线程, 如: 多线程下载软件.
一个进程至少有一个线程, 为了提高效率, 可以在一个进程中开启多个执行任务, 即多线程.
多进程: 操作系统中同时运行的多个程序.
多线程: 在同一个进程中同时运行的多个任务.
我们查看 Windows 环境下的任务管理器:
在操作系统中允许多个任务, 每一个任务就是一个进程, 每一个进程也可以同时执行多个任务, 每一个任务就是线程.
多线程作为一种多任务, 并发的工作方式, 当然有其存在优势:
进程之前不能共享内存, 而线程之间共享内存 (堆内存) 则很简单.
系统创建进程时需要为该进程重新分配系统资源, 创建线程则代价小很多, 因此实现多任务并发时, 多线程效率更高.
Java 语言本身内置多线程功能的支持, 而不是单纯第作为底层系统的调度方式, 从而简化了多线程编程.
用 java 语言创建进程
- import java.io.IOException;
- // 如何用 java 语言开启一个进程
- public class ProcessDemo {
- public static void main(String[] args) throws IOException{
- // 方式一: 使用 Runtime 的 exec 方法
- Runtime.getRuntime().exec("notepad");
- // 方式二: 使用 ProcessBuilder 类中的 start 方法
- ProcessBuilder pb = new ProcessBuilder("notepad");
- pb.start();
- }
- }
创建和启动线程
方式一: 继承 Thread 类
自定义类继承于 Thread 类, 那么该自定义类就是线程类;
2.覆写 run 方法, 将线程运行的代码存放在 run 中;
3.创建自定义类的对象, 即线程对象;
4.调用线程对象的 start 方法, 启动线程.
- package thread_create;
- // 继承方式 创建和启动线程
- class MusicThread extends Thread{ //MusicThread 是线程子类
- public void run() { // 子类必须重写 run()方法
- for(int i = 0 ;i <50;i++)
- {
- System.out.println("听音乐"+i);
- }
- }
- }
- public class ExtendsThreadDemo {
- public static void main(String[] args){
- for(int i = 0 ;i < 50;i++)
- {
- System.out.println("玩游戏"+i);
- if( i == 10){
- MusicThread t = new MusicThread(); // 创建线程子类的实例
- t.start(); // 底层也调用了 run(); 启动线程
- }
- }
- }
- }
方式二: 实现 Runnable 接口
1.自定义类实现 Runnable 接口;
2.覆写 run 方法, 线程运行的代码存放在 run 中;
3.通过 Thread 类创建线程对象, 并将实现了 Runnable 接口的实现类对象作为参数传递给 Thread 类的构造器.
4.Thread 类对象调用 start 方法, 启动线程.
- class MusicRunnable implements java.lang.Runnable{ // 注意 MusicRunnable 类不是线程类或者线程子类
- public void run() { // 接口的实现必须覆盖方法.
- for(int i = 0 ;i < 50;i++)
- {
- System.out.println("听音乐"+i);
- }
- }
- }
- public class ImplementsRunnableDemo {
- public static void main(String[] args){
- for(int i = 0 ;i < 50;i++)
- {
- System.out.println("玩游戏"+i);
- if( i == 10){
- Runnable t = new MusicRunnable(); // 启动线程必须用线程类对象调用 start();
- new Thread(t).start(); // 类 Thread 的一个构造器中 Thread(Runnable target)分配新的 Thread 对象.
- }
- }
- }
- }
对比两种方法(吃苹果比赛)
1, 继承 Thread 类
- /**
- * 案例: 存在 50 个苹果, 现在有请三个童鞋 (小 A, 小 B, 小 C) 上台表演吃苹果.
- * 因为 A,B,C 三个人可以同时吃苹果, 此时得使用多线程技术来实现这个案例.
- *
- * 此处程序不合理, ABC 每个线程都执行 50 次, 即 ABC 每个人都吃一次编号 50 的苹果
- */
- class Person extends Thread{
- private int num= 50;
- Person(String name){
- super(name);
- }
- public void run() {
- for(int i = 0;i < 50;i++){
- if(num> 0)
- System.out.println(super.getName()+"吃了第"+num--+"个苹果");
- }
- }
- }
- // 使用继承 Thread 的方式创建线程
- public class ExtendsDemo {
- public static void main(String[] args){
- Person p1 = new Person("A");
- p1.start();
- Person p2 = new Person("B");
- p2.start();
- Person p3 = new Person("C");
- p3.start();
- }
- }
2, 实现 Runnable 接口
- class Apple implements Runnable{
- private int num = 50;
- public void run(){
- for(int i = 0; i<50 ;i++){
- if(num> 0){
- System.out.println(Thread.currentThread().getName()+"吃了第"+num--+"个苹果");
- }//Thread.currentThread() 返回对当前正在执行的线程对象的引用.
- }
- }
- }
- // 使用实现 Runnable 接口的方式, 这种方法可以解决此问题
- public class ImplementsDemo {
- public static void main(String[] args){
- Runnable r = new Apple();
- new Thread(r,"A").start();
- new Thread(r,"B").start();
- new Thread(r,"C").start();
- }
- }
对比
继承方式:
java 中类是单继承的, 如果继承了 Thread, 该类就不能再有其他的直接父类
从操作上分析, 继承方式更简单, 获取线程的名字也简单(操作上, 简单)
从多线程共享一个资源上分析, 继承方式不能做到
实现方式
java 中类可以实现接口, 此时该类还可以继承其他类, 并且可以实现接口(设计上, 更优雅)
从操作上分析, 实现方式稍微复杂点, 获取线程的名字比较复杂, 得使用 currentThread()获取当前线程的引用.
从多线程共享一个资源上分析, 实现方式可以做到.
来源: https://www.cnblogs.com/tfper/p/9861463.html