首页编程java编程java双循环为什么会多 java双层嵌套循环break continue

java双循环为什么会多 java双层嵌套循环break continue

编程之家2023-10-11110次浏览

大家好,关于java双循环为什么会多很多朋友都还不太明白,不过没关系,因为今天小编就来为大家分享关于java双层嵌套循环break continue的知识点,相信应该可以解决大家的一些困惑和问题,如果碰巧可以解决您的问题,还望关注下本站哦,希望对各位有所帮助!

java双循环为什么会多 java双层嵌套循环break continue

java Nio读写为什么是双向

作者:美团技术团队

链接:https://zhuanlan.zhihu.com/p/23488863

来源:知乎

java双循环为什么会多 java双层嵌套循环break continue

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。

那么NIO的本质是什么样的呢?它是怎样与事件模型结合来解放线程、提高系统吞吐的呢?

java双循环为什么会多 java双层嵌套循环break continue

本文会从传统的阻塞I/O和线程池模型面临的问题讲起,然后对比几种常见I/O模型,一步步分析NIO怎么利用事件模型处理I/O,解决线程池瓶颈处理海量连接,包括利用面向事件的方式编写服务端/客户端程序。最后延展到一些高级主题,如Reactor与Proactor模型的对比、Selector的唤醒、Buffer的选择等。

注:本文的代码都是伪代码,主要是为了示意,不可用于生产环境。

传统BIO模型分析

让我们先回忆一下传统的服务器端同步阻塞I/O处理(也就是BIO,Blocking I/O)的经典编程模型:

{

ExecutorService executor= Excutors.newFixedThreadPollExecutor(100);//线程池

ServerSocket serverSocket= new ServerSocket();

serverSocket.bind(8088);

while(!Thread.currentThread.isInturrupted()){//主线程死循环等待新连接到来

Socket socket= serverSocket.accept();

executor.submit(new ConnectIOnHandler(socket));//为新的连接创建新的线程

}

class ConnectIOnHandler extends Thread{

private Socket socket;

public ConnectIOnHandler(Socket socket){

this.socket= socket;

}

public void run(){

while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){死循环处理读写事件

String someThing= socket.read()....//读取数据

if(someThing!=null){

......//处理数据

socket.write()....//写数据

}

}

}

}

这是一个经典的每连接每线程的模型,之所以使用多线程,主要原因在于socket.accept()、socket.read()、socket.write()三个主要函数都是同步阻塞的,当一个连接在处理I/O的时候,系统是阻塞的,如果是单线程的话必然就挂死在那里;但CPU是被释放出来的,开启多线程,就可以让CPU去处理更多的事情。其实这也是所有使用多线程的本质:

利用多核。

当I/O阻塞系统,但CPU空闲的时候,可以利用多线程使用CPU资源。

现在的多线程一般都使用线程池,可以让线程的创建和回收成本相对较低。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的I/O并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。

不过,这个模型最本质的问题在于,严重依赖于线程。但线程是很"贵"的资源,主要表现在:

线程的创建和销毁成本很高,在Linux这样的操作系统中,线程本质上就是一个进程。创建和销毁都是重量级的系统函数。

线程本身占用较大内存,像Java的线程栈,一般至少分配512K~1M的空间,如果系统中的线程数过千,恐怕整个JVM的内存都会被吃掉一半。

线程的切换成本是很高的。操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用。如果线程数过高,可能执行线程切换的时间甚至会大于线程执行的时间,这时候带来的表现往往是系统load偏高、CPU sy使用率特别高(超过20%以上),导致系统几乎陷入不可用的状态。

容易造成锯齿状的系统负载。因为系统负载是用活动线程数或CPU核心数,一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大。

所以,当面对十万甚至百万级连接的时候,传统的BIO模型是无能为力的。随着移动端应用的兴起和各种网络游戏的盛行,百万级长连接日趋普遍,此时,必然需要一种更高效的I/O处理模型。

NIO是怎么工作的

很多刚接触NIO的人,第一眼看到的就是Java相对晦涩的API,比如:Channel,Selector,Socket什么的;然后就是一坨上百行的代码来演示NIO的服务端Demo……瞬间头大有没有?

我们不管这些,抛开现象看本质,先分析下NIO是怎么工作的。

常见I/O模型对比

