详解JUC并发编程中的进程与线程学习

目录
  • 进程与线程
    • 进程
    • 线程
    • 同步异步
    • 串行并行执行时间
    • 创建和运行线程
    • Thread 与 Runnable 的关系原理分析
    • 查看进程
    • 线程运行原理
    • 线程上下文切换
    • start与run方法
    • sleep方法
    • sleep打断
    • join方法
    • interrupt 方法
    • 守护进程
    • 线程的状态
    • Java API 层面
  • 总结

进程与线程

进程

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的
  • 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
  • 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)

线程

线程是主要负责运行指令,进程是主要管加载指令。

一个进程之内可以分为一到多个线程。

一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行

Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器

同步异步

  • 需要等待结果返回,才能继续运行就是同步
  • 不需要等待结果返回,就能继续运行就是异步

串行并行执行时间

使用多核cpu并行执行可以明显的提高执行效率

  • 串行执行时间 = 各个线程时间累加和 + 汇总时间
  • 并行执行时间 = 最慢的线程时间 + 汇总时间

注意:单核依然是并发的思想(即:cpu轮流去执行线程,微观上仍旧是串行),使用单核的多线程可能会比使用单核的单线程慢,这是因为多线程上下文切换反而浪费了时间。

创建和运行线程

1.使用 Thread

public static void main(String[] args) {
        // 创建线程对象
        Thread t = new Thread("线程1") {
            public void run() {
                // 要执行的任务
                log.debug("线程1被启动了");
            }
        };
        // 启动线程
        t.start();
        log.debug("测试");
    }

2.使用 Runnable 配合 Thread

public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            public void run() {
                // 要执行的任务
                log.debug("线程1被启动了");
            }
        };
        // 创建线程对象
        Thread t = new Thread(runnable);
        t.setName("线程1");
        // 启动线程
        t.start();
        log.debug("测试");
    }

这里的Runnable是一个接口,接口中只有一个抽象方法,由我们来提供实现,实现中包含线程的代码就可以了。

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

Thread 与 Runnable 的关系原理分析

方法1原理分析

方法2是使用runnable对象,当成参数传给Thread构造方法,其中又调用了init方法,下面是Thread构造方法的源码

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

继续跟踪查看runnable对象传到哪里去了,可以看到又传给了另一个重载的init,如下

private void init(ThreadGroup g, Runnable target, String name,                      long stackSize) {        init(g, target, name, stackSize, null, true);    }private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

再次跟踪可以看到是把runnable对象传给了一个thread的一个成员变量

//省略部分代码
this.target = target;

那么这个成员变量在哪里在使用了呢,经过查找可以发现是在run方法里面,只不过Thread发现有runnable对象就会先采用runnable的run方法。

@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

方法2原理分析

通过创建一个子类去重写Thread类的run方法,这样就不会执行父类的run方法。

1.用 Runnable 更容易与线程池等高级 API 配合

2.用 Runnable 让任务类脱离了 Thread 继承体系,更灵活

方法3 FutureTask配合Thread创建线程

Future接口中含有get方法来返回结果的

//省略部分代码
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;

而runnable本身是没有返回结果的,runnable不能将结果传给其他线程。

public interface Runnable {
    public abstract void run();
}

要注意到FutureTask也实现了Runnable接口,也可以传给Thread的有参构造里面。

创建线程的代码

public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建任务对象
        FutureTask<Integer> task3 = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.debug("线程1被执行了");
                return 666;
            }
        });
        // 参数1 是任务对象; 参数2 是线程名字,推荐
        new Thread(task3, "线程1").start();
        // 主线程阻塞,同步等待 task 执行完毕的结果
        Integer result = task3.get();
        log.debug("结果是:{}", result);
    }

查看进程

  • 任务管理器可以查看进程和线程数,也可以用来杀死进程,也可以在控制台使用tasklist查看进程taskkill杀死进程
  • jconsole 远程监控配置来查看

线程运行原理

JVM 中由堆、栈、方法区所组成,其中栈就是给线程使用的。

方法调用时,就会对该方法产生一个栈帧,方法的局部变量都会在栈帧中存储。栈是后进先出,当method2执行完就会回收,在执行完同时会记录返回地址,然后在method1中继续执行。

