java面试知识点

三个基本特征

面向对象的三个基本特征是:
• 封装:把客观事物封装成抽象的类,给对象提供了隐藏内部特性和行为的能力。private修饰符
• 继承:子类继承父类之后,它可以使用父类的所有功能,并可以定义自己的方法对功能进行扩展。
• 多态:本质上就是父类的引用指向子类的对象。可以增强代码的健壮性,假设你要设计一个通用框架,只管按照父类的设计进行操作,不用管具体是什么样的子类,这样代码维护起来就更加容易。

注:Java中public,private,protected,和默认的区别:

1、private修饰词,表示成员是私有的,只有自身可以访问;
2、public修饰词,表示成员是公开的,所有其他类都可以访问;
3、protected,表示受保护权限,体现在继承,即子类可以访问父类受保护成员,同时相同包内的其他类也可以访问protected成员。
4、无修饰词(默认),表示包访问权限,同一个包内可以访问(friendly, java语言中是没有friendly这个修饰符的,这样称呼应该是来源于c++ );

五种设计原则:

单一职责原则(SRP)
开放封闭原则(OCP)
里氏替换原则(LSP)
依赖倒置原则(DIP)
接口隔离原则(ISP)

1.单一职责原则(Single-Resposibility Principle)
一个类,最好只做一件事。只有一个方向引起它的变化;
个人经验:具体做法就是分模块进行封装,不要写一个功能复杂的类。

2.开放封闭原则(Open-Closed principle)
对扩展开放,对修改封闭。
1)、对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
2)、对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改 。
个人经验:如果一个类要修改最好采用属性或方法注入的方式(采用接口),即依赖注入;或者采用继承的方式;

3.里氏替换原则(Liskov-Substituion Principle)
子类必须能够替换其基类
个人经验:基类是个超级类(抽象类或一个接口),子类实现时要实现一些必要方法;

4.依赖倒置原则(Dependecy-Inversion Principle)
依赖于抽象,对接口和抽象类编程,不要对实现编程。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。依赖于抽象,就是对接口编程,不要对实现编程。
个人经验:将具体被调用类的实现封装起来(使用接口或抽象类),提供接口给具体调用的类;

5.接口隔离原则(Interface-Segregation Principle)
这个原则的意思是:使用多个专门的接口比使用单个接口要好的多。一个接口应该保证,实现该接口的实例对象可以只呈现为单一的角色;这样,当某个客户程序的要求发生变化,而迫使接口发生改变时,影响到其他客户程序的可能生性小。
个人经验:尽量少暴露类的方法和属性。接口隔离,使用多个小的专门的接口,而不要使用一个大的总接口。

java的优点

1,java语言比较简洁,丢弃了C++语言中类似于头文件、指针、运算符重载等很少使用、较难以理解的一些功能,简化语言的同时,也降低了出错的可能。

2,采用面向对象的思想,具有封装、继承、多态三大特性,易维护,易扩展,而且质量和效率也高。

3, 平台无关性。 Java语言能运行于不同的平台,一次编译多处运行,当然这里的“到处”的前提是“装有JVM”。

虚拟机 JVM

Java虚拟机是一个可以执行Java字节码的虚拟机进程,它知道底层硬件平台的指令长度和其他特性。当java虚拟机执行编译程序生成字节码文件时,可以把字节码解释成具体平台上的机器指令执行。

我们来看一下JAVA的编译过程:
这里写图片描述

JVM的结构:

这里写图片描述

JVM 将内存区域划分为堆、栈、本地方法栈、方法区、程序计数器。其中方法区和堆 是线程共享的。
程序计数器是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,是线程隔离的。

1)堆
所有通过new创建的对象(类的实例和数组)的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。堆被划分为新生代和旧生代(永久代来存放方法区),新生代又被进一步划分为Eden和Survivor区,最后Survivor由From Space和To Space组成,结构图如下所示:
这里写图片描述
● 新生代。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例
● 旧生代。用于存放新生代中经过多次垃圾回收仍然存活的对象

2)栈
每个线程执行方法的时候都会在栈中申请一个栈帧,包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果

3)本地方法栈
用于支持native方法的执行,存储了每个native方法调用的状态

4)方法区
存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用永久代(Permanet Generation)来存放方法区,可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值

垃圾回收机制

Java的垃圾回收机制是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收堆中无任何引用的对象占据的内存空间。

下面的方法调用时用于显式通知JVM可以进行一次垃圾回收,但真正垃圾回收机制具体在什么时间点开始发生动作这同样是不可预料的,这和抢占式的线程在发生作用时的原理一样。
System.gc()
Runtime.getRuntime().gc()

JVM分别对新生代和旧生代采用不同的垃圾回收算法。

• 新生代的GC:
新生代通常存活时间较短,每次垃圾回收都要回收大部分对象,因此基于Copying(复制)算法来进行回收。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当一块的内存用完了,就扫描出存活着的对象并复制到另外一块完全未使用的空间中,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。

在执行机制上JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew)
1)串行GC
在整个扫描和复制过程采用单线程的方式来进行,适用于单CPU、新生代空间较小及对暂停时间要求不是非常高的应用上,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定
2)并行回收GC
在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数
3)并行GC
与旧生代的并发GC配合使用

