java编程对cpu的实际需求有多少

PHP是世界上最好的语言[]。

编程语言层面上的东西根本不是重点,把基本功打好,学一门新的语言就是信手拈来。

上一篇文章《》讨论了如何熟悉并利用领域知识和计算机知识编写更高质量代码。这一篇也是从一个非常简单的编程问题开始,通过分析针对这个问题的不同实现方式,阐释学习一门语言都要学些什么以及怎么样才算熟悉了一门语言。

注:本文无意嘲讽《21天精通XXXX》系列,毕竟或许还是有人可以做到的,尤其是在现在这种有人只要用过就敢妄称精通的大环境下。本文只是想说明一些显而易见却又被人熟视无睹的事实。

不少人可能毕业之后就从来没写过哪怕排序之类的基本算法,这很正常,但是没人用不到各类Containers(Collections)。而且,但凡是代码写得足够多又不勤于复制粘贴的懒人,就少不得写些工具类把一些常用操作独立出来。比如这样一个函数:

我常常拿这个简单的函数当面试题,基本没有人猜不出来这个函数是要干什么,倒是会有个别人表示不知道前面的<T>是干什么用的。有性子急的可能很迅速地就构思出了如下实现,然后下一秒就发现坑在哪儿了。(简洁起见,本文所有代码略去参数为null之类的边界检查):

显然上面的代码结果是不正确的。我们下面就来一步步地分析,看这个函数在Java里应该如何实现,都有哪些坑。主要意图是在这个过程体会,怎样才是充分地并合理地利用一个语言本身的理念与机制去解决问题。我也建议各位看官也先想一想自己心中的答案是什么再往下看。

一说到“正确”,一说到“方案”,不少有经验的程序员的标准回答就是“看情况(It depends)”。看什么情况?看到底是要解决什么问题。我曾经也很喜欢这样回答问题,听上去多特么严谨。但是有时候这么说就不合适了,比如面试的时候这么回答就是犯傻,把到手的按自己能力方向展开并引导面试节奏的机会拱手还给了面试官。知道什么情况就说什么。没有具体上下文,要么自己假设一个;要么说自己的实现方式为了通用起见,所以牺牲了这些、这些、这些。即使不是面试,也不要说“看情况”,除非你真打算把情况一一列举出来并分情况讨论,你乐意说可能听的人也没耐心听,因为问问题的人一般只需要其中一个情况,你不嫌累,听者也会嫌烦。

回到问题上来。所以这个函数的需求是什么?需求就是函数签名所描述的那些。就像的问题就在于不能有效提高使用者的智商。”。Java不仅能,而且擅长。

想我写了快十年C#代码,其间几乎没有细想过List的实现是用数组更好还是用链表更好这个问题(毕竟也很少直接用List)。然而写Java的第一天我就意识到了。很火。其中有一个排名不高的的回答有些碍眼。他说,LinkedList的作者(此人也是Effective JavaJava Concurrency in Practice两本书的作者),发过:

自动地为程序员们做了很多事情,而且做得很出色。但是做得太好,好到程序员代码写烂了它都能工作得还不错,结果不小心把不少.NET程序员都给惯傻了,傻到出点儿问题都不知道怎么下手处理,然后还反过来骂.NET是垃圾。而Java程序员从一上手写代码开始,就要开始解决这样那样的问题,水平慢慢地就被迫堆高了。其后果就是.NET始终被Java压制,这大概是微软始料未及的吧。

在引言里说过,这篇文章只是想说说那些显而易见的事儿。然而一千个人眼中有一千个哈姆雷特。所以我担心不说得清楚些,还是会有人不知道我在说在什么。本文的目的不是解释这个except函数在Java里怎么写也不是为了展示Java里多样的写法。本文要说的那些事是:

  1. 编程没有简单问题,再简单的问题都有多种解法适用于不同的环境。
  2. 学习一门语言从来不是一个简单的事儿。但是学习到这个语言的公共部分是个很简单的事儿(就像大牛们宣扬的,你基本功扎实的话,一两天的事儿)。
  3. 学习一门语言到公共部分就可以解决你工作上的所有问题,而且在这个层次,的确可以说用什么语言写代码都是无所谓的。(因为你总是可以自己搞定剩下的语言特定部分——大凡都通过重新造轮子的方式。)
  4. 深入学习一个语言,从来不是一个简单的事儿。(那怕简单如Java都是这样,我也没有深入学习过Java,我只是简单了解了一下JCF,然后看了点Best Practice而已。)
  5. 在你把一个语言学到一定深度之前,你可能会看到一些代码看不太懂。这个时候正确的积极的心态是承认自己的水平不够,而不是抱怨这个代码的可读性太差、搞得别人都看不懂。
  6. 最好等真精通了一门或几门语言之后再谈论什么一通百通,否则对于个人成长而言遗患无穷。(以Java为类,感觉对java.lang,java.util,包里的所有最新的类的所有方法都了解并能合理使用才能算熟悉级别的吧。)
  7. 选择一门表达力强并且适用你个人理解力极限的编程语言,可以提高效率、节省时间、延长生命、早下班、赚更多的钱。(没最后两点大概没有人关心吧。)
  8. 公司不幸用了Java你也得适应。