线程之间的栈帧是相互独立的,之间互不干扰。

线程上下文切换

当上下文切换时,要保存当前的状态,因为可能是时间片用完了,此时线程还没有结束。Java中对应的就是程序计数器

start与run方法

启动一个线程必须要用start方法,如果直接调用类里面的run方法实际走的是main主线程。

线程start前getState()得到的是NEW

线程start后getState()得到的是RUNNABLE

public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("t1被启动");
            }
        };
        System.out.println(t1.getState());
        t1.start();
        System.out.println(t1.getState());
    }

sleep方法

在sleep期间调用getState()方法可以得到TIMED_WAITING

public static void main(String[] args) {
        Thread t1 = new Thread("线程1") {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        log.debug("线程1 state: {}", t1.getState());
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("线程1 state: {}", t1.getState());
    }

sleep打断

sleep可以使用interrupt方法打断,打断后会触发InterruptedException异常

public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("进入睡眠");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    log.debug("被唤醒");
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        Thread.sleep(1000);
        log.debug("打断");
        t1.interrupt();
    }

sleep防止cpu使用100%

在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield或 sleep 来让出cpu的使用权给其他程序

yield方法会把cpu的使用权让出去,然后调度执行其它线程。线程的调度最终还是依赖的操作系统的调度器。

join方法

该方法会等待线程的结束

static int r = 11;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        log.debug("主线程开始");
        Thread t1 = new Thread(() -> {
            sleep(1);
            r = 888;
        },"线程1");
        t1.start();
//        t1.join();
        log.debug(String.valueOf(r));
        log.debug("主线程线程结束");
    }

join没有使用时,返回的是11,若是使用join返回的是888,是主线程在同步等待线程1。

当然还有其他的方法等待

1.sleep方法等待线程1结束

2.利用FutureTask的get方法

join(long n)方法可以传入参数,等待线程运行结束,最多等待 n 毫秒,假如执行时间大于等待的时间,就会不再等待。那么该线程会直接结束吗?答案是不会。

如下代码

public class Test10 {
    static int r = 11;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        log.debug("主线程开始");
        Thread t1 = new Thread(() -> {
            sleep(2);
            r = 888;
            log.debug("线程1结束");
        },"线程1");
        t1.start();
        t1.join(1000);
        log.debug(String.valueOf(r));
        log.debug("主线程线程结束");
    }
}

输出结果,可以看到这里只是主线程不再等待。

16:28:53.360 c.Test10 [main] - 主线程开始
16:28:54.411 c.Test10 [main] - 11
16:28:54.411 c.Test10 [main] - 主线程线程结束
16:28:55.404 c.Test10 [线程1] - 线程1结束

interrupt 方法

interrupt可以用来打断处于阻塞状态的线程。在打断后,会有一个打断标记(布尔值)会提示是否被打断过,被打断过标记为true否则为false.
但是sleep、wait和join可以来清空打断标记。

代码如下

public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("sleep...");
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");
        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
        log.debug("打断标记:{}", t1.isInterrupted());
    }

线程被打断后并不会结束运行,有人就会问了,那我们如何在打断线程后关闭线程呢?答案就是利用打断标记去实现。

可以在线程的死循环之中加入一个判断去实现。

boolean interrupted = Thread.currentThread().isInterrupted();
                if(interrupted) {
                    log.debug("退出循环");
                    break;
                }

守护进程

Java 进程通常需要所有线程都运行结束,才会结束。

但是存在一种守护进程,只要其他非守护进程结束,守护进程就会结束。垃圾回收器就使用的守护进程。

线程的状态

操作系统层面(早期进程的状态)

  • 初始状态 在语言层面创建了线程对象,还未与操作系统线程关联
  • 可运行状态(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行任务。
  • 运行状态 获取了 CPU 时间片运行中的状态
  • 调用阻塞api使运行状态转为阻塞状态
  • 终止状态 表示线程已经执行完毕

Java API 层面

1、新建状态(New)

Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };
log.debug("t1 state {}", t1.getState());

2、就绪状态(Runnable)与运行状态(Running)

Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                while(true) { // runnable
                }
            }
        };