• 旧生代的GC:
而由于老年代的特点是对象存活时间长,每次都只回收少量对象,一般使用的是Mark-Compact(标记-整理)算法。标记出所有需要被回收的对象,将存活对象都向一端移动,然后清理垃圾对象所占的内存,减少内存碎片带来的效率损耗。

在执行机制上JVM提供了串行GC(SerialMSC)、并行GC(parallelMSC)和并发GC(CMS)。

• 注意,在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。
垃圾回收不发生在永久代,如果永久带满了或者超过临界值,会触发完全垃圾回收,对整个堆进行整理。

内存泄漏

Java和C++中一个很大的区别就是Java有垃圾回收机制GC自动管理内存的回收,但是也有些情况也会导致Java编写的程序出现内存泄露的问题。

大部分泄漏原因都是因为创建或调用了外部资源而没有正确的释放所导致的,内存对象明明已经不需要的时候,还仍然保留着这块内存和它的引用。使得一个内存对象的生命周期超出了程序需要它的时间长度。

1、监听器的使用,在释放对象的同时没有相应删除监听器的时候可能导致内存泄露。

2、大量临时变量的使用,没有及时将对象设置为null也可能导致内存的泄露

3、数据库的连接没有关闭情况,包括连接池方法连接数据库,如果没有关闭ResultSet等也都可能出现内存泄露的问题。

举例一个内存泄露的例子:

Vector v = new Vector(10);
for (int i = 1; i<100; i++){
Object o = new Object();
v.add(o);
o = null;
}

别以为o对象被回收了,实际上v引用了这些对象,还被引用的对象GC是没有办法回收的,这样就很可能导致内存的泄露。

4、静态集合类像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着。

5、内部类和外部类的引用容易出现内存泄露的问题

内存调优

内存调优主要的目的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。特别要关注Full GC,因为它会对整个堆进行整理。

导致Full GC一般由于以下几种情况:
● 旧生代空间不足
调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象
● Pemanet Generation空间不足
增大Perm Gen空间,避免太多静态对象
● 统计得到的GC后晋升到旧生代的平均大小大于旧生代剩余空间
控制好新生代和旧生代的比例
● System.gc()被显示调用
垃圾回收不要手动触发,尽量依靠JVM自身的机制

调优手段主要是通过控制堆内存的各个部分的比例和GC策略来实现,下面来看看各部分比例不良设置会导致什么后果
1)新生代设置过小
一是新生代GC次数非常频繁,增大系统消耗;二是导致大对象直接进入旧生代,占据了旧生代剩余空间,诱发Full GC
2)新生代设置过大
一是新生代设置过大会导致旧生代过小(堆总量一定),从而诱发Full GC;二是新生代GC耗时大幅度增加
一般说来新生代占整个堆1/3比较合适
3)Survivor设置过小
导致对象从eden直接到达旧生代,降低了在新生代的存活时间
4)Survivor设置过大
导致eden过小,增加了GC频率
另外,通过-XX:MaxTenuringThreshold=n来控制新生代存活时间,尽量让对象在新生代被回收

新生代和旧生代都有多种GC策略和组合搭配,JVM提供两种较为简单的GC策略的设置方式:
1)吞吐量优先
JVM以吞吐量为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,来达到吞吐量指标。这个值可由-XX:GCTimeRatio=n来设置
2)暂停时间优先
JVM以暂停时间为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,尽量保证每次GC造成的应用停止时间都在指定的数值范围内完成。这个值可由-XX:MaxGCPauseRatio=n来设置

最后汇总一下JVM常见配置
1. 堆设置
○ -Xms:初始堆大小
○ -Xmx:最大堆大小
○ -XX:NewSize=n:设置年轻代大小
○ -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
○ -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
○ -XX:MaxPermSize=n:设置持久代大小
2. 收集器设置
○ -XX:+UseSerialGC:设置串行收集器
○ -XX:+UseParallelGC:设置并行收集器
○ -XX:+UseParalledlOldGC:设置并行年老代收集器
○ -XX:+UseConcMarkSweepGC:设置并发收集器
3. 垃圾回收统计信息
○ -XX:+PrintGC
○ -XX:+PrintGCDetails
○ -XX:+PrintGCTimeStamps
○ -Xloggc:filename
4. 并行收集器设置
○ -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
○ -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
○ -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
5. 并发收集器设置
○ -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
○ -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

集合类

Java集合类与数组的区别:
数组:长度固定可以存储基本数据类型和引用数据类型,存储的元素必须是同一数据类型;
集合:长度可变,只能存储引用数据类型(数组,类,接口),存储的元素可以是不同数据类型;

Java集合类与数组的联系:使用相应的toArray()和Arrays.asList()方法可以转换。

Collection:集合层次中的根接口
├Set:不包含重复元素,可能无序
├ ├HashSet: 底层数据结构是哈希表,线程不同步,无序高效
├ └SortedSet (TreeSet): 底层数据结构是二叉树,线程不同步,有序排列

└List:可包含重复元素,有序
├LinkedList:底层数据结构是链表,线程不同步
├ArrayList:底层数据结构是数组,线程不同步
└Vector:底层数据结构是数组,线程同步
  └Stack

Map:存储的是key-value对,不能包含重复的key,可以有重复的value
├Hashtable:底层数据结构是哈希表,线程同步,不存储null值
├SortedMap(TreeMap)
└HashMap:底层数据结构是哈希表,线程不同步,可存储null值

hashmap原理

1,什么是 HashMap,为什么用到它?