所有的系统I/O都分为两个阶段:等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。

需要说明的是等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操作的阻塞是使用CPU的,真正在"干活",而且这个过程非常快,属于memory copy,带宽通常在1GB/s级别以上,可以理解为基本不耗时。

下图是几种常见I/O模型的对比:

以socket.read()为例子:

传统的BIO里面socket.read(),如果TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据,返回读到的数据。

对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回0,永远不会阻塞。

最新的AIO(Async I/O)里面会更进一步:不但等待就绪是非阻塞的,就连数据从网卡到内存的过程也是异步的。

换句话说,BIO里用户最关心“我要读”,NIO里用户最关心"我可以读了",在AIO模型里用户更需要关注的是“读完了”。

NIO一个重要的特点是:socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)。

如何结合事件模型使用NIO同步非阻塞特性

回忆BIO模型,之所以需要多线程,是因为在进行I/O操作的时候,一是没有办法知道到底能不能写、能不能读,只能"傻等",即使通过各种估算,算出来操作系统没有能力进行读写,也没法在socket.read()和socket.write()函数中返回,这两个函数无法进行有效的中断。所以除了多开线程另起炉灶,没有好的办法利用CPU。

NIO的读写函数可以立刻返回,这就给了我们不开线程利用CPU的最好机会:如果一个连接不能读写(socket.read()返回0或者socket.write()返回0),我们可以把这件事记下来,记录的方式通常是在Selector上注册标记位,然后切换到其它就绪的连接(channel)继续进行读写。

下面具体看下如何利用事件模型单线程处理所有I/O请求:

NIO的主要事件有几个:读就绪、写就绪、有新连接到来。

我们首先需要注册当这几个事件到来的时候所对应的处理器。然后在合适的时机告诉事件选择器:我对这个事件感兴趣。对于写操作,就是写不出去的时候对写事件感兴趣;对于读操作,就是完成连接和系统没有办法承载新读入的数据的时;对于accept,一般是服务器刚启动的时候;而对于connect,一般是connect失败需要重连或者直接异步调用connect的时候。

其次,用一个死循环选择就绪的事件,会执行系统调用(Linux 2.6之前是select、poll,2.6之后是epoll,Windows是IOCP),还会阻塞的等待新事件的到来。新事件到来的时候,会在selector上注册标记位,标示可读、可写或者有连接到来。

注意,select是阻塞的,无论是通过操作系统的通知(epoll)还是不停的轮询(select,poll),这个函数是阻塞的。所以你可以放心大胆地在一个while(true)里面调用这个函数而不用担心CPU空转。

所以我们的程序大概的模样是:

interface ChannelHandler{

void channelReadable(Channel channel);

void channelWritable(Channel channel);

}

class Channel{

Socket socket;

Event event;//读,写或者连接

}

//IO线程主循环:

class IoThread extends Thread{

public void run(){

Channel channel;

while(channel=Selector.select()){//选择就绪的事件和对应的连接

if(channel.event==accept){

registerNewChannelHandler(channel);//如果是新连接,则注册一个新的读写处理器

}

if(channel.event==write){

getChannelHandler(channel).channelWritable(channel);//如果可以写,则执行写事件

}

if(channel.event==read){

getChannelHandler(channel).channelReadable(channel);//如果可以读,则执行读事件

}

}

}

Map<Channel,ChannelHandler> handlerMap;//所有channel的对应事件处理器

}

这个程序很简短,也是最简单的Reactor模式:注册所有感兴趣的事件处理器,单线程轮询选择就绪事件,执行事件处理器。

优化线程模型

由上面的示例我们大概可以总结出NIO是怎么解决掉线程的瓶颈并处理海量连接的:

NIO由原来的阻塞读写(占用线程)变成了单线程轮询事件,找到可以进行读写的网络描述符进行读写。除了事件的轮询是阻塞的(没有可干的事情必须要阻塞),剩余的I/O操作都是纯CPU操作,没有必要开启多线程。

并且由于线程的节约,连接数大的时候因为线程切换带来的问题也随之解决,进而为处理海量连接提供了可能。

单线程处理I/O的效率确实非常高,没有线程切换,只是拼命的读、写、选择事件。但现在的服务器,一般都是多核处理器,如果能够利用多核心进行I/O,无疑对效率会有更大的提高。