t2.start();
log.debug("t2 state {}", t2.getState());

3、阻塞状态(Blocked)

用一个线程拿到锁,使得当前线程没拿到锁会出现阻塞状态。

Thread t6 = new Thread("t6") {
            @Override
            public void run() {
                synchronized (TestState.class) { // blocked
                    try {
                        Thread.sleep(90000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
t6.start();

4、等待状态(Waiting)

等待一个未执行完成的线程

t2.join(); //等待状态

5、超时等待(Time_Waiting)

可以在指定的时间自行返回的。

6、终止状态(TERMINATED)

线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 终止的线程不可再次复生。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • Python多进程并发与多线程并发编程实例总结

    本文实例总结了Python多进程并发与多线程并发.分享给大家供大家参考,具体如下: 这里对python支持的几种并发方式进行简单的总结. Python支持的并发分为多线程并发与多进程并发(异步IO本文不涉及).概念上来说,多进程并发即运行多个独立的程序,优势在于并发处理的任务都由操作系统管理,不足之处在于程序与各进程之间的通信和数据共享不方便:多线程并发则由程序员管理并发处理的任务,这种并发方式可以方便地在线程间共享数据(前提是不能互斥).Python对多线程和多进程的支持都比一般编程语言更高级

  • Java的线程与进程以及线程的四种创建方式

    目录 问题描述 case代码截图 数据库DO controller定义 dao定义 mapper实现 mysql相关properties配置 数据库数据 测试结果 具体错误信息 解决 总结 问题描述 这里我想测试某个与springboot相关的问题,结果在搭建mybatis时,发现没有成功从数据库中获取数据,报错信息为 com.mysql.cj.exceptions.DataConversionException: Unsupported conversion from LONG to java

  • java高并发之理解进程和线程

    目录 进程 线程 进程与线程的一个简单解释 总结 进程 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.程序是指令.数据及其组织形式的描述,进程是程序的实体. 进程具有的特征: 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的 并发性:任何进程都可以同其他进行一起并发执行 独立性:进程是系统进行资源分配和调度的一个独立单位 结构性:进程由程序,数据和进程控制块三部分组成 我们经常使用wi

  • python并发编程之多进程、多线程、异步和协程详解

    最近学习python并发,于是对多进程.多线程.异步和协程做了个总结. 一.多线程 多线程就是允许一个进程内存在多个控制权,以便让多个函数同时处于激活状态,从而让多个函数的操作同时运行.即使是单CPU的计算机,也可以通过不停地在不同线程的指令间切换,从而造成多线程同时运行的效果. 多线程相当于一个并发(concunrrency)系统.并发系统一般同时执行多个任务.如果多个任务可以共享资源,特别是同时写入某个变量的时候,就需要解决同步的问题,比如多线程火车售票系统:两个指令,一个指令检查票是否卖完

  • 详解JUC并发编程中的进程与线程学习

    目录 进程与线程 进程 线程 同步异步 串行并行执行时间 创建和运行线程 Thread 与 Runnable 的关系原理分析 查看进程 线程运行原理 线程上下文切换 start与run方法 sleep方法 sleep打断 join方法 interrupt 方法 守护进程 线程的状态 Java API 层面 总结 进程与线程 进程 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存.在指令运行过程中还需要用到磁盘.网络等设备.进程就是用来加载指令.管理内

  • 详解Java多线程编程中LockSupport类的线程阻塞用法

    LockSupport是用来创建锁和其他同步类的基本线程阻塞原语. LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而且park()和unpark()不会遇到"Thread.suspend 和 Thread.resume所可能引发的死锁"问题. 因为park() 和 unpark()有许可的存在:调用 park() 的线程和另一个试图将其 unpark() 的线程之间的竞争将保持活性. 基本用法 LockSupport 很类似于二元信号

  • 详解Java并发编程基础之volatile

    目录 一.volatile的定义和实现原理 1.Java并发模型采用的方式 2.volatile的定义 3.volatile的底层实现原理 二.volatile的内存语义 1.volatile的特性 2.volatile写-读建立的happens-before关系 3.volatile的写/读内存语义 三.volatile内存语义的实现 1.volatile重排序规则 2.内存屏障 3.内存屏障示例 四.volatile与死循环问题 五.volatile对于复合操作非原子性问题 一.volati

  • 详解Java并发编程之原子类

    目录 原子数组 AtomicIntegerArray 原子更新器 AtomicIntegerFieldUpdater 原子累加器 LongAdder 原子数组 原子数组有AtomicIntegerArray.AtomicLongArray.AtomicReferenceArray,主要是用来对数组中的某个元素进行原子操作.三个类的方法基本类似,这里只介绍一下AtomicIntegerArray的方法. AtomicIntegerArray 两个构造方法,第一个构造方法传入数组长度初始化一个所有值

  • 详解C++模板编程中typename用法

    typename的常规用法 typename在C++类模板或者函数模板中经常使用的关键字,此时作用和class相同,只是定义模板参数:在下面的例子中,该函数实现泛型交换数据,即交换两个数据的内容,数据的类型由_Tp决定. template <typename _Tp> inline void swap(_Tp& __a, _Tp& __b) { _Tp __tmp = __a; __a = __b; __b = __tmp; } typename的第二个用法:修饰类型 限定名和

  • 详解Java多线程编程中的线程同步方法

    1.多线程的同步: 1.1.同步机制: 在多线程中,可能有多个线程试图访问一个有限的资源,必须预防这种情况的发生.所以引入了同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问. 1.2.共享成员变量的例子: 成员变量与局部变量: 成员变量: 如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作,这多个线程是共享一个成员变量的. 局部变量: 如果一个变量是局部变量,那么多个线程对同一个对象进行操作,每个线程都会有一个该局部变量的拷贝.他们

  • 详解java并发编程(2) --Synchronized与Volatile区别

    1 Synchronized 在多线程并发中synchronized一直是元老级别的角色.利用synchronized来实现同步具体有一下三种表现形式: 对于普通的同步方法,锁是当前实例对象. 对于静态同步方法,锁是当前类的class对象. 对于同步方法块,锁是synchronized括号里配置的对象. 当一个代码,方法或者类被synchronized修饰以后.当一个线程试图访问同步代码块的时候,它首先必须得到锁,退出或抛出异常的时候必须释放锁.那么这样做有什么好处呢? 它主要确保多个线程在同一

  • 详解JavaScript异步编程中jQuery的promise对象的作用

    Promise, 中文可以理解为愿望,代表单个操作完成的最终结果.一个Promise拥有三种状态:分别是unfulfilled(未满足的).fulfilled(满足的).failed(失败的),fulfilled状态和failed状态都可以被监听.一个愿望可以从未满足状态变为满足或者失败状态,一旦一个愿望处于满足或者失败状态,其状态将不可再变化.这种"不可改变"的特性对于一个Promise来说非常的重要,它可以避免Promise的状态监听器修改一个Promise的状态导致别的监听器的行

  • 详解Python并发编程之创建多线程的几种方法

    大家好,并发编程 今天开始进入第二篇. 今天的内容会比较基础,主要是为了让新手也能无障碍地阅读,所以还是要再巩固下基础.学完了基础,你们也就能很顺畅地跟着我的思路理解以后的文章. 本文目录 学会使用函数创建多线程 学会使用类创建多线程 多线程:必学函数讲解 经过总结,Python创建多线程主要有如下两种方法: 函数 类 接下来,我们就来揭开多线程的神秘面纱. . 学会使用函数创建多线程 在Python3中,Python提供了一个内置模块 threading.Thread,可以很方便地让我们创建多

  • 详解Python并发编程之从性能角度来初探并发编程

    . 前言 作为进阶系列的一个分支「并发编程」,我觉得这是每个程序员都应该会的. 并发编程 这个系列,我准备了将近一个星期,从知识点梳理,到思考要举哪些例子才能更加让人容易吃透这些知识点.希望呈现出来的效果真能如想象中的那样,对小白也一样的友好. 昨天大致整理了下,这个系列我大概会讲如下内容(后期可能调整): 对于并发编程,Python的实现,总结了一下,大致有如下三种方法: 多线程 多进程 协程(生成器) 在之后的章节里,将陆陆续续地给大家介绍到这三个知识点. . 并发编程的基本概念 在开始讲解

随机推荐