HashMap的底层数据结构是哈希表,用来存储键值对,线程不同步,很快,可存储null值。

2,HashMap的工作原理。

HasnMap基于hashing的原理,将键值对以Entry对象的形式存储在bucket中(Bucket是OSS(对象存储)上的命名空间,Bucket名称在整个OSS服务中具有全局唯一性,且不能修改)

我们可以用put(key,value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。
当我们用put()方法时,先对键调用hashcode()方法,找到bucket位置,存储这个键值对的Entry对象。
当我们用get()方法时,先对键调用hashcode()方法,找到bucket位置,再调用keys.equals()方法找到要找的值对象。

3,两个对象的hashcode相同会发生什么?

HashMap用LinkedList存储对象,当我调用put()方法时。若两个hashcode相同,它们的bucket位置相同,这个Entry对象就会存储在LinkedList中。

当我调用get()方法时。HashMap会使用键对象的hashcode找到Entry对象所在的bucket位置,然后调用keys.equals()方法找到LinkedList中正确的节点,最终找到要找的值对象。

3.相关问题:

  1).为什么集合类没有实现Cloneable和Serializable接口:
    克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。
 
  2). 迭代器 (Iterator) :
     是一个接口,用于取集合中的元素。     
     Iterator接口提供了很多对集合元素进行迭代的方法(hasNext, Next, Remove),迭代器可以在迭代的过程中删除底层集合的元素
  3). Iterator和ListIterator的区别:
    Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
    Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
    ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
 
  4)Enumeration接口和 Iterator接口的区别:
    Enumeration速度是Iterator的2倍,同时占用更少的内存。但是,Iterator远远比Enumeration安全(因为其他线程不能够修改正在被iterator遍历的集合里面的对象)。同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。

区别1

Vector,ArrayList,LinkedList

答: 1、Vector、ArrayList都是以类似数组的形式存储在内存中,LinkedList则以链表的形式进行存储。
2、LinkedList适合指定位置插入、删除操作,不适合查找;ArrayList、Vector适合查找,不适合指定位置的插入、删除操作。
3、Vector线程同步,ArrayList、LinkedList线程不同步。
4、ArrayList在元素填满容器时会自动扩充容器大小的50%,而Vector则是100%,因此ArrayList更节省空间。

区别2

HashTable, HashMap,TreeMap,ConcurrentHashMap

答:
1、HashTable线程同步,HashMap非线程同步。
2、HashTable不允许<键,值>有空值,HashMap允许<键,值>有空值。
3、HashTable使用Enumeration,HashMap使用Iterator。
4、HashTable中hash数组的默认大小是11,增加方式的old*2+1,HashMap中hash数组的默认大小是16,增长方式一定是2的指数倍。

5、TreeMap能够把它保存的记录根据键排序,默认是按升序排序。

6、ConcurrentHashMap引入了”分段锁”的概念. 具体可以理解为把一个大的Map拆分成N个小的HashTable,每一段数据都配一把锁,当一个线程访问其中一段数据的时候,其他段的数据也能被其他线程访问。

设计模式

设计模式的优点:
1、设计模式融合了众多专家的经验,并以标准的形式给广大群众使用,她提供了一中通用的设计词汇,和一种通用的设计语言,以方便开发人员之间开发和交流,使得设计方案更加通俗易懂;
2、设计模式使人们可以方便简单的复用设计模式;
3、设计模式是设计方案易于修改;
4、设计模式的使用将提高软件开发效率与质量,节约成本;
5、设计模式有助于初学者理解面向对象的思想。

●创建型模式,共5种:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
●结构型模式,共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
●行为型模式,共11十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

1.1 创建型模式
AbstractFactory ( 抽象工厂 ) :定义一个创建一系列相关对象的接口,无需指定具体类
FactoryMethod ( 工厂方法 ) :定义一个创建对象的接口,让子类决定实例化哪一个类
Singleton ( 单态模式 ) :保证一个类只有一个实例
Builder ( 建造者模式 ) :将一个复杂对象的构建与它的表示相分离
Prototype ( 原型模式 )

1.2 结构型模式
Adapter (适配器模式 ):将一类接口转化为用户希望的另一个接口,使类兼容
Bridge ( 桥接模式)
Composite ( 组合模式 )
Decorator ( 装饰模式 ) :当不适合采用生成子类的方法对已有的类进行扩充时,可以采用装饰设计模式动态地给一个对象添加一些额外的职责
Facade ( 外观模式 )
Flyweight ( 享元模式 ) :当应用程序由于使用大量的对象,造成很大的存储开销时,可以采用享元设计模式运用共享技术来有效地支持大量细粒度的对象
Proxy ( 代理模式 )

1.3 行为型模式
Chain of Responsibility ( 责任链模式 ) :在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
Command ( 命令模式 )
Interpreter ( 解释器模式 )
Iterator ( 迭代器*式 )
Mediator ( 中介者模式 )
Memento ( 备忘录模式 )
Observer ( 观察者模式 ) :定义对象一对多的依赖关系;当对象改变,依赖它的对象都得到通知
State ( 状态模式 )
Strategy ( 策略模式 )
TemplateMethod ( 模板方法 )
Visitor ( 访问者模式 )

注:MVC模式是不是一种设计模式?
MVC不是设计模式,应该是框架/架构模式,因为它的定义是抽象的,没有足够的细节描述使你直接去实现,而只能根据MVC的概念和思想,用几个设计模式组合实现。