操作系统概第七版中文版习题答案(全)

1.1在多道程序和分时环境中,多个用户同时共享一个系统,这种情况导致多种安全问题。a. 列出此类的问题 b.在一个分时机器中,能否确保像在专用机器上一样的安全度?并解释之。

答:a.窃取或者复制某用户的程序或数据;没有合理的预算来使用资源(CPU,内存,磁盘空间,外围设备)b.应该不行,因为人类设计的任何保护机制都会不可避免的被另外的人所破译,而且很自信的认为程序本身的实现是正确的是一件困难的事。

1.2资源的利用问题在各种各样的操作系统中出现。试例举在下列的环境中哪种资源必须被严格的管理。(a)大型电脑或迷你电脑系统(b)与服务器相联的工作站(c)手持电脑

答:(a)大型电脑或迷你电脑系统:内存和CPU资源,外存,网络带宽(b)与服务器相联的工作站:内存和CPU资源(c)手持电脑:功率消耗,内存资源

1.3在什么情况下一个用户使用一个分时系统比使用一台个人计算机或单用户工作站更好?

答:当另外使用分时系统的用户较少时,任务十分巨大,硬件速度很快,分时系统有意义。充分利用该系统可以对用户的问题产生影响。比起个人电脑,问题可以被更快的解决。还有一种可能发生的情况是在同一时间有许多另外的用户在同一时间使用资源。当作业足够小,且能在个人计算机上合理的运行时,以及当个人计算机的性能能够充分的运行程序来达到用户的满意时,个人计算机是最好的,。

1.4在下面举出的三个功能中,哪个功能在下列两种环境下,(a)手持装置(b)实时系统需要操作系统的支持?(a)批处理程序(b)虚拟存储器(c)分时

答:对于实时系统来说,操作系统需要以一种公平的方式支持虚拟存储器和分时系统。对于手持系统,操作系统需要提供虚拟存储器,但是不需要提供分时系统。批处理程序在两种环境中都是非必需的。

1.5描述对称多处理(SMP)和非对称多处理之间的区别。多处理系统的三个优点和一个缺点?

答:SMP意味着所以处理器都对等,而且I/O可以在任何处理器上运行。非对称多处理有一个主处理器控制系统,与剩下的处理器是随从关系。主处理器为从处理器安排工作,而且I/O也只在主处理器上运行。多处理器系统能比单处理器系统节省资金,这是因为他们能共享外设,大容量存储和电源供给。它们可以更快速的运行程序和增加可靠性。多处理器系统能比单处理器系统在软、硬件上也更复杂(增加计算量、规模经济、增加可靠性)

1.6集群系统与多道程序系统的区别是什么?两台机器属于一个集群来协作提供一个高可靠性的服务器的要求是什么?

答:集群系统是由多个计算机耦合成单一系统并分布于整个集群来完成计算任务。另一方面,多道程序系统可以被看做是一个有多个CPU组成的单一的物理实体。集群系统的耦合度比多道程序系统的要低。集群系统通过消息进行通信,而多道程序系统是通过共享的存储空间。为了两台处理器提供较高的可靠性服务,两台机器上的状态必须被复制,并且要持续的更新。当一台处理器出现故障时,另一台处理器能够接管故障处理的功能。

答:客户机-服务器(client-server)模型可以由客户机和服务器的角色被区分。在这种模型下,客户机向服务器发出请求,然后服务器满足这种请求。对等系统(peer-to-peer)模

许多以Java多线程开发为主题的技术书籍,都会把对Java虚拟机和Java内存模型的讲解,作为讲授Java并发编程开发的主要内容,有的还深入到计算机系统的内存、CPU、缓存等予以说明。实际上,在实际的Java开发工作中,仅仅了解并发编程的创建、启动、管理和通信等基本知识还是不够的。一方面,如果要开发出高效、安全的并发程序,就必须深入Java内存模型和Java虚拟机的工作原理,从底层了解并发编程的实质;更进一步地,在现今大数据的时代,要开发出高并发、高可用、考可靠的分布式应用及各种中间件,更需要深入到计算机工作原理的底层去进行代码开发。

