java虚拟机中多线程总结

我记得最开始接触多进程,多线程这一块的时候我不是怎么理解,为什么要有多线程啊?多线程到底是个什么鬼啊?我一个程序好好的就可以运行为什么要用到多线程啊?反正我是十分费解,即使过了很长时间我还是不是很懂,听别人说过也自己试过,但总是没有理解透彻;

时间过了很久感觉现在对多线程有了一点新的理解,我们还是从最基本的开始,顺便看看从jvm的角度看看多线程在jvm中是怎么分配内存的,顺便和前面的几篇内容串一下;

1.现实中的多线程

举个例子:假如你一个人在家,你现在听首歌5分钟,烧开水需要10分钟,玩一局游戏要20分钟,现在问题来了,你完成这三件事总共需要多少分钟?

假如是小学生肯定会回答5+10+20=35分钟啊,但我们比小学生牛一点,稍微思考一下就知道是20分钟,因为三件事可以同时做嘛,玩游戏的同时可以听歌,顺便烧开水,一把游戏打完,歌听完了,水也烧开然后可以去泡茶了,舒服!

我们用一个比较简陋的图看看这两种方式(这里先不考虑并发与并行的区别,方便理解)

可以粗略的看到如果是小学生的话,要一件事一件事的做,最后花的时间是三者时间总和;而我们比较聪明,由于三件事互不影响,我们可以三件事同时开始做,这样就大大减少了不必要的等待时间,最终三者花费的时间差不多就是最长的那一个。

这里稍微提一下并发和并行的区别;

并发:这个是在计算机单核CPU的前提之下,我们要清楚一个CPU在某一时刻只能做一件事,但是现在有三件事(听歌,烧开水,玩游戏)交给CPU做,CPU是个好人,任劳任怨,一下子去听歌,一下子去烧开水,一下子玩游戏,最终可以把三件事都给做完,但是假如同时有几百件事交给CPU做呢?emmmm,最后CPU就被累垮了,住院去了,于是我们计算机也卡死了;举个最贴近我们的例子:以前上学的时候作业太多,很多时候都是很多科目的作业都没有做完,那怎么办呢?只有早上去早点去抄一下同学的,但是各个课代表来收作业了,于是只能这个科目作业抄一点马上又把另外一个科目作业抄一点,玛德,最后终于在规定时间都抄完了,可是假如你有100个科目的作业没做完,你会怎么办?用命去抄也抄不完了,于是你就累病了。。。。

并行:多核CPU的前提,现在一个电脑都有多个CPU,那么CPU同时就可以做多件事,即使事情再多,多个CPU进行切换最终花费的时间确实大大减少;还是说说上面抄作业的例子,假如你现在有10门科目的作业没做完,就靠你一个头脑一只手肯定来不及啊!于是这个时候你唤醒了前世的记忆,原来你是哪吒转世,特么的居然可以变成是三头六臂,这得可以同时抄多少份作业啊!!!一下子作业就做完了,舒服!但是这个时候作业科目太多的话你即使有三头六臂也不够用啊,而且相互之间的协调也就变成一个很重要的问题。

并发和并行就是这个意思,我们现在只关注并发,看看在单核CPU的计算机中一个程序是怎么运行的?

2.进程和线程

想想什么叫做进程呢?我的理解就是程序进入了内存就是进程,比如我们电脑桌面双击QQ,优酷,java虚拟机等,操作系统就会把这些软件的内容加载到内存中去运行去了,然后就是运行某编程语言写的代码,转化为机器码调用操作系统的接口,然后操作系统的内核会那些硬件驱动程序发出一些指令,然后我们的电脑屏幕就出现变化了。。。我们简单画一画图,我们主要看JVM

我们再进入JVM中看看,其中线程1、2、3就是我们在java代码中要去实现的;

进程:我们百度一下进程的定义,最重要的一点就是进程是操作系统资源分配的基本单位,因为每启动一个程序,一个进程就创建了,在操作系统堆内存空间上就开辟了一块空间,也就是分配了资源。