单例模式:

这里写图片描述
这里写图片描述

●饿汉式和懒汉式区别:

饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:

1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题。
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。

2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

至于以下这三种实现又有些区别:
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。

泛型

泛型的是指将所操作的数据类型指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

泛型的好处是安全简单。因为没有泛型的情况的下,需要做显式的强制类型转换,如果对实际参数类型不可知,强制类型转换会出现错误的情况,而编译器可能不提示错误,在运行的时候才出现异常。泛型会在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

Java泛型实现原理:类型擦除

Java语言中的泛型实现方法称为类型擦除,是伪泛型。

Java中的泛型是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

如在代码中定义的List和List等类型,在编译后都会编程List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。

JAVA反射机制

JAVA反射机制是指动态获取信息以及动态调用对象方法的这种功能。
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;

j2ee是什么?

J2EE是java三大体系之一。Java分为三个体系,分别为J2SE(标准版),J2 ME(微型版),J2EE(企业版),
J2EE是一套不同于传统应用开发的技术架构,核心是一组技术规范与指南,其中所包含的各类组件、服务架构及技术层次,均有共同的标准及规格,让各种依循J2EE架构的不同平台之间,存在良好的兼容性,提高可移植性、安全性与再用价值。

J2EE组件和“标准的” Java类的不同点在于:
它被装配在一个J2EE应用中,具有固定的格式并遵守J2EE规范,由J2EE服务器对其进行管理。J2EE规范是这样定义J2EE组件的:客户端应用程序和applet是运行在客户端的组件;Java Servlet和Java Server Pages (JSP) 是运行在服务器端的Web组件;Enterprise Java Bean (EJB )组件是运行在服务器端的业务组件。

如何在j2ee项目中处理高并发量访问?

1、HTML静态化。因为纯静态化的html页面是效率最高、消耗最小的。
2、图片服务器分离。图片是最消耗资源的,我们有必要将图片与页面进行分离,基本上大型网站都会有独立的、甚至很多台的图片服务器。
3、在面对大量访问的时候,数据库的瓶颈很快就能显现出来,我们可以使用数据库集群或者库表散列。
(数据库集群在架构、成本、扩张性方面都会受到所采用数据库类型的限制,库表散列是从应用程序的角度来考虑改善系统架构,通过在应用程序中安装功能模块将数据库进行分离,不同的模块对应不同的数据库或者表,再按照一定的策略对某个页面或者功能进行更小的数据库散列,比如一个论坛就可以对帖子、用户按照板块和ID进行散列数据库和表,这样就能够低成本的提升系统的性能并且有很好的扩展性。)
4、缓存。Linux上提供的Memory Cache是常用的缓存接口,比如用Java开发的时候就可以调用MemoryCache对一些数据进行缓存和通讯共享。
5、搭建镜像站点,数据进行定时更新或者实时更新
6、CDN加速技术。通过在现有的网络中增加一层新的网络架构,将网站的内容发布到最接近用户的网络“边缘”,使用户可以就近取得所需的内容,提高用户访问网站的响应速度。
7、负载均衡技术,将整个区间段的业务流分配到合适的应用服务器进行处理:

负载均衡技术:
(1)、DNS负载均衡,在DNS中为多个地址配置同一个名字,查询这个名字的客户机将得到其中一个地址,使得不同的客户访问不同的服务器,达到负载均衡的目的(DNS负载均衡是一种简单而有效的方法,但是它不能区分服务器的差异,也不能反映服务器的当前运行状态)。
(2)、使用代理服务器,将用户请求转发给多台服务器,从而达到负载均衡的目的,提升网页的访问速度。
(3)、NAT负载均衡,通过网络地址转换的网关,将一个外部IP地址映射为多个内部IP地址,对每次连接请求动态使用其中一个内部地址,达到负载均衡的目的。
(4)、协议内部支持负载均衡 ,比如HTTP协议中的重定向能力等,HTTP运行于TCP连接的最高层。
(5)、对于大型网络,可以采用混合型负载均衡 ,由于多个服务器群内硬件设备、各自的规模、提供的服务等的差异,我们可以考虑给每个服务器群采用最合适的负载均衡方式,然后又在这多个服务器群间再一次负载均衡向外界提供服务,从而达到最佳的性能

区别3

接口与抽象类

1,语法层面上的区别:

//接口
[public] interface Inter{
    public static final [int x=3]; //成员变量
    public abstract [void show()];  //成员方法
}
//抽象类
[public] abstract class ClassName {
    abstract void fun();
}

• 接口中的方法都是抽象的,而抽象类可以同时包含抽象和非抽象方法。
• 接口的成员修饰符都是public的,抽象类的成员修饰符可以是 public、protect和private。
• 接口声明的变量都是final的,抽象类可以包含非fina的变量。
• 接口可以多实现,抽象类只能单继承。
• 一个类如果要实现一个接口,必须实现接口声明的所有方法;但是类可以不实现抽象类声明的所有方法,当然这种情况下类必须声明为抽象的。

2,设计层面上的区别:

1)抽象类是对一种事物的抽象,是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
继承是一个 “是不是”的关系,而 接口 实现则是 “有没有”的关系。

2)设计层面不同。对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,所有子类都具备了该方法;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的实现。

  下面看一个网上流传最广泛的例子:门和警报的例子:门都有open( )和close( )两个动作,此时我们可以定义通过抽象类和接口来定义这个抽象概念:

abstract class Door {
    public abstract void open();
    public abstract void close();
}
或者
interface Door {
    public abstract void open();
    public abstract void close();
}

  但是现在如果我们需要门具有报警alarm( )的功能,那么该如何实现?下面提供两种思路:

  1)将这三个功能都放在抽象类里面,但是这样一来所有继承于这个抽象类的子类都具备了报警功能,但是有的门并不一定具备报警功能;

  2)将这三个功能都放在接口里面,需要用到报警功能的类就需要实现这个接口中的open( )和close( ),也许这个类根本就不具备open( )和close( )这两个功能,比如火灾报警器。

  从这里可以看出, Door的open() 、close()和alarm()根本就属于两个不同范畴内的行为,open()和close()属于门本身固有的行为特性,而alarm()属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含alarm()行为,Door设计为单独的一个抽象类,包含open和close两种行为。再设计一个报警门继承Door类和实现Alarm接口。

interface Alram {
    void alarm();
}

abstract class Door {
    void open();
    void close();
}

class AlarmDoor extends Door implements Alarm {
    void oepn() {
      //....
    }
    void close() {
      //....
    }
    void alarm() {
      //....
    }
}

区别4

synchronized和volatile

1)synchronized:用来修饰方法 或者代码块。一次只有一个线程可以执行代码的受保护部分。
2)volatile:是变量修饰符,被volatile修饰的变量的读写操作必须在主内存中进行。
比较:
1.synchronized是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。而 volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取,可以实现synchronized的部分效果,但当n=n+1,n++等时,volatile关键字将失效,不能起到像synchronized一样的线程同步的效果。
2.volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
3.volatile仅能实现变量的修改可见性,并不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
4.volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
5.volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

区别5

String,StringBuffer与StringBuilder的区别

String 字符串常量,内容不可变;
StringBuffer 、StringBuilder 字符串变量,内容可变。
StringBuffer线程安全;StringBuilder 字符串变量(非线程安全)

区别6

final、finally、finalize的异同

final:修饰符。修饰的类不能被继承;修饰的方法不能被重写;修饰的变量是不可被修改的常量。
finally:异常处理时提供finally块。无论是否抛出异常finally代码块都会执行,主要用来释放应用占有的资源(I/O缓冲区、数据库连接)。
finalize():是Object类的一个方法,它在对象被垃圾回收之前由java虚拟机调用,作必要的清理工作。

区别7

GET,POST区别

答: 基础知识:Http的请求格式如下。

<request line>          主要包含三个信息:1、请求的类型(GET或POST),2、要访问的资源(如\res\img\a.jif),3、Http版本(http/1.1)
<header>                用来说明服务器要使用的附加信息
<blank line>             这是Http的规定,必须空一行
[<request-body>]     请求的内容数据

区别:
1、Get是从服务器端获取数据,Post则是向服务器端发送数据。
2、在客户端,Get方式通过URL提交数据,在URL地址栏可以看到请求消息,该消息被编码过;Post数据则是放在Html header内提交。
3、对于Get方式,服务器端用Request.QueryString获取变量的值;对用Post方式,服务器端用Request.Form获取提交的数据值。
4、Get方式提交的数据最多1024字节,而Post则没有限制。
5、Get方式提交的参数及参数值会在地址栏显示,不安全,而Post不会,比较安全。

区别8

Session, Cookie区别

答: 1、Session由应用服务器维护的一个服务器端的存储空间;Cookie是客户端的存储空间,由浏览器维护。
2、用户可以通过浏览器设置决定是否保存Cookie,而不能决定是否保存Session,因为Session是由服务器端维护的。
3、Session中保存的是对象,Cookie中保存的是字符串。
4、Session和Cookie不能跨窗口使用,每打开一个浏览器系统会赋予一个SessionID,此时的SessionID不同,若要完成跨浏览器访问数据,可以使用 Application。
5、Session、Cookie都有失效时间,过期后会自动删除,减少系统开销。

区别9

Tomcat,Apache,JBoss的区别?

答: 1、Apache是Http服务器,Tomcat是web服务器,JBoss是应用服务器。
2、Apache解析静态的html文件;Tomcat可解析jsp动态页面、也可充当servlet容器。

区别10

Applet和Servlet

1.Applet是运行在客户端主机的浏览器上的客户端Java程序。而Servlet是运行在web服务器上的服务端的组件。
applet可以使用用户界面类,而Servlet没有用户界面,相反,Servlet是等待客户端的HTTP请求,然后为请求产生响应。

2.applet的生命周期:
Init:每次被载入的时候都会创建applet控制类的实例,然后初始化applet。
Start:开始执行applet。
Stop:结束执行applet。
Destroy:卸载applet之前,做最后的清理工作。

3,Servlet生命周期分为三个阶段:
  1),初始化阶段 : 调用init()方法
  2),响应客户请求阶段 : 调用service()方法
  3),终止阶段 : 调用destroy()方法

ssh框架

MVC,是模型(model)-视图(view)-控制器(controller)的缩写,用一种将业务逻辑、数据和界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑.

SSH框架为 struts+spring+hibernate的一个集成框架,是目前较流行的一种Web应用程序开源框架。集成SSH框架的系统从职责上分为:表示层、业务逻辑层、数据持久层.三个框架都是轻量级的,使用很灵活。