仔细分析一下我们需要的线程,其实主要包括以下几种:

事件分发器,单线程选择就绪的事件。

I/O处理器,包括connect、read、write等,这种纯CPU操作,一般开启CPU核心个线程就可以。

业务线程,在处理完I/O后,业务一般还会有自己的业务逻辑,有的还会有其他的阻塞I/O,如DB操作,RPC等。只要有阻塞,就需要单独的线程。

Java的Selector对于Linux系统来说,有一个致命限制:同一个channel的select不能被并发的调用。因此,如果有多个I/O线程,必须保证:一个socket只能属于一个IoThread,而一个IoThread可以管理多个socket。

另外连接的处理和读写的处理通常可以选择分开,这样对于海量连接的注册和读写就可以分发。虽然read()和write()是比较高效无阻塞的函数,但毕竟会占用CPU,如果面对更高的并发则无能为力。

NIO在客户端的魔力

通过上面的分析,可以看出NIO在服务端对于解放线程,优化I/O和处理海量连接方面,确实有自己的用武之地。那么在客户端上,NIO又有什么使用场景呢?

常见的客户端BIO+连接池模型,可以建立n个连接,然后当某一个连接被I/O占用的时候,可以使用其他连接来提高性能。

但多线程的模型面临和服务端相同的问题:如果指望增加连接数来提高性能,则连接数又受制于线程数、线程很贵、无法建立很多线程,则性能遇到瓶颈。

每连接顺序请求的Redis

对于Redis来说,由于服务端是全局串行的,能够保证同一连接的所有请求与返回顺序一致。这样可以使用单线程+队列,把请求数据缓冲。然后pipeline发送,返回future,然后channel可读时,直接在队列中把future取回来,done()就可以了。

伪代码如下:

class RedisClient Implements ChannelHandler{

private BlockingQueue CmdQueue;

private EventLoop eventLoop;

private Channel channel;

class Cmd{

String cmd;

Future result;

}

public Future get(String key){

Cmd cmd= new Cmd(key);

queue.offer(cmd);

eventLoop.submit(new Runnable(){

List list= new ArrayList();

queue.drainTo(list);

if(channel.isWritable()){

channel.writeAndFlush(list);

}

});

}

public void ChannelReadFinish(Channel channel,Buffer Buffer){

List result= handleBuffer();//处理数据

//从cmdQueue取出future,并设值,future.done();

}

public void ChannelWritable(Channel channel){

channel.flush();

}

}

这样做,能够充分的利用pipeline来提高I/O能力,同时获取异步处理能力。

多连接短连接的HttpClient

类似于竞对抓取的项目,往往需要建立无数的HTTP短连接,然后抓取,然后销毁,当需要单机抓取上千网站线程数又受制的时候,怎么保证性能呢?

何不尝试NIO,单线程进行连接、写、读操作?如果连接、读、写操作系统没有能力处理,简单的注册一个事件,等待下次循环就好了。

如何存储不同的请求/响应呢?由于http是无状态没有版本的协议,又没有办法使用队列,好像办法不多。比较笨的办法是对于不同的socket,直接存储socket的引用作为map的key。

常见的RPC框架,如Thrift,Dubbo

这种框架内部一般维护了请求的协议和请求号,可以维护一个以请求号为key,结果的result为future的map,结合NIO+长连接,获取非常不错的性能。

NIO高级主题

Proactor与Reactor

一般情况下,I/O复用机制需要事件分发器(event dispatcher)。事件分发器的作用,即将那些读写事件源分发给各读写事件的处理者,就像送快递的在楼下喊:谁谁谁的快递到了,快来拿吧!开发人员在开始的时候需要在分发器那里注册感兴趣的事件,并提供相应的处理者(event handler),或者是回调函数;事件分发器在适当的时候,会将请求的事件分发给这些handler或者回调函数。

涉及到事件分发器的两种模式称为:Reactor和Proactor。 Reactor模式是基于同步I/O的,而Proactor模式是和异步I/O相关的。在Reactor模式中,事件分发器等待某个事件或者可应用或个操作的状态发生(比如文件描述符可读写,或者是socket可读写),事件分发器就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。