本文尝试以一个较为全面的角度,以Java虚拟机工作原理和Java内存模型为切入,配合一些计算机CPU缓存的知识,深入理解Java多线程开发中的难点,包括线程安全和线程通信等内容。
逻辑上来说,大部分计算机系统的高级编程语言及其编译器、虚拟机等构件,都是来源于计算机硬件系统的原理和要求,而不是相反。Java虚拟机和并发编程原理也不例外,因此第一部分先介绍一下困扰许多初学者的Java多线程开发的源头——CPU缓存模型。
计算机中,所有的计算都是在CPU寄存器中完成,而指令完成所需要的数据读取和写入,都需要从RAM主存获取。受硬件工艺的影响,现在的CPU处理速度已经远远超过主存的访问速度,差额基本是成千上万的差距。
因此,CPU缓存设计应运而生。如下为CPU缓存架构图和CPU缓存与主存的速度对比:

使用CPU缓存来处理数据的步骤大致为:
1. 把需要的数据从主存复制一份到CPU缓存中;
2. CPU从缓存中读取数据并计算;
3. 计算完成的数据刷新到主存中。
如上的工作机制,会在多线程环境下导致缓存不一致的问题。为此,使用“总线加锁”(已淘汰)和“缓存一致性协议”来解决,它大致的思想是:
当CPU操作缓存中的数据时,如果发现该变量是一个共享变量,意味着其它缓存中也会有这个变量的副本,然后——
1. 如果是读操作,不做任何处理,只是从缓存中读取数据到寄存器
2. 如果是写操作,发出信号通知其它CPU将该变量的cache line置为无效状态,其它CPU在运行该变量读取的时候需要从主存更新数据。

受许多资料和书籍讲述不严谨所致,很多初学者往往简单地把Java虚拟机理解为类似编译器甚至解释器的存在,把Java虚拟机当做黑盒,认为输入了Java源代码,就可以输出计算机直接跑的程序了;因为JVM在不同操作系统上都有实现,所以可以做到“一份代码,多种机器运行的效果”。这样理解对小白或者外行人来说可能OK,但对于有想法深入学习Java的小伙伴,是远远不够的。
事实上,Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。包括编译器以及JRE在内的整套体系,构成了完整的JVM。JVM原生支持包括Java、Scala、Kotlin在内的语言编译后运行。而其中,JRE又是JVM的核心部分。JRE的体系结构图如下:

程序计数器:线程私有,每个线程都有独立的程序计数器,用于存放当前线程接下来将要执行的字节码指令、分支、循环、跳转、异常处理等信息。
Java虚拟机栈:线程私有,生命周期与线程相同。线程运行中,执行方法时都会创建“栈帧”,用于存放局部变量表、操作栈、动态链接、方法出口等信息。虚拟机栈的大小可以通过-xss来配置,需要特别注意的是:方法的调用是栈帧被压入和弹出的过程。在一定的容量之下,如果局部变量表等占用的内存越小,则可被压入的栈帧就越多,反之亦然。栈帧的内存大小称为宽度,栈帧的数量则称为深度,两者成反比。
本地方法栈:线程私有,JVM为本地方法(Java Native Interface, C/C++实现的程序)所划分的内存区域,用于被线程调用诸如网络通信、文件操作等方法。
堆:所有线程共享,Java运行期间几乎所有对象都存储于此。堆内存也会被细分为新生代、老生代等子堆。
方法区:多个线程共享,存储那些在类的加载阶段(详见下文)已经被JVM加载的类信息、常量、静态变量、即时编译器JIT编译后的代码等数据。Java8中,改区的持久代内存改为元空间。
特别地,Java程序中线程的数量,受Java虚拟机栈和堆影响较大,可以粗略地认为:一个Java进程的内存大小=堆内存 + 线程数量 * 线程私有栈内存。结合操作系统特性,可以明确一个计算线程数量的公式:线程数=(最大地址空间MaxProcessMemory - JVM堆内存 - 系统保留内存ReservedOsMemory)/ThreadStackSize(XSS)
当Java源文件经过javac编译完成,生成类文件之后,首先会被类加载器即ClassLoader加载。ClassLoader的主要职责是加载编译好的类文件,在对应的内存区域中生成该类的各个数据结构。类的加载分为加载、连接初始化三个阶段,如图:


1. 加载:加载类的class文件
2.1 验证:确保class文件的正确性,如版本、魔术因子等
2.2 准备:为类的静态变量分配内存,并且初始化默认值
2.3 解析:把类中的符号引用转为直接引用
3. 初始化:为类的静态变量赋代码编写阶段锁赋的值
需要注意的是:类的加载实施的是懒加载,即用的时候才加载,并且在同一个运行时包下,一个类只会被初始化一次。
类的完整的生命周期,除了类加载,还包括使用和卸载。
关于使用,JVM定义了6种主动使用类的场景,会导致类的加载和初始化
new对象;访问类的静态变量(静态常量不会!);访问类的静态方法;使用反射;初始化子类会初始化父类;启动类
注意初始化一个类为元素的数组不会加载类。
类加载的最终产物,是堆内存中的Class对象。而对于同一个ClassLoader,不管类被加载多少次,指向的都是同一个Class对象
类被加载后在栈内存中的分布情况如图

通过CPU缓存和JVM工作模式的介绍,是为了引入Java内存模型的概念。Java内存模型(Java Memory Mode, JMM)定义了JVM如何与计算机的主存进行工作,理解JMM对正确理解Java多线程开发是十分重要的。JMM模型如下图所示:

Java内存模型的工作逻辑,与上面介绍到的CPU缓存一致性工作逻辑十分相似,其关于多线程的工作要点如下:
1. 共享变量存储于主内存中,每个线程都可以访问。
2. 每个线程都有私有的工作内存,或称本地内存。这只是个逻辑概念,其实质是涵盖了寄存器、缓存、编译器优化和硬件等。
3. 共享变量只以副本的形式,存储在本地内存中。
4. 线程不能直接操作主内存,只有操作了本地内存中的副本,才能刷新到主内存中。
5. 每个线程也不能操作其它线程的私有的本地内存
Java线程安全的实现
Java并发编程安全需要具备的三大特性:原子性、可见性和有序性。下面将介绍,基于JMM模型和Java线程安全的实现方式,是如何确保三大特性的。
在Java并发编程中,简单的读取和赋值操作是原子性的,但是多个原子操作并在一起就不是了,比如将一个变量赋值给另外一个变量的操作。
JMM只保证了简单读取和赋值的原子性。因此,并发编程中需要用到synchronized实现同步,或者使用Lock接口的实现类加锁;对于基本数据类型如int的自增操作,也可以使用JUC包下的java.util.concurrent.atomic.*包下的原子类型。而volatile修饰的变量,不具备原子性。
基于JMM模型,对于线程读取共享变量:首次只要从主内存读取到工作内存,以后都在工作内存中读取即可;对于修改共享变量,新值先更新在工作内存中,再刷新到主存中。但什么时候刷新是不确定的。因此,Java并发编程中,要确保共享变量在多线程中同步更新,可以采取如下方式:
通过synchronized关键字同步,可以确保在锁释放之前,对变量的修改刷新到主内存中;
通过Lock接口实现类实现同步,同样可以在锁unlock之前,把修改刷新到主内存中;
使用volatile关键字,当某线程修改了工作内存中的共享变量副本,会直接刷新主存中的值,并且其它线程会立刻收到本地内存中共享变量副本失效的信息,从而及时从主内存中更新值。
在JMM模型中,为了充分利用硬件性能,编译器和指令器有可能会对程序指令进行重排序。单线程下,这不会有什么问题,但多线程下则可能带来意想不到的状况。
关于并发编程的有序性,JMM基于一套原生Happens-before原则,来确保了多线程下一定程度的有序性。具体说来:
程序次序规则:即便发生了重排序,在一个线程内最终的运行结果会与程序编写顺序的结果一致。
锁定规则:先unlock再lock。即一个锁是锁定状态,需要先解锁才能再加锁。
volatile规则:如果一个线程对volatile变量读,另一个线程对该变量写,那么写操作一定发生在读操作之前。
传递规则:如果操作A先于B,B先于C,那么A肯定先于C。
线程启动规则:线程的start方法先于其它操作。
线程中断规则:必须是先有interrupt()方法调用,才有中断信号的捕获。
线程终结规则:线程的所有操作都必须先于线程死亡。
对象终结规则:一个对象的初始化先于对象GC之前。
此外,在并发编程中,比较常用的是使用synchronized关键字和Lock接口同步,或者volatile关键字,来确保多线程下的有序性。

感兴趣可以加Java架构师群获取Java工程化、高性能及分布式、高性能、深入浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点高级进阶干货的直播免费学习权限 都是大牛带飞 让你少走很多的弯路的 群..号是: 对了 小白勿进 最好是有开发经验

1、具有工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加。

2、在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加。

3、如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的,可以加。

4、觉得自己很牛B,一般需求都能搞定。但是所学的知识点没有系统化,很难在技术领域继续突破的可以加。

5.阿里Java高级大牛直播讲解知识点,分享知识,多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!

我要回帖

更多关于 java系统需求分析 的文章

 

随机推荐