●Struts是一个典型的MVC框架,主要负责web表示层的显示。主要通过JSP和Servlet技术实现
●Spring是一个全方位的整合框架,在ssh框架中对hibernate和struts进行整合,解决层与层之间的耦合问题。其核心技术是IOC(控制反转,也称依赖注入)和AOP(面向切面编程)。所谓依赖注入即组件之间的依赖关系由容器在运行期决定,降低了程序之间的耦合度。
●Hibernate是一种对象关系映射(ORM)框架, 提供了从 Java类到数据表的映射,也提供了数据查询和恢复等机制, 大大减少数据访问的复杂度。它对JDBC进行了非常轻量级的对象封装,把对数据库的直接操作 , 转换为对持久对象的操作。在用jsp的servlet做网页开发的时候有个web.xml的映射文件,里面有个mapping的标签就是用来做文件映射的。

由SSH构建系统的基本业务流程是:
1、在表示层中,首先通过JSP页面实现交互界面,负责传送请求(Request)和接收响应(Response),然后Struts根据配置文件(struts-config.xml)将ActionServlet接收到的Request委派给相应的Action处理。
2、在业务层中,管理服务组件的Spring IoC容器负责向Action提供业务模型(Model)组件和该组件的协作对象数据处理(DAO)组件完成业务逻辑,并提供事务处理、缓冲池等容器组件以提升系统性能和保证数据的完整性。
3、在持久层中,则依赖于Hibernate的对象化映射和数据库交互,处理DAO组件请求的数据,并返回处理结果。

1

Structs

●Struts是一个典型的MVC框架,主要负责web表示层的显示。主要通过JSP和Servlet技术实现

●Struts1流程/工作机制/设计模式(MVC):
-(1) 在web应用启动时就会加载初始化ActionServlet;
  -(2) ActionServlet从struts-config.xml文件中读取配置信息, 决定是否需要表单验证;  
-(3)如果需要验证,就调用ActionForm的validate()方法;
  -(4)如果ActionForm的validate()方法返回null或返回一个不包含ActionMessage的ActuibErrors对象, 就表示表单验证成功;
  -(5)ActionServlet根据ActionMapping所包含的映射信息决定将请求转发给哪个Action,如果相应的 Action实例不存在,就先创建这个实例,然后调用Action的execute()方法;
  -(6)Action的execute()方法返回一个ActionForward对象,ActionServlet在把客户请求转发给 ActionForward对象指向的JSP组件;
  -(7)ActionForward对象指向JSP组件生成动态网页,返回给客户;

●Struts2的执行流程:
Struts 2框架本身大致可以分为3个部分:核心控制器FilterDispatcher、业务控制器Action和用户实现的业务逻辑组件。
核心控制器FilterDispatcher是Struts 2框架的基础,包含了框架内部的控制流程和处理机制。
业务控制器Action和业务逻辑组件是需要用户来自己实现的。我们在开发Action和业务逻辑组件的同时,还需要编写相关的配置文件,供核心控制器FilterDispatcher来使用。
Struts 2的工作流程是基于webwork技术的框架。基本简要流程:
1、客户端浏览器发出HTTP请求。
2、根据web.xml配置,该请求被FilterDispatcher接收。
3、根据struts.xml配置,找到需要调用的Action类和方法,并通过IoC方式,将值注入给Aciton。
4、Action调用业务逻辑组件处理业务逻辑,这一步包含表单验证。
5、Action执行完毕,根据struts.xml中的配置找到对应的返回结果result,并跳转到相应页面。
6、返回HTTP响应到客户端浏览器。

●为什么要用Struts框架:
  JSP、Servlet、JavaBean技术的出现为构建强大的企业应用系统提供了可能。但用这些技术构建的系统非常的繁乱,加大了后期维护以及二次开发的难度。所以需要一个规则把这些技术组织起来,自然就出现了Struts框架,它非常先进地实现了MVC模式,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

问: 1,Action是不是线程安全的?如果不是有什么方式可以保证Action的线程安全?如果是,说明原因
不是.
声明局部变量,或者扩展RequestProcessor,让每次都创建一个Action,或者在spring中用scope=”prototype”来管理

2,Struts2拦截器原理?ModelDriver原理?
当请求struts2的action时,Struts2会查找配置文件,并根据其配置实例化相对的拦截器对象,然后串成一个列表,最后一个一个地调用列表中的拦截器

2

Spring

●对Spring的理解,项目中都用什么?怎么用的?对IOC、和AOP的理解及实现原理?

答: Spring是一个开源框架,处于MVC模式中的控制层,在ssh框架中对hibernate和struts进行整合,解决层与层之间的耦合问题。其核心技术是IOC(控制反转,也称依赖注入)和AOP(面向切面编程)。

●Spring中AOP(面向切片)技术是设计模式中的动态代理模式。只需实现jdk提供的动态代理接口InvocationHandler,所有被代理对象的方法都由InvocationHandler接管实际的处理任务。利用它很容易实现如权限拦截、运行期监控等功能。

●Spring中IOC(控制反转,也称依赖注入)利用了Java的反射机制来实现。组件之间的依赖关系由容器在运行期决定,,降低了程序之间的耦合度。系统中用到的对象不是在系统加载时就全部实例化,而是在调用到这个类时才会实例化该类的对象,从而提升了系统性能。其中依赖注入的方法有两种,通过构造函数注入,通过set方法进行注入。