线程:现在再来看线程,百度一下线程定义,其实就是说:进程就是一个程序,这个程序之中可能会同时执行多个任务的代码,每一个任务就是一个线程,而且每一个线程都会在JVM中有自己独立的java栈,pc寄存器,而且CPU只能切换线程,即使是不同程序的线程也可以相互切换。

这里就要说明一下,想比进程和线程,创建一个进程是要在操作系统内存中去开辟空间,会涉及到对操作系统一些函数的调用,而创建一个线程(比如在JVM中)只需要在jvm中个部分开辟空间,相比较之下,肯定是创建线程所耗费的操作系统资源比较少,但是也不可能无限制的创建很多线程,不然jvm也会出问题!

我随便查了一下,一般的web服务器线程数最大不能超过CPU核数*50,如:8核 < 300,16核 < 800,根据实际情况还可以适当调一下。

记得有句话叫做多个线程之间会竞争CPU资源这句话当初我可是很久都没有理解,这竞争CPU资源到底什么鬼?CPU的资源到底是什么啊?emmmm..

记得以前家里比较穷,没有像现在一样手机电脑这么多,家里只有一个电视!但是有的时候家里人每个人喜欢看的节目都不一样,于是不可避免的相互之间就为了争这个遥控器而发生冲突,哈哈哈!这个时候遥控器就相当于CPU,我们每个人都相当于一个线程要完成自己的事情。但是遥控器就一个,就会相互抢遥控器,有的时候我抢过来遥控器看火影忍者没到一分钟,就被我姐抢去看美食节目,没过一会儿遥控器就被我爸抢去看新闻去了。。。。。

3.java中的多线程用法

java之中用多线程主要是3种方式:类,接口,线程池,接下来我们就随意看一下这三种方式

3.1.类

这种方式主要是继承Thread类,实现run()方法,run()方法就是我们所需要做的任务的逻辑代码,然后将这个类实例化调用start()方法,表示现在这个线程随时可以被CPU调用;

我还是以上面玩游戏,烧开水和听歌为例,随意写个小例子:

注意:这里先不看GC,前台线程有四个线程,我们创建的三个,还有执行main方法的这个线程(这个也叫主线程),我们只能保证主线程最优先运行,至于这四个线程哪个先停止,随机。。。

3.2.接口

这种方式也差不多,实现Runnable接口,实现其中的run()方法,然后实例化这个对象并传入Thread类中,再调用start()方法;

3.3.线程池

什么是线程池呢?你看看我们上面写的创建线程的方法,都是用的时候就去创建,用完了就销毁,下次又要用就又去创建,这种做法很不好,因为每次创建和销毁线程都是很消耗jvm内部资源的,因为在jvm内部会进行申请空间,分配空间和释放空间各种操作,对jvm的性能会有一定的影响,而且假如某个特殊的情况下每个线程只会运行很短的时间就会结束,那么就会十分频繁的常见和销毁线程,导致在jvm中频繁的申请和释放内存,这极大的影响jvm的运行性能。

但是啊,如果我们能在程序启动的时候,就先创建一定数量的线程放在一个池子里,我们要用的话就去拿,用完了就再放到池子里,这样就很好的避免了创建和销毁线程的过程,这种方式比较友好;其中这个存放线程的池子就叫做线程池,接下来我们随意看看线程池的用法:

顺便一提,利用线程池执行线程任务有两种方式,一种是我们用的pool.execute(xxxx),另外一种是pool.submit(xxxx),用法和参数都一样,区别是用submit()提交内部其实还是调用execute(),而且还可以获取线程执行后的返回值,后面我们会分析到的;

线程池起到一个类似缓冲的作用,它可以对池子中的线程数目进行控制,想想,假如我们程序直接创建线程那可能会由于创建线程太多导致jvm崩溃,但是我们有一个确定容量的池子,我们不用担心这个池子会炸了,我们只需要从池子里拿就好了,至于拿不拿得到的问题后面我们会好好分析的;

3.4.看看Callable接口

这个接口干嘛的呢?有了Runnable接口了,还要这个接口干嘛?