而在Proactor模式中,事件处理者(或者代由事件分发器发起)直接发起一个异步读写操作(相当于请求),而实际的工作是由操作系统来完成的。发起时,需要提供的参数包括用于存放读到数据的缓存区、读的数据大小或用于存放外发数据的缓存区,以及这个请求完后的回调函数等信息。事件分发器得知了这个请求,它默默等待这个请求的完成,然后转发完成事件给相应的事件处理者或者回调。举例来说,在Windows上事件处理者投递了一个异步IO操作(称为overlapped技术),事件分发器等IO Complete事件完成。这种异步模式的典型实现是基于操作系统底层异步API的,所以我们可称之为“系统级别”的或者“真正意义上”的异步,因为具体的读写是由操作系统代劳的。

举个例子,将有助于理解Reactor与Proactor二者的差异,以读操作为例(写操作类似)。

在Reactor中实现读

注册读就绪事件和相应的事件处理器。

事件分发器等待事件。

事件到来,激活分发器,分发器调用事件对应的处理器。

事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。

在Proactor中实现读:

处理器发起异步读操作(注意:操作系统必须支持异步IO)。在这种情况下,处理器无视IO就绪事件,它关注的是完成事件。

事件分发器等待操作完成事件。

在分发器等待过程中,操作系统利用并行的内核线程执行实际的读操作,并将结果数据存入用户自定义缓冲区,最后通知事件分发器读操作完成。

事件分发器呼唤处理器。

事件处理器处理用户自定义缓冲区中的数据,然后启动一个新的异步操作,并将控制权返回事件分发器。

可以看出,两个模式的相同点,都是对某个I/O事件的事件通知(即告诉某个模块,这个I/O操作可以进行或已经完成)。在结构上,两者也有相同点:事件分发器负责提交IO操作(异步)、查询设备是否可操作(同步),然后当条件满足时,就回调handler;不同点在于,异步情况下(Proactor),当回调handler时,表示I/O操作已经完成;同步情况下(Reactor),回调handler时,表示I/O设备可以进行某个操作(can read或 can write)。

下面,我们将尝试应对为Proactor和Reactor模式建立可移植框架的挑战。在改进方案中,我们将Reactor原来位于事件处理器内的Read/Write操作移至分发器(不妨将这个思路称为“模拟异步”),以此寻求将Reactor多路同步I/O转化为模拟异步I/O。以读操作为例子,改进过程如下:

注册读就绪事件和相应的事件处理器。并为分发器提供数据缓冲区地址,需要读取数据量等信息。

分发器等待事件(如在select()上等待)。

事件到来,激活分发器。分发器执行一个非阻塞读操作(它有完成这个操作所需的全部信息),最后调用对应处理器。

事件处理器处理用户自定义缓冲区的数据,注册新的事件(当然同样要给出数据缓冲区地址,需要读取的数据量等信息),最后将控制权返还分发器。

如我们所见,通过对多路I/O模式功能结构的改造,可将Reactor转化为Proactor模式。改造前后,模型实际完成的工作量没有增加,只不过参与者间对工作职责稍加调换。没有工作量的改变,自然不会造成性能的削弱。对如下各步骤的比较,可以证明工作量的恒定:

标准/典型的Reactor:

步骤1:等待事件到来(Reactor负责)。

步骤2:将读就绪事件分发给用户定义的处理器(Reactor负责)。

步骤3:读数据(用户处理器负责)。

步骤4:处理数据(用户处理器负责)。

改进实现的模拟Proactor:

步骤1:等待事件到来(Proactor负责)。

步骤2:得到读就绪事件,执行读数据(现在由Proactor负责)。

步骤3:将读完成事件分发给用户处理器(Proactor负责)。

步骤4:处理数据(用户处理器负责)。

对于不提供异步I/O API的操作系统来说,这种办法可以隐藏Socket API的交互细节,从而对外暴露一个完整的异步接口。借此,我们就可以进一步构建完全可移植的,平台无关的,有通用对外接口的解决方案。

代码示例如下:

interface ChannelHandler{

void channelReadComplate(Channel channel,byte[] data);

void channelWritable(Channel channel);

}

class Channel{

Socket socket;

Event event;//读,写或者连接

}

//IO线程主循环:

class IoThread extends Thread{

public void run(){

Channel channel;

while(channel=Selector.select()){//选择就绪的事件和对应的连接

if(channel.event==accept){

registerNewChannelHandler(channel);//如果是新连接,则注册一个新的读写处理器

Selector.interested(read);

}

if(channel.event==write){

getChannelHandler(channel).channelWritable(channel);//如果可以写,则执行写事件

}

if(channel.event==read){

byte[] data= channel.read();

if(channel.read()==0)//没有读到数据,表示本次数据读完了

{

getChannelHandler(channel).channelReadComplate(channel,data;//处理读完成事件

}

if(过载保护){

Selector.interested(read);

}

}

}

}

Map<Channel,ChannelHandler> handlerMap;//所有channel的对应事件处理器

}

Selector.wakeup()

主要作用

解除阻塞在Selector.select()/select(long)上的线程,立即返回。

两次成功的select之间多次调用wakeup等价于一次调用。

如果当前没有阻塞在select上,则本次wakeup调用将作用于下一次select——“记忆”作用。

为什么要唤醒?

注册了新的channel或者事件。

channel关闭,取消注册。

优先级更高的事件触发(如定时器事件),希望及时处理。

原理

Linux上利用pipe调用创建一个管道,Windows上则是一个loopback的tcp连接。这是因为win32的管道无法加入select的fd set,将管道或者TCP连接加入select fd set。

wakeup往管道或者连接写入一个字节,阻塞的select因为有I/O事件就绪,立即返回。可见,wakeup的调用开销不可忽视。

Buffer的选择

通常情况下,操作系统的一次写操作分为两步:

将数据从用户空间拷贝到系统空间。

从系统空间往网卡写。同理,读操作也分为两步:

①将数据从网卡拷贝到系统空间;

②将数据从系统空间拷贝到用户空间。

对于NIO来说,缓存的使用可以使用DirectByteBuffer和HeapByteBuffer。如果使用了DirectByteBuffer,一般来说可以减少一次系统空间到用户空间的拷贝。但Buffer创建和销毁的成本更高,更不宜维护,通常会用内存池来提高性能。

如果数据量比较小的中小应用情况下,可以考虑使用heapBuffer;反之可以用directBuffer。

NIO存在的问题

使用NIO!=高性能,当连接数<1000,并发程度不高或者局域网环境下NIO并没有显著的性能优势。

NIO并没有完全屏蔽平台差异,它仍然是基于各个操作系统的I/O系统实现的,差异仍然存在。使用NIO做网络编程构建事件驱动模型并不容易,陷阱重重。

推荐大家使用成熟的NIO框架,如Netty,MINA等。解决了很多NIO的陷阱,并屏蔽了操作系统的差异,有较好的性能和编程模型。

老司机带你分析SpringMVC框架设计原理与实现

链接:https://pan.baidu.com/s/1cksL0_VmSMdkIXWFSOx19g密码:57w4

Netty粘包分包现象及解决方案实战,防socket攻击

链接:https://pan.baidu.com/s/1kTF2oqHOqvrPJrKa7TpXOQ密码:dk9n

大型企业级高并发下数据库水平切分之读写分离技巧详解

链接:https://pan.baidu.com/s/1OrXSGCCboqgVX2vgfC7Z7Q密码:ri8q

分布式事务出现场景及解决方案详细剖析

链接:https://pan.baidu.com/s/1BBf6cePibN0xawFEY7A6ZA密码:380p

以上都是小编收集了大神的灵药,喜欢的拿走吧!喜欢小编就轻轻关注一下吧!

java中什么是类爆炸

可能是由于设计者对面向对象设计经验的缺少,也可能设计者是一个刻板的教条主义者,结果出品了一个很不理想的设计,其中可能就体现在类的数量爆炸的问题。

类爆炸的现象已经发生在我们的软件系统中了。比如我们某期的系统中各种模块文件的数量已经达到一千多个了,虽然比起操作系统这样的系统来说,由一千多个模

块组成的系统不算什么,但是我们目前的软件团队维护这么多的模块真的是有些吃力。由于我们使用的是VB,这还导致另一个问题,VB能装载的文件总数是有限

制的,最后用户提出了新的需求需要新的模块来完成实现,系统却已经不允许加入新的模块了,最后不得不对系统进行拆分或者对某些模块进行合并。

类爆炸的直接原因是设计者对类的抽象粒度没能把握好,只要两个事务有所差别就用不同的类来设计。粒度能多小就做多小,以为这样可以减少耦合。事实是如此

吗?最近组长让我写一份设计问题,他已经规定了设计文档的规范和大纲,规范中说“本系统编码使用了三种类:界面类、实体类、记录集类,并调用了公用模块中

相应函数”,这可能是他从别的设计规范中继承抄袭过来的。但是我最后提交的设计文档没有实体类和记录集类,组长问我为什么没有这两种类,我说我不需要这两

种类,我这个功能一个界面就可完成了。但是他觉得,如果我没有那两个类就应该在设计文档中说明没有那两个类,我说我的设计文档中没有描述那两个类就表明我

没有那两个类,而不需要在文档中说明“实体类,无;记录集类,无”。

如果每一个功能的完成都必须设计成“界面类、实体类、记录集类”这三种类来联合完成,我们就陷入了教条主义的深渊中。曾经和某个项目经理探讨过,“a=c

与a=b=c”的取舍问题,我的观点是根据具体情况来决定是使用“a=c”的结构还是使用“a=b=c”的结构,他的观点是每个功能都一律使用

“a=b=c”的结构,这导致我很郁闷。为什么要在很简单的情况下,本来可以直接就让“a=c”,何必非要加一个中间件“b”,通过“b”来让

“a=c”?不是我不知道“a=b=c”的结构的用意,而是我觉要根据具体情况来应用。我们的系统的类爆炸就是因为不分优劣一律使用“a=b=c”的结构

而爆炸的。对于面向对象的初期使用者来说,总会津津乐道他在系统中实现了面向对象的设计,尽管那个设计比较糟糕。其实这位项目经理只是给了一个系统的规范

文档而已,至于说是他设计了系统的架构,那还远远谈不到。系统中有什么类,类如何创建,类如何组织,类之间如何通信,他都没有做。只是在文档里说了“本系

统编码使用了三种类:界面类、实体类、记录集类,并调用了公用模块中相应函数”,一句话了事。系统中到底有多少类,他不知道。

我在阅读设计专家关于面向对象设计和设计模式的文章时,这些专家一再强调要谨慎使用面向对象和设计模式,否则后果就是苦果。我在应用面向对象时一向比较小心,一步一步的学习使用,而不是一步到位,毕竟我是个初学者。

再举一个例子。我们的系统中有一个连接类,大家都知道这个类是用来连接数据库的。不过我想很少有人知道为什么设计者要设计出这样一个类来。是因为他刚刚读

过设计模式中有一个“单例模式”。对于我们现在的这个系统来说,使用一个数据库连接对象就可以了,设计者为了避免每个程序员都去创建新的数据库连接,就使

用“单例模式”设计出一个连接类来。“单例模式”的用意就是某个类的实例在整个系统中只能有一个实例存在。比如我们用的windows剪贴板,在整个系统

中只能有一个剪贴板,大家都不会去new一个新的剪贴板出来。

我感到非常的郁闷,在一个公用模块里申明一个系统变量connection就可以了,告诉大家这个对象是我们的数据库连接对象,大家都用这个对象,为什么

再来一个clsConnection类对connection重新包装一下?,这反而就有问题了,我可以new出无数个clsConnection的实

例,没有达到“单例模式”的用意,因为在clsConnection类里没有提供静态方法来总是返回系统中已经存在的连接对象,这成了“多例模式”了。也

许设计者的另一个用意是要使用设计模式中的“简单工厂模式”。不过,不管是想练习什么模式,对于一个connection根本没有必要再包装了。这好比我

们有一个系统级变量,为了避免大家都去申明这个变量就用一个类来包装这个变量。那么系统中已经存在这样一个类,为了避免大家乱用这个类,就再来一个类来包

装这个类?层层包裹下去,怎么才算安全?(这里的例子是应用VB做的系统,JAVA使用者请勿随意理解。这里有语言差异。)

使用面向对象设计技术会产生良好的系统,但是,类是面向对象中的东西,那么类爆炸也必然是使用面向对象的产物,这是不良设计导致的。

我们有的程序员有些过于遵守规范而显得有些刻板了。举个例子。某个程序员做了一个类A和一个类B,实体B是实体A的载体(规范中要求每个实体都要对应一个

类),类A提供了一个修改自身的方法,当实体B的某个属性改变时必须要改变实体A的某个属性。我看了源代码,发现一条SQL语句就可以解决这个问题,但是

这个程序员为了用类A的修改方法,在类B中写了一个循环,先找出所有属于实体B的实体A,并创建类A的实例,然后调用类A的修改方法。代码不但冗长还效率

低下。这个程序员有自已的理由去那样做,理由1是上面领导制定的规范要求这样做,理由2是这是一种面向对象的应用,因为类A已经提供了修改实体A的方法,

别人就应该重用这个方法。一切讲究重用。

我想提出的是,如果重用这个方法即不使代码简洁又不能提高效率而且还造成强烈的耦合,为什么还要重用它?在面向对象中,大家知道类的构造函数是用来做什么

的吗?重载方法又是为什么吗?为什么一个类可以有多个不同的构造函数?不同的构造函数是为了达到不同的目的,而不仅仅是为了实例化一个类。方法的重载也是

为了实现不同的目的。当类A提供的方法不能很好的完成任务时,我们就应该舍弃它或者重载它。如果规范要求必须类B调用类A的方法(这个“必须”很值得疑

问)时,那么应该在类A中提供不同的修改方法以使设计合理。类A可以有这样的两个方法:方法1(以实体A自身的引用为参数),方法2(以实体B的引用为参

数)。

关于重用。

我们现在设计系统一直想达到重用的目的。但是考虑我们所做软件的性质,我们对系统组件应该达到什么样的重用程度。我们的组件是不是要发布出去供第三方二次

开发?我们的组件是不是每年能达到2次重用?业务组件和与业务无关的组件重用的能力是不是有很大区别?我们不同的客户的业务规范是不是相差比较大?

由于我们现在对业务抽象的不到位,设计出来的类的粒度控制的不够好。业务相关和业务无关的对象的分离做的不够好,因此,实现组件甚至一个子系统的重用是很难的,只能为不同的客户去修改现有的代码,这显然不是重用,而是维护。我们的代码一年连一次重用的机会都没有。

关于创新。

如果没有创新的设计,后果是可想而知的,不管我们了解的业务再多,我们总是用最原始或者最笨拙的设计去实现业务。这样我们对业务了解的越多,系统做的越

大,代码就越混乱越不稳定。能达到将就凑乎的使用已经是不错的了。当硬件技术飞速发展的时候,软件技术却落后,结果是什么?那么,在我们公司,业务和技

术,哪个是硬件哪个是软件?当所有程序员都意识到争当项目经理和项目组长可以不去编写程序而待遇却提高了时,结果是什么?设计需要创新,了解业务却是一种

带有明显的“被动”特征。用户不告诉你他的业务规则你就不知道,告诉你,你就知道。当用户停止提供需求时,这段时间内,需求调研人员应该做什么工作?是不

是留下了大量的需求文档,是不是去抽象业务规则了?需求调研人员是不是能发现不同客户的不同业务之间的相似性为设计人员提供指导?

我们无法为客户去创新业务,但我们应该去创新我们的设计。一个软件的设计很难保持三年不变,如果三年后还不能有所创新而发生变换,那就落后了。为了适应新

的形式,微软敢于修改自己的操作系统的内核使系统升级。不升级意味着失去财富,而升级时难免要修改部分内核。那么,应用创新技术付出的代价大还是保持原有

系统不变的受益大?我们要考虑新系统生产时的阵痛和它以后带来的长远利益。

公司的人员流动的特征我们是否加以分析了,流走的是什么的样人,进来的又是什么样的人。这些人的技术能力、性格、悟性又是如何?我们拥有了许多安于现状不

具创新的老员工,我们该怎么对待他们?如果一个员工的性格激烈但悟性良好能有创新,我们是不是排击他了?兢兢业业、按部就班、任由指挥是不是就是一个优秀

的员工?

一个公司,各种性格的员工的存在应该有一个比例。全部都是不安分的创新者是不好的,充斥大量的安分守己、明哲保身、上面怎么说下面就怎么做的员工也是不好的现象。公司员工的性格和悟性的分布应该象一个波浪一样,有浪头有浪波,这样才能形成巨浪。

关于沟通。

公司越大,我发现员工之间的沟通越差。当我们还是一家小公司的时候,我可以认识所有的人,现在仅能认识个别几个人。沟通,不是由领导来强调下面的人去做

的,而是由领导来启动和带动的。所谓“领导”两个字,就是“领”和“导”,什么意思?大家自然知道。如何才能称得上一个领导,他必须具有领头和导向的作

用。各个部门的领导肩负着不同的“领导”。技术领导,他的技术是不是一定要强?若不强,是不是他能通过沟通的艺术来让下面的人服从?

沟通是一个大的问题。比如我早已经应用过一个比较好的数据库设计模型,但是新项目的设计者从来也没有咨询过我是怎么做的,结果他自己搞出一个很糟糕的数据

模型。沟通是一个双向过程。被动的沟通与主动的沟通的效果自然是不同的。我们现在缺乏主动沟通,就是被动沟通都不能好好的参与。所以,沟通出现了“推模

式”和“拉模式”。举个例子,我有些编程技巧放到公司网站了,很少有人去用主动的看,如果他主动去看了,这种行为模式称作“拉模式”。为了让更多的人知道

我的技巧,我只能主动把技术文章发到每个人的邮箱里,我的这种行为模式是一种“推模式”。我把技术文章推到每个人的邮箱里了,那么接收邮件的人是不是“拉

过来”看一眼?我发现,有一部分人是从来不看的,技术人员也不看。到底为什么不看?看不起我的文章还是太了解我而觉得没有必要看?他的心态我没有办法了

解,总之,沟通是有障碍的。

“聚集”沟通需要大家坐在一起去探讨某些问题,但这可能会浪费参与人的时间。因此我一直以为“推模式”的沟通还是可取的。使用邮件来沟通,想看则看不想看

就不看,尊重接收邮件的人的参与意识。但是发邮件会引来一些误会。一部分人会把你的邮件当作垃圾,心里不喜欢你给他发邮件但又不能说出来。一部分人会认为

发邮件的人脑子有毛病或者出风头,我觉得应该让每一个人都摆正心态:尊重发邮件的人。历来都有“枪打出头鸟”的现象,人在浪头上,难免遭遇不恰当的言语。

因此,永远低调和保持沉默便成为一种人生哲学。激进主义是用来推动社会前进的,保守主义是用来维持社会稳定的。我们允许这些同时存在。

由对事演化到对人。

当我们探讨问题时一般都是本着“对事不对人”的态度的。对于言者也许是这样的心里,但听者可能认为言者就是“对人不对事”。我们没有办法使他消除那种心里,因为那是他的性格使然。但是我们希望每个人都心胸开阔一些。

在探讨关于类爆炸的问题时,我说了一些题外话。

当我和一些同事在探讨设计中的缺陷时,发现大家的眼睛都还是明亮的,知道问题所在。不幸的是,几乎所有的人保都持沉默而不将问题暴露出来。当我暴露问题时,会得到个别人的善意的奉劝:“不要去做”。

java双层嵌套循环break continue

没事来做下这题

import java.util.Scanner;

public class Test1{

public static void main(String[] args){

//密码输入次数

int count= 0;

//跳出两次循环,设置标志位

boolean flag=true;

System.out.print("请输入银行卡密码:");

while(flag){

Scanner scanner= new Scanner(System.in);

String password= scanner.nextLine();

if("111111".equals(password)){

System.out.print("密码正确,请输入取款金额(取款金额要在0-2000之内,仅提供百元大钞):");

while(true){

Scanner scanner1= new Scanner(System.in);

String moneyStr= scanner1.nextLine();

int money=0;

try{

money= Integer.parseInt(moneyStr);

}catch(Exception e){

System.out.println("警告:你必须输入数字!");

}

if(money> 0&& money<= 2000&& money% 100== 0){

System.out.println("您成功取款"+ money+"元,感谢使用本系统!再见!");

//跳出两次循环

flag=false;

break;

} else{

System.out.print("您输入的金额有误,请重新输入(取款金额要在0-2000之内,仅提供百元大钞):");

}

}

} else{

if(count== 2){

System.out.println("密码输入错误三次,取款结束");

break;

}

System.out.print("密码错误,请重新输入:");

count++;

}

}

}

}

关于本次java双循环为什么会多和java双层嵌套循环break continue的问题分享到这里就结束了,如果解决了您的问题,我们非常高兴。

读卡器是什么(相机读卡器是什么)java什么情况下使用锁?Java中有哪些锁,区别是什么