注: JAVA反射机制是指动态获取信息以及动态调用对象方法的这种功能。在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性

●spring工作机制及为什么要用?
1.spring 将所有的请求都提交给DispatcherServlet。DispatcherServlet查询一个或多个HandlerMapping,找到处理请求的 Controller,将请求提交到目标Controller
2.Controller进行业务逻辑处理后,会返回一个ModelAndView
3.Dispathcher查询一个或多个ViewResolver视图解析器,找到ModelAndView对象指定的视图对象
4.视图对象负责渲染返回给客户端。

Spring的优点:
1、降低了组件之间的耦合性,实现了软件各层之间的解耦。
2、可以使用容易提供的众多服务,如事务管理,消息服务,日志记录等。
3、容器提供了AOP技术,利用它很容易实现如权限拦截、运行期监控等功能。

3

Hibernate

●谈谈Hibernate的理解,一级和二级缓存的作用,在项目中Hibernate都是怎么使用缓存的。

Hibernate是一种对象关系映射(ORM)框架, 提供了从 Java类到数据表的映射。它对JDBC进行了非常轻量级的对象封装,把对数据库的直接操作 , 转换为对持久对象的操作。

Hibernate工作原理:
1、配置hibernate对象关系映射文件、启动服务器
2、服务器通过实例化Configuration对象,读取hibernate.cfg.xml文件的配置内容,并根据相关的需求建好表以及表之间的映射关系。
3、通过实例化的Configuration对象建立SessionFactory实例,通过SessionFactory实例创建Session对象。
4、通过Session对象完成数据库的增删改查操作。

●注:Hibernate有四种查询方式:
1、get、load方法,根据id号查询对象。
2、HQL(Hibernate query language)语言
3、标准查询语言
4、通过sql查询

●原理及过程:
  1.读取并解析配置文件
  2.读取并解析映射信息,创建SessionFactory
  3.打开Sesssion
  4.创建事务Transation
  5.持久化操作
  6.提交事务
  7.关闭Session
  8.关闭SesstionFactory

●为什么要用?
1. 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。
2. Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现。他很大程度的简化DAO层的编码工作
3. hibernate使用Java反射机制,而不是字节码增强程序来实现透明性。
4. hibernate的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系。

问:
1,Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系)
类与类之间的关系主要体现在表与表之间的关系进行操作,它们都是对对象进行操作,我们程序中把所有的表与类都映射在一起,它们通过配置文件中的many-to-one、one-to-many、many-to-many

2,hibernate实现ORM原理?缓存机制是什么?
利用反射原理,将实体类中的字段按照xml配置或者annotation解析成一条或者多条sql语句,然后放入数据库执行

3,Hibernate是如何延迟加载?
(1). Hibernate2延迟加载实现:a)实体对象 b)集合(Collection)
(2). Hibernate3 提供了属性的延迟加载功能。当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。

●Hibernate中的状态转移:
(1)临时状态(transient):
当我们通过Java的new关键字来生成一个实体对象时,这时这个实体对象就处于临时状态。它还没有纳入Hibernate的缓存管理中,在数据库中不存在一条与它对应的记录。
java是如何进入临时状态的:1、通过new语句创建一个对象时。2、刚调用session的delete方法时,从seesion缓存中删除一个对象时。
(2)持久化状态(persisted):
持久化对象就是已经被保存进数据库的实体对象,并且这个实体对象现在还处于Hibernate的缓存管理之中。这时对该实体对象的任何修改,都会在清理缓存时同步到数据库中。
java如何进入持久化状态:1、seesion的save()方法。2、seesion的load().get()方法返回的对象。3、seesion的find()方法返回的list集合中存放的对象。4、Session的update().save()方法。
(3)游离状态(detached):
当一个持久化对象,脱离开Hibernate的缓存管理后,它就处于游离状态,游离对象和临时对象的最大区别在于,游离对象在数据库中可能还存在一条与它对应的记录,只是现在这个游离对象脱离了Hibernate的缓存管理,而临时对象不会在数据库中出现与它对应的数据记录。
java如何进入流离状态:1、Session的close()。2、Session的evict()方法,从缓存中删除一个对象。

●Hibernate缓存的作用:
Hibernate是一个持久层框架,经常访问物理数据库,通过缓存可以降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。

●Hibernate缓存机制:
Hibernate缓存包括两大类:Hibernate一级缓存和Hibernate二级缓存。

Hibernate一级缓存又称为“Session的缓存”,它是内置的,是必需的,不能被卸载。由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存。在第一级缓存中,持久化类的每个实例都具有唯一的OID。

Hibernate二级缓存又称为“SessionFactory的缓存”,由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,为被缓存的数据提供了事务隔离级别。第二级缓存是可选的,是一个可配置的插件,在默认情况下,SessionFactory不会启用这个插件。

●缓存的范围分为三类。
1 事务范围:缓存只能被当前事务访问。
2 进程范围:缓存被进程内的所有事务共享。
3 集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。