不知道有没有注意到那个Runnable接口的run()方法是没有返回值的,也就是说我们只能把任务交给这个线程去做,但是做了之后有没有成功,线程是否异常我们都是不知道的,于是才有了Callable接口,这个接口就是对Runnable接口的一个补充,这个接口的实现类中没有run()方法,却有一个call()方法用于执行我们的任务逻辑,而且还能有返回值,并且能抛出异常等

顺便一提,返回值已经被封装成一个Future<T>类型的了,我们只需要从这个Futrue中取到返回值就可以进行后续操作了,有兴趣的可以看看Futrue这个包装类中有哪些方法可以试试,反正我暂时是没什么兴趣的。。。。

4.多线程下的jvm内存结构

初学者学多线程其实最迷糊的一点就是多线程的程序中,jvm是什么样的啊?还是向以前那样分吗?到底多线程这个东西在jvm中是怎么样存在的呢?下面我们就来简单看看;

我自己总结的一句话:一个线程一个栈,一个方法一个帧;

这句话的意思就是每创建一个线程就会创建一个栈,每调用一个方法就会在栈中压入一个栈帧;

其实java栈是一个动态的东西,不像我们前面看jvm内存结构就是一大块java栈,里面可以有很多块,一个线程一块,总共合起来叫做java栈,我继续来画一个丑陋的图看一看:

其实可以看到在我们java程序中用多线程的话,那么每一个线程都会创建一个栈,同时每个线程都有自己的PC计数器,而且每个栈都是该线程私有的,别的线程不能访问;但是在java堆和方法区中的数据,是所有线程共享的,由于所有的线程都能够使用共享区的数据,假设一个线程拿到堆中的一个A对象进行修改但是需要的时间比较长,此时另一个线程也要拿到A对象进行判断然后做一些操作,这个时候就会出问题,因为前一个线程修改的数据还没有同步过来,后面线程拿到的是旧数据,这个问题就是多线程的同步问题,后面我们慢慢分析;

 5.总结

其实初学者觉得多线程比较难,主要是因为不理解多线程到底是什么?我们可以把多线程代码用这种奇葩的形式看是不是明显多了,其中主线程最开始执行并创建自己的栈和PC计数器,一直到创建其他的三个线程并把分别调用start()方法的时候,这些线程会随机由CPU执行以及切换线程,并且各个线程都会创建自己的栈和PC计数器;而堆和方法区的数据是共享的,这会导致出现线程同步问题;

注意:千万不要觉得主线程比其他创建的线程要特殊,除了我们程序是由主线程开始之外,这些线程都是出于同一地位,很有可能首先是主线程执行完毕,然后再执行1、2、3这三个线程哦~~

(0)