Hibernate二级缓存的并发访问策略有四种:只读(read-only)、非严格读写(nonstrict-read-write)、读写(read-write)和事务(transactional)。但是目前还没有二级缓存提供者完全支持所有的并发访问策略。
●只读(read-only): 对于永远不会被修改的数据可以采用这种并发访问策略,它的并发性能是最高的。但必须保证数据不会被修改,否则就会出错。
●非严格读写(nonstrict-read-write): 非严格读写不能保证缓存与数据库中数据的一致性,如果存在两个事务并发地访问缓存数据的可能,则应该为该数据配置一个很短的过期时间,以减少读脏数据的可能。对于极少被修改,并且可以容忍偶尔脏读的数据可以采用这种并发策略。
●读写(read-write): 读写策略提供了“read committed”数据库隔离级别。对于经常被读但很少修改的数据可以采用这种策略,它可以防止读脏数据。
●事务(transactional): 它提供了Repeatable Read事务隔离级别。它可以防止脏读和不可重复读这类的并发问题。

什么样的数据适合存放到第二级缓存中?
  1 很少被修改的数据
  2 不是很重要的数据,允许出现偶尔并发的数据
  3 不会被并发访问的数据
  4 参考数据
不适合存放到第二级缓存的数据?
  1 经常被修改的数据
  2 财务数据,绝对不允许出现并发
  3 与其他应用共享的数据。

 Hibernate的二级缓存策略的一般过程如下:
  1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。
  2) 把获得的所有数据对象根据ID放入到第二级缓存中。
  3) 当Hibernate根据ID访问数据对象的 时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
  4) 删除、更新、增加数据的 时候,同时更新缓存。

●如何优化Hibernate?
  1.使用双向一对多关联,不使用单向一对多
  2.灵活使用单向一对多关联
  3.不用一对一,用多对一取代
  4.配置对象缓存,不使用集合缓存
  5.一对多集合使用Bag,多对多集合使用Set
  6. 继承类使用显式多态
  7. 表字段要少,表关联可以多一点,有二级缓存撑腰

并发编程

生产者—消费者模型

生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据。

这里实现如下情况的生产–消费模型:
生产者不断交替地生产两组数据“姓名–1 –> 内容–1”,“姓名–2–> 内容–2”,消费者不断交替地取得这两组数据,这里的“姓名–1”和“姓名–2”模拟为数据的名称,“内容–1 ”和“内容–2 ”模拟为数据的内容。

由于本程序中牵扯到线程运行的不确定性,因此可能会出现以下问题:
1、假设生产者线程刚向数据存储空间添加了数据的名称,还没有加入该信息的内容,程序就切换到了消费者线程,消费者线程将把信息的名称和上一个信息的内容联系在一起;
2、生产者生产了若干次数据,消费者才开始取数据,或者是,消费者取完一次数据后,还没等生产者放入新的数据,又重复取出了已取过的数据。

问题1很明显要靠同步来解决,问题2则需要线程间通信,生产者线程放入数据后,通知消费者线程取出数据,消费者线程取出数据后,通知生产者线程生产数据,这里用wait/notify机制来实现。

详细的实现代码如下:

class Info{ // 定义信息类
    private String name = "name";//定义name属性,为了与下面set的name属性区别开
    private String content = "content" ;// 定义content属性,为了与下面set的content属性区别开
    private boolean flag = true ;   // 设置标志位,初始时先生产
    public synchronized void set(String name,String content){
        while(!flag){
            try{
                super.wait() ;
            }catch(InterruptedException e){
                e.printStackTrace() ;
            }
        }
        this.setName(name) ;    // 设置名称
        try{
            Thread.sleep(300) ;
        }catch(InterruptedException e){
            e.printStackTrace() ;
        }
        this.setContent(content) ;  // 设置内容
        flag  = false ; // 改变标志位,表示可以取走
        super.notify();
    }
    public synchronized void get(){
        while(flag){
            try{
                super.wait() ;
            }catch(InterruptedException e){
                e.printStackTrace() ;
            }
        }
        try{
            Thread.sleep(300) ;
        }catch(InterruptedException e){
            e.printStackTrace() ;
        }
        System.out.println(this.getName() + 
            " --> " + this.getContent()) ;
        flag  = true ;  // 改变标志位,表示可以生产
        super.notify();
    }
    public void setName(String name){
        this.name = name ;
    }
    public void setContent(String content){
        this.content = content ;
    }
    public String getName(){
        return this.name ;
    }
    public String getContent(){
        return this.content ;
    }
}
class Producer implements Runnable{ // 通过Runnable实现多线程
    private Info info = null ;      // 保存Info引用
    public Producer(Info info){
        this.info = info ;
    }
    public void run(){
        boolean flag = true ;   // 定义标记位
        for(int i=0;i<10;i++){
            if(flag){
                this.info.set("姓名--1","内容--1") ;    // 设置名称
                flag = false ;
            }else{
                this.info.set("姓名--2","内容--2") ;    // 设置名称
                flag = true ;
            }
        }
    }
}
class Consumer implements Runnable{
    private Info info = null ;
    public Consumer(Info info){
        this.info = info ;
    }
    public void run(){
        for(int i=0;i<10;i++){
            this.info.get() ;
        }
    }
}
public class ThreadCaseDemo03{
    public static void main(String args[]){
        Info info = new Info(); // 实例化Info对象
        Producer pro = new Producer(info) ; // 生产者
        Consumer con = new Consumer(info) ; // 消费者
        new Thread(pro).start() ;
        //启动了生产者线程后,再启动消费者线程
        try{
            Thread.sleep(500) ;
        }catch(InterruptedException e){
            e.printStackTrace() ;
        }

        new Thread(con).start() ;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值