相关推荐

  • 浅谈java多线程wait,notify

    前言 1.因为涉及到对象锁,Wait.Notify一定要在synchronized里面进行使用. 2.Wait必须暂定当前正在执行的线程,并释放资源锁,让其他线程可以有机会运行 3.notify/notifyall: 唤醒线程 共享变量 public class ShareEntity { private String name; // 线程通讯标识 private Boolean flag = false; public ShareEntity() { } public String getN

  • java多线程之停止线程的方法实例代码详解

    和线程停止相关的三个方法 /* 中断线程.如果线程被wait(),join(),sleep()等方法阻塞,调用interrupt()会清除线程中断状态,并收到InterruptedException异常.另外interrupt();对于isAlive()返回false的线程不起作用. */ public void interrupt(); /* 静态方法,判断线程中断状态,并且会清除线程的中断状态.所以连续多次调用该方法,第二次之后必定返回false.另外,isAlive()用于判断线程是否处于

  • Java 实现多线程切换等待唤醒交替打印奇偶数

    引言 在日常工作生活中,可能会有用时几个人或是很多人干同一件事,在java编程中,同样也会出现类似的情况,多个线程干同样一个活儿,比如火车站买票系统不能多个人买一到的是同一张票,当某个窗口(线程)在卖某一张票的时候,别的窗口(线程)不允许再卖此张票了,在此过程中涉及到一个锁和资源等待的问题,如何合理正确的让线程与线程在干同一件事的过程中,不会抢资源以及一个一直等待一个一直干活的状况,接下来就聊一下多线程的等待唤醒以及切换的过程,在此就以A和B两个线程交替打印奇偶数的例子为例,代码如下: pack

  • Java多线程之volatile关键字及内存屏障实例解析

    前面一篇文章在介绍Java内存模型的三大特性(原子性.可见性.有序性)时,在可见性和有序性中都提到了volatile关键字,那这篇文章就来介绍volatile关键字的内存语义以及实现其特性的内存屏障. volatile是JVM提供的一种最轻量级的同步机制,因为Java内存模型为volatile定义特殊的访问规则,使其可以实现Java内存模型中的两大特性:可见性和有序性.正因为volatile关键字具有这两大特性,所以我们可以使用volatile关键字解决多线程中的某些同步问题. volatile

  • Java多线程之CAS算法实现线程安全

    前言 对于线程安全,我们有说不尽的话题.大多数保证线程安全的方法是添加各种类型锁,使用各种同步机制,用限制对共享的.可变的类变量并发访问的方式来保证线程安全.文本从另一个角度,使用"比较交换算法"(CompareAndSwap)实现同样的需求.我们实现一个简单的"栈",并逐步重构代码来进行讲解. 本文通俗易懂,不会涉及到过多的底层知识,适合初学者阅读(言外之意是各位大神可以绕道了). 旅程开始 1.先定个小目标,实现一个"栈" "栈&q

  • Java多线程批量数据导入的方法详解

    前言: 当遇到大量数据导入时,为了提高处理的速度,可以选择使用多线程来批量处理这些处理.常见的场景有: 大文件导入数据库(这个文件不一定是标准的CSV可导入文件或者需要在内存中经过一定的处理) 数据同步(从第三方接口拉取数据处理后写入自己的数据库) 以上的场景有一个共性,这类数据导入的场景简单来说就是将数据从一个数据源移动到另外一个数据源,而其中必定可以分为两步 数据读取:从数据源读取数据到内存 数据写入:将内存中的数据写入到另外一个数据源,可能存在数据处理 而且根据读取的速度一般会比数据写入的

  • Java多线程中wait、notify、notifyAll使用详解

    基础知识 首先我们需要知道,这几个都是Object对象的方法.换言之,Java中所有的对象都有这些方法. public final native void notify(); public final native void notifyAll(); public final native void wait(long timeout) throws InterruptedException; public final void wait() throws InterruptedExceptio

  • java虚拟机中多线程总结

    我记得最开始接触多进程,多线程这一块的时候我不是怎么理解,为什么要有多线程啊?多线程到底是个什么鬼啊?我一个程序好好的就可以运行为什么要用到多线程啊?反正我是十分费解,即使过了很长时间我还是不是很懂,听别人说过也自己试过,但总是没有理解透彻: 时间过了很久感觉现在对多线程有了一点新的理解,我们还是从最基本的开始,顺便看看从jvm的角度看看多线程在jvm中是怎么分配内存的,顺便和前面的几篇内容串一下: 1.现实中的多线程 举个例子:假如你一个人在家,你现在听首歌5分钟,烧开水需要10分钟,玩一局游

  • 作为程序员必须掌握的Java虚拟机中的22个重难点(推荐0

    Java虚拟机一直是比较重要的知识点,是Java高级开发必会的.本文为你总结了关于JVM的22个重点.难点,图文并茂的向你展示和JVM有关的重点知识.全文共7000字左右. 概念 虚拟机:指以软件的方式模拟具有完整硬件系统功能.运行在一个完全隔离环境中的完整计算机系统 ,是物理机的软件实现.常用的虚拟机有VMWare,Visual Box,Java Virtual Machine(Java虚拟机,简称JVM). Java虚拟机阵营:Sun HotSpot VM.BEA JRockit VM.IB

  • java 虚拟机中对象访问详解

    java 虚拟机中对象访问详解 对象访问会涉及到Java栈.Java堆.方法区这三个内存区域. 如下面这句代码: Object objectRef = new Object(); 假设这句代码出现在方法体中,"Object objectRef" 这部分将会反映到Java栈的本地变量中,作为一个reference类型数据出现.而"new Object()"这部分将会反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的

  • java虚拟机中栈的运行知识点总结

    运行原理 1.不同线程中所包含的栈帧是不允许存在相互引用的. 2.如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给当前一个栈针,并且虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧. 3.Java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令:另一种是抛出异常.不管使用哪种方式,都会导致栈帧被弹出. 实例 public class StackFrameTest { public static void main(String[] args) {

  • 在Java程序中使用数据库的新方法

    Java 8终于到来了! 经过几年的等待, java程序员终于能在java中得到函数式编程的支持了. 函数式编程的支持能流程化现有的代码并且为java提供强大的能力.在这些新特性中最瞩目的是java程序员对数据库的操作方式.函数式编程带来了令人激动的简便高效的数据库API. Java 8 将会支持可与像C#的LINQ等语言竞争的新的数据库访问方式. 处理数据的函数式方式 Java 8 不仅仅添加了函数式支持,它也通过新的函数式处理数据的方式扩展了集合(Collection)类. 而通常情况下ja

  • java虚拟机深入学习之内存管理机制

    前言 前面说过了类的加载机制,里面讲到了类的初始化中时用到了一部分内存管理的知识,这里让我们来看下Java虚拟机是如何管理内存的. 先让我们来看张图 有些文章中对线程隔离区还称之为线程独占区,其实是一个意思了.下面让我们来详细介绍下这五部分: 运行时数据区 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都拥有自己的用途,并随着JVM进程的启动或者用户线程的启动和结束建立和销毁. 先让我们了解下进程和线程的区别: 进程是资源分配的最小单位,线程是程序

  • Java虚拟机内存区域划分详解

    在谈 JVM 内存区域划分之前,我们先来看一下 Java 程序的具体执行过程,我画了一幅图. Java 源代码文件经过编译器编译后生成字节码文件,然后交给 JVM 的类加载器,加载完毕后,交给执行引擎执行.在整个执行的过程中,JVM 会用一块空间来存储程序执行期间需要用到的数据,这块空间一般被称为运行时数据区,也就是常说的 JVM 内存. 所以,当我们在谈 JVM 内存区域划分的时候,其实谈的就是这块空间--运行时数据区. 大家应该对官方出品的<Java 虚拟机规范>有所了解吧?了解这个规范可

  • 深入理解Java虚拟机 JVM 内存结构

    目录 前言 JVM是什么 JVM内存结构概览 运行时数据区 程序计数器 Java虚拟机栈 本地方法栈 方法区 运行时常量池 Java堆 直接内存 前言 JVM是Java中比较难理解和掌握的一部分,也是面试中被问的比较多的,掌握好JVM底层原理有助于我们在开发中写出效率更高的代码,可以让我们面对OutOfMemoryError时不再一脸懵逼,可以用掌握的JVM知识去查找分析问题.去进行JVM的调优.去让我们的应用程序可以支持更高的并发量等......总之一句话,学好JVM很重要! JVM是什么 J

  • Java 天生就是多线程

    目录 一.Java 中的线程 1.启动 2.中止 3.阻塞 4.深入理解run 和 start 5.join 方法 6.线程优先级 7.守护线程 8.synchronized 内置锁 9.对象锁和类锁 二.总结 一.Java 中的线程 一个Java 程序从main() 方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上Java 程序天生就是多线程程序,因为执行main() 方法的是一个名称为main 的线程. public static void main(String[]

  • 解析Java虚拟机中类的初始化及加载器的父委托机制

    类的初始化 在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值. 在程序中,静态变量的初始化有两种途径: 1.在静态变量的声明处进行初始化: 2.在静态代码块中进行初始化. 没有经过显式初始化的静态变量将原有的值. 一个比较奇怪的例子: package com.mengdd.classloader; class Singleton { // private static Singleton mInstance = new Singleton();// 位置1 // 位置1输

随机推荐