我在sream159上买了这个游戏,但是却出现了打不开的问题,貌似是端口问题,该怎么解决?

java总结(基础知识-面试) - CSDN博客
java总结(基础知识-面试)
l&Java发射(案例)
l&反射含义:
l&JAVA反射机制是在中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
l&JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
l&【案例1】通过一个对象获得完整的包名和类名
l&package&R
l&* 通过一个对象获得完整的包名和类名
l&class&Demo{
l&//other codes...
l&class&hello{
l&public&static&void&main(String[] args) {
l&Demo demo=new&Demo();
l&System.out.println(demo.getClass().getName());
l&【运行结果】:Reflect.Demo
l&添加一句:所有类的对象其实都是Class的实例。
l&package&R
l&class&Demo{
l&//other codes...
l&class&hello{
l&public&static&void&main(String[] args) {
l&Class&?& demo1=null;
l&Class&?& demo2=null;
l&Class&?& demo3=null;
l&//一般尽量采用这种形式
l&demo1=Class.forName(&Reflect.Demo&);
l&}catch(Exception e){
l&e.printStackTrace();
l&demo2=new&Demo().getClass();
l&demo3=Demo.class;
l&System.out.println(&类名称&& &+demo1.getName());
l&System.out.println(&类名称&& &+demo2.getName());
l&System.out.println(&类名称&& &+demo3.getName());
l&【案例2】实例化Class类对象
l&【运行结果】:
l&类名称&& Reflect.Demo
l&类名称&& Reflect.Demo
l&类名称&& Reflect.Demo
l&【案例3】通过Class实例化其他类的对象
l&通过无参构造实例化对象
l&package&R
l&class&Person{
l&public&String getName() {
l&public&void&setName(String name) {
l&this.name =
l&public&int&getAge() {
l&public&void&setAge(int&age) {
l&this.age =
l&@Override
l&public&String toString(){
l&return&&[&+this.name+&&
&+this.age+&]&;
l&private&S
l&private&int&
l&class&hello{
l&public&static&void&main(String[] args) {
l&Class&?& demo=null;
l&demo=Class.forName(&Reflect.Person&);
l&}catch&(Exception e) {
l&e.printStackTrace();
l&Person per=null;
l&per=(Person)demo.newInstance();
l&}&catch&(InstantiationException e) {
l&// TODO Auto-generated catch block
l&e.printStackTrace();
l&}&catch&(IllegalAccessException e) {
l&// TODO Auto-generated catch block
l&e.printStackTrace();
l&per.setName(&Rollen&);
l&per.setAge(20);
l&System.out.println(per);
l&【运行结果】:
l&[Rollen& 20]
l&但是注意一下,当我们把Person中的默认的无参构造函数取消的时候,比如自己定义只定义一个有参数的构造函数之后,会出现错误:
l&比如我定义了一个构造函数:
l&public&Person(String name,&int&age) {
l&this.age=
l&this.name=
l&然后继续运行上面的程序,会出现:
l&java.lang.InstantiationException: Reflect.Person
l&at java.lang.Class.newInstance0(Class.java:340)
l&at java.lang.Class.newInstance(Class.java:308)
l&at Reflect.hello.main(hello.java:39)
l&Exception in thread &main&&java.lang.NullPointerException
l&at Reflect.hello.main(hello.java:47)
l&所以大家以后再编写使用Class实例化其他类的对象的时候,一定要自己定义无参的构造函数
l&【案例3】通过Class调用其他类中的构造函数&(也可以通过这种方式通过Class创建其他类的对象)
l&package&R
l&import&java.lang.reflect.C
l&class&Person{
l&public&Person() {
l&public&Person(String name){
l&this.name=
l&public&Person(int&age){
l&this.age=
l&public&Person(String name,&int&age) {
l&this.age=
l&this.name=
l&public&String getName() {
l&public&int&getAge() {
l&@Override
l&public&String toString(){
l&return&&[&+this.name+&& &+this.age+&]&;
l&private&S
l&private&int&
l&class&hello{
l&public&static&void&main(String[] args) {
l&Class&?& demo=null;
l&demo=Class.forName(&Reflect.Person&);
l&}catch&(Exception e) {
l&e.printStackTrace();
l&Person per1=null;
l&Person per2=null;
l&Person per3=null;
l&Person per4=null;
l&//取得全部的构造函数
l&Constructor&?& cons[]=demo.getConstructors();
l&per1=(Person)cons[0].newInstance();
l&per2=(Person)cons[1].newInstance(&Rollen&);
l&per3=(Person)cons[2].newInstance(20);
l&per4=(Person)cons[3].newInstance(&Rollen&,20);
l&}catch(Exception e){
l&e.printStackTrace();
l&System.out.println(per1);
l&System.out.println(per2);
l&System.out.println(per3);
l&System.out.println(per4);
l&【运行结果】:
l&[null& 0]
l&[Rollen& 0]
l&[null& 20]
l&[Rollen& 20]
l&【案例】
l&返回一个类实现的接口:
l&package&R
l&interface&China{
l&public&static&final&String name=&Rollen&;
l&public&static&&int&age=20;
l&public&void&sayChina();
l&public&void&sayHello(String name,&int&age);
l&class&Person&implements&China{
l&public&Person() {
l&public&Person(String sex){
l&this.sex=
l&public&String getSex() {
l&public&void&setSex(String sex) {
l&this.sex =
l&@Override
l&public&void&sayChina(){
l&System.out.println(&hello ,china&);
l&@Override
l&public&void&sayHello(String name,&int&age){
l&System.out.println(name+&& &+age);
l&private&S
l&class&hello{
l&public&static&void&main(String[] args) {
l&Class&?& demo=null;
l&demo=Class.forName(&Reflect.Person&);
l&}catch&(Exception e) {
l&e.printStackTrace();
l&//保存所有的接口
l&Class&?& intes[]=demo.getInterfaces();
l&for&(int&i =&0; i & intes. i++) {
l&System.out.println(&实现的接口&& &+intes[i].getName());
l&【运行结果】:
l&实现的接口&& Reflect.China
l&(注意,以下几个例子,都会用到这个例子的Person类,所以为节省篇幅,此处不再粘贴Person的代码部分,只粘贴主类hello的代码)
l&【案例4】:取得其他类中的父类
l&class&hello{
l&public&static&void&main(String[] args) {
l&Class&?& demo=null;
l&demo=Class.forName(&Reflect.Person&);
l&}catch&(Exception e) {
l&e.printStackTrace();
l&//取得父类
l&Class&?& temp=demo.getSuperclass();
l&System.out.println(&继承的父类为:&& &+temp.getName());
l&【运行结果】
l&继承的父类为:&& java.lang.Object
l&【案例】:获得其他类中的全部构造函数
l&这个例子需要在程序开头添加import&java.lang.reflect.*;
l&然后将主类编写为:
l&class&hello{
l&public&static&void&main(String[] args) {
l&Class&?& demo=null;
l&demo=Class.forName(&Reflect.Person&);
l&}catch&(Exception e) {
l&e.printStackTrace();
l&Constructor&?&cons[]=demo.getConstructors();
l&for&(int&i =&0; i & cons. i++) {
l&System.out.println(&构造方法:& &+cons[i]);
l&【运行结果】:
l&构造方法:& public Reflect.Person()
l&构造方法:& public Reflect.Person(java.lang.String)
l&但是细心的读者会发现,上面的构造函数没有public&或者private这一类的修饰符
l&下面这个例子我们就来获取修饰符
l&class&hello{
l&public&static&void&main(String[] args) {
l&Class&?& demo=null;
l&demo=Class.forName(&Reflect.Person&);
l&}catch&(Exception e) {
l&e.printStackTrace();
l&Constructor&?&cons[]=demo.getConstructors();
l&for&(int&i =&0; i & cons. i++) {
l&Class&?& p[]=cons[i].getParameterTypes();
l&System.out.print(&构造方法:& &);
l&int&mo=cons[i].getModifiers();
l&System.out.print(Modifier.toString(mo)+& &);
l&System.out.print(cons[i].getName());
l&System.out.print(&(&);
l&for(int&j=0;j&p.++j){
l&System.out.print(p[j].getName()+& arg&+i);
l&if(j&p.length-1){
l&System.out.print(&,&);
l&System.out.println(&){}&);
l&【运行结果】:
l&构造方法:& public Reflect.Person(){}
l&构造方法:& public Reflect.Person(java.lang.String arg1){}
l&有时候一个方法可能还有异常,呵呵。下面看看:
l&class&hello{
l&public&static&void&main(String[] args) {
l&Class&?& demo=null;
l&demo=Class.forName(&Reflect.Person&);
l&}catch&(Exception e) {
l&e.printStackTrace();
l&Method method[]=demo.getMethods();
l&for(int&i=0;i&method.++i){
l&Class&?& returnType=method[i].getReturnType();
l&Class&?& para[]=method[i].getParameterTypes();
l&int&temp=method[i].getModifiers();
l&System.out.print(Modifier.toString(temp)+& &);
l&System.out.print(returnType.getName()+&& &);
l&System.out.print(method[i].getName()+& &);
l&System.out.print(&(&);
l&for(int&j=0;j&para.++j){
l&System.out.print(para[j].getName()+& &+&arg&+j);
l&if(j&para.length-1){
l&System.out.print(&,&);
l&Class&?& exce[]=method[i].getExceptionTypes();
l&if(exce.length&0){
l&System.out.print(&) throws &);
l&for(int&k=0;k&exce.++k){
l&System.out.print(exce[k].getName()+& &);
l&if(k&exce.length-1){
l&System.out.print(&,&);
l&System.out.print(&)&);
l&System.out.println();
l&【运行结果】:
l&public java.lang.String& getSex ()
l&public void& setSex (java.lang.String arg0)
l&public void& sayChina ()
l&public void& sayHello (java.lang.String arg0,int arg1)
l&public final native void& wait (long arg0) throws&java.lang.InterruptedException
l&public final void& wait () throws&java.lang.InterruptedException
l&public final void& wait (long arg0,int arg1) throws&java.lang.InterruptedException
l&public boolean& equals (java.lang.Object arg0)
l&public java.lang.String& toString ()
l&public native int& hashCode ()
l&public final native java.lang.Class& getClass ()
l&public final native void& notify ()
l&public final native void& notifyAll ()
l&【案例6】接下来让我们取得其他类的全部属性吧,最后我讲这些整理在一起,也就是通过class取得一个类的全部框架
l&class&hello {
l&public&static&void&main(String[] args) {
l&Class&?& demo =&null;
l&demo = Class.forName(&Reflect.Person&);
l&}&catch&(Exception e) {
l&e.printStackTrace();
l&System.out.println(&===============本类属性========================&);
l&// 取得本类的全部属性
l&Field[] field = demo.getDeclaredFields();
l&for&(int&i =&0; i & field. i++) {
l&// 权限修饰符
l&int&mo = field[i].getModifiers();
l&String priv = Modifier.toString(mo);
l&// 属性类型
l&Class&?& type = field[i].getType();
l&System.out.println(priv +&& &&+ type.getName() +&& &
l&+ field[i].getName() +&&;&);
l&System.out.println(&===============实现的接口或者父类的属性========================&);
l&// 取得实现的接口或者父类的属性
l&Field[] filed1 = demo.getFields();
l&for&(int&j =&0; j & filed1. j++) {
l&// 权限修饰符
l&int&mo = filed1[j].getModifiers();
l&String priv = Modifier.toString(mo);
l&// 属性类型
l&Class&?& type = filed1[j].getType();
l&System.out.println(priv +&& &&+ type.getName() +&& &
l&+ filed1[j].getName() +&&;&);
l&【运行结果】:
l&===============本类属性========================
l&private java.lang.S
l&===============实现的接口或者父类的属性========================
l&public static final java.lang.S
l&【案例】其实还可以通过反射调用其他类中的方法:
l&class&hello {
l&public&static&void&main(String[] args) {
l&Class&?& demo =&null;
l&demo = Class.forName(&Reflect.Person&);
l&}&catch&(Exception e) {
l&e.printStackTrace();
l&//调用Person类中的sayChina方法
l&Method method=demo.getMethod(&sayChina&);
l&method.invoke(demo.newInstance());
l&//调用Person的sayHello方法
l&method=demo.getMethod(&sayHello&, String.class,int.class);
l&method.invoke(demo.newInstance(),&Rollen&,20);
l&}catch&(Exception e) {
l&e.printStackTrace();
l&【运行结果】:
l&hello ,china
l&Rollen& 20
l&【案例】调用其他类的set和get方法
l&class&hello {
l&public&static&void&main(String[] args) {
l&Class&?& demo =&null;
l&Object obj=null;
l&demo = Class.forName(&Reflect.Person&);
l&}&catch&(Exception e) {
l&e.printStackTrace();
l&obj=demo.newInstance();
l&}catch&(Exception e) {
l&e.printStackTrace();
l&setter(obj,&Sex&,&男&,String.class);
l&getter(obj,&Sex&);
l&* @param obj
l&*&&&&&&&&&&& 操作的对象
l&* @param att
l&*&&&&&&&&&&& 操作的属性
l&public&static&void&getter(Object obj, String att) {
l&Method method = obj.getClass().getMethod(&get&&+ att);
l&System.out.println(method.invoke(obj));
l&}&catch&(Exception e) {
l&e.printStackTrace();
l&* @param obj
l&*&&&&&&&&&&& 操作的对象
l&* @param att
l&*&&&&&&&&&&& 操作的属性
l&* @param value
l&*&&&&&&&&&&& 设置的值
l&* @param type
l&*&&&&&&&&&&& 参数的属性
l&public&static&void&setter(Object obj, String att, Object value,
l&Class&?& type) {
l&Method method = obj.getClass().getMethod(&set&&+ att, type);
l&method.invoke(obj, value);
l&}&catch&(Exception e) {
l&e.printStackTrace();
l&}// end class
l&【运行结果】:
l&【案例】通过反射操作属性
l&class&hello {
l&public&static&void&main(String[] args)&throws&Exception
l&Class&?& demo =&null;
l&Object obj =&null;
l&demo = Class.forName(&Reflect.Person&);
l&obj = demo.newInstance();
l&Field field = demo.getDeclaredField(&sex&);
l&field.setAccessible(true);
l&field.set(obj,&&男&);
l&System.out.println(field.get(obj));
l&}// end class
l&【案例】通过反射取得并修改数组的信息:
l&import&java.lang.reflect.*;
l&class&hello{
l&public&static&void&main(String[] args) {
l&int[] temp={1,2,3,4,5};
l&Class&?&demo=temp.getClass().getComponentType();
l&System.out.println(&数组类型: &+demo.getName());
l&System.out.println(&数组长度& &+Array.getLength(temp));
l&System.out.println(&数组的第一个元素: &+Array.get(temp,&0));
l&Array.set(temp,&0,&100);
l&System.out.println(&修改之后数组第一个元素为: &+Array.get(temp,&0));
l&【运行结果】:
l&数组类型:&int
l&数组长度& 5
l&数组的第一个元素: 1
l&修改之后数组第一个元素为:&100
l&【案例】通过反射修改数组大小
l&class&hello{
l&public&static&void&main(String[] args) {
l&int[] temp={1,2,3,4,5,6,7,8,9};
l&int[] newTemp=(int[])arrayInc(temp,15);
l&print(newTemp);
l&System.out.println(&=====================&);
l&String[] atr={&a&,&b&,&c&};
l&String[] str1=(String[])arrayInc(atr,8);
l&print(str1);
l&* 修改数组大小
l&public&static&Object arrayInc(Object obj,int&len){
l&Class&?&arr=obj.getClass().getComponentType();
l&Object newArr=Array.newInstance(arr, len);
l&int&co=Array.getLength(obj);
l&System.arraycopy(obj,&0, newArr,&0, co);
l&return&newA
l&public&static&void&print(Object obj){
l&Class&?&c=obj.getClass();
l&if(!c.isArray()){
l&System.out.println(&数组长度为: &+Array.getLength(obj));
l&for&(int&i =&0; i & Array.getLength(obj); i++) {
l&System.out.print(Array.get(obj, i)+& &);
l&【运行结果】:
l&数组长度为:&15
l&1 2 3 4 5 6 7 8 9 0 0 0 0 0 0 =====================
l&数组长度为:&8
l&a b c null null null null null
l&动态代理
l&【案例】首先来看看如何获得类加载器:
l&class&test{
l&class&hello{
l&public&static&void&main(String[] args) {
l&test t=new&test();
l&System.out.println(&类加载器& &+t.getClass().getClassLoader().getClass().getName());
l&【程序输出】:
l&类加载器& sun.misc.Launcher$AppClassLoader
l&其实在java中有三种类类加载器。
l&1)Bootstrap ClassLoader&此加载器采用c++编写,一般开发中很少见。
l&2)Extension ClassLoader&用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类
l&3)AppClassLoader&加载classpath指定的类,是最常用的加载器。同时也是java中默认的加载器。
l&如果想要完成动态代理,首先需要定义一个InvocationHandler接口的子类,已完成代理的具体操作。
l&package&R
l&import&java.lang.reflect.*;
l&//定义项目接口
l&interface&Subject {
l&public&String say(String name,&int&age);
l&// 定义真实项目
l&class&RealSubject&implements&Subject {
l&@Override
l&public&String say(String name,&int&age) {
l&return&name +&&& &&+
l&class&MyInvocationHandler&implements&InvocationHandler {
l&private&Object obj =&null;
l&public&Object bind(Object obj) {
l&this.obj =
l&return&Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
l&.getClass().getInterfaces(),&this);
l&@Override
l&public&Object invoke(Object proxy, Method method, Object[] args)
l&throws&Throwable {
l&Object temp = method.invoke(this.obj, args);
l&class&hello {
l&public&static&void&main(String[] args) {
l&MyInvocationHandler demo =&new&MyInvocationHandler();
l&Subject sub = (Subject) demo.bind(new&RealSubject());
l&String info = sub.say(&Rollen&,&20);
l&System.out.println(info);
l&【运行结果】:
l&Rollen& 20
l&类的生命周期
l&在一个类编译完成之后,下一步就需要开始使用类,如果要使用一个类,肯定离不开JVM。在程序执行中JVM通过装载,链接,初始化这3个步骤完成。
l&类的装载是通过类加载器完成的,加载器将.class文件的二进制文件装入JVM的方法区,并且在堆区创建描述这个类的java.lang.Class对象。用来封装数据。但是同一个类只会被类装载器装载以前
l&链接就是把二进制数据组装为可以运行的状态。
l&链接分为校验,准备,解析这3个阶段
l&校验一般用来确认此二进制文件是否适合当前的JVM(版本),
l&准备就是为静态成员分配内存空间,。并设置默认值
l&解析指的是转换常量池中的代码作为直接引用的过程,直到所有的符号引用都可以被运行程序使用(建立完整的对应关系)
l&完成之后,类型也就完成了初始化,初始化之后类的对象就可以正常使用了,直到一个对象不再使用之后,将被垃圾回收。释放空间。
l&当没有任何引用指向Class对象时就会被卸载,结束类的生命周期
l&将反射用于工厂模式
l&先来看看,如果不用反射的时候,的工厂模式吧:
l&* @author Rollen-Holt 设计模式之 工厂模式
l&interface&fruit{
l&public&abstract&void&eat();
l&class&Apple&implements&fruit{
l&public&void&eat(){
l&System.out.println(&Apple&);
l&class&Orange&implements&fruit{
l&public&void&eat(){
l&System.out.println(&Orange&);
l&// 构造工厂类
l&// 也就是说以后如果我们在添加其他的实例的时候只需要修改工厂类就行了
l&class&Factory{
l&public&static&fruit getInstance(String fruitName){
l&fruit f=null;
l&if(&Apple&.equals(fruitName)){
l&f=new&Apple();
l&if(&Orange&.equals(fruitName)){
l&f=new&Orange();
l&return&f;
l&class&hello{
l&public&static&void&main(String[] a){
l&fruit f=Factory.getInstance(&Orange&);
l&f.eat();
l&这样,当我们在添加一个子类的时候,就需要修改工厂类了。如果我们添加太多的子类的时候,改的就会很多。
l&现在我们看看利用反射机制:
l&package&R
l&interface&fruit{
l&public&abstract&void&eat();
l&class&Apple&implements&fruit{
l&public&void&eat(){
l&System.out.println(&Apple&);
l&class&Orange&implements&fruit{
l&public&void&eat(){
l&System.out.println(&Orange&);
l&class&Factory{
l&public&static&fruit getInstance(String ClassName){
l&fruit f=null;
l&f=(fruit)Class.forName(ClassName).newInstance();
l&}catch&(Exception e) {
l&e.printStackTrace();
l&return&f;
l&class&hello{
l&public&static&void&main(String[] a){
l&fruit f=Factory.getInstance(&Reflect.Apple&);
l&if(f!=null){
l&f.eat();
l&现在就算我们添加任意多个子类的时候,工厂类就不需要修改。
l&上面的爱吗虽然可以通过反射取得接口的实例,但是需要传入完整的包和类名。而且用户也无法知道一个接口有多少个可以使用的子类,所以我们通过属性文件的形式配置所需要的子类。
l&下面我们来看看:&结合属性文件的工厂模式
l&首先创建一个fruit.properties的资源文件,
l&内容为:
l&apple=Reflect.Apple
l&orange=Reflect.Orange
l&然后编写主类代码:
l&package&R
l&import&java.io.*;
l&import&java.util.*;
l&interface&fruit{
l&public&abstract&void&eat();
l&class&Apple&implements&fruit{
l&public&void&eat(){
l&System.out.println(&Apple&);
l&class&Orange&implements&fruit{
l&public&void&eat(){
l&System.out.println(&Orange&);
l&//操作属性文件类
l&class&init{
l&public&static&Properties getPro()&throws&FileNotFoundException, IOException{
l&Properties pro=new&Properties();
l&File f=new&File(&fruit.properties&);
l&if(f.exists()){
l&pro.load(new&FileInputStream(f));
l&pro.setProperty(&apple&,&&Reflect.Apple&);
l&pro.setProperty(&orange&,&&Reflect.Orange&);
l&pro.store(new&FileOutputStream(f),&&FRUIT CLASS&);
l&class&Factory{
l&public&static&fruit getInstance(String ClassName){
l&fruit f=null;
l&f=(fruit)Class.forName(ClassName).newInstance();
l&}catch&(Exception e) {
l&e.printStackTrace();
l&return&f;
l&class&hello{
l&public&static&void&main(String[] a)&throws&FileNotFoundException,
IOException{
l&Properties pro=init.getPro();
l&fruit f=Factory.getInstance(pro.getProperty(&apple&));
l&if(f!=null){
l&f.eat();
l&【运行结果】:Apple
Java 集合框架
前言:数据结构对程序设计有着深远的影响,在面向过程的C语言中,数据库结构用struct来描述,而在面向对象的编程中,数据结构是用类来描述的,并且包含有对该数据结构操作的方法。
在Java语言中,Java语言的设计者对常用的数据结构和算法做了一些规范(接口)和实现(具体实现接口的类)。所有抽象出来的数据结构和操作(算法)统称为Java集合框架(JavaCollectionFramework)。
Java程序员在具体应用时,不必考虑数据结构和算法实现细节,只需要用这些类创建出来一些对象,然后直接应用就可以了,这样就大大提高了编程效率。
1. 先说Set和List:
1.1. Set子接口:无序,不允许重复。List子接口:有序,可以有重复元素。具体区别是
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。&对应类有
HashSet,TreeSet&
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。&相应类有
ArrayList,LinkedList,Vector&
Set和List具体子类:
2.2.&&实例比较&
HashSet:以哈希表的形式存放元素,插入删除速度很快。
ArrayList:动态数组,LinkedList:链表、队列、堆栈。
Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用
1.Collection接口
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些
Collection允许相同的元素而另一些不行。一些能排序而另一些不行。JavaSDK不提供直接继承自Collection的类,JavaSDK提供的类都是继承自Collection的“子接口”如List和Set。
所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个
Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。
如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
Iteratorit=collection.iterator();//获得一个迭代子
while(it.hasNext()){
Objectobj=it.next();//得到下一个元素
由Collection接口派生的两个接口是List和Set。
2.List接口
List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
和下面要提到的Set不同,List允许有相同的元素。
除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个
ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。
实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。
2.1.LinkedList类
LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在
LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
Listlist=Collections.synchronizedList(newLinkedList(...));
2.2.ArrayList类
ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。
size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率(自动增判断长度后增长也会浪费时间的呀!)。
和LinkedList一样,ArrayList也是非同步的(unsynchronized)。(扩展阅读:在java.util.concurrent包中定义的CopyOnWriteArrayList提供了线程安全的Arraylist,但是当进行add和set等变化操作时它是通过为底层数组创建新的副本实现的,所以比较耗费资源
(源码在此:publicboolean&add(E e) {
finalReentrantLock&lock =this.lock;
lock.lock();
Object[] elements = getArray();
int&len = elements.length;
Object[] newElements = Arrays.copyOf(elements,len + 1);
newElements[len] =
setArray(newElements);
return&true;
}&finally&{
lock.unlock();
但是如果存在频繁遍历,遍历操作比变化(写入和修改)操作多的时候这种遍历就相对于自己进行的同步遍历效果要好,而且它也允许存在null元素)
2.3.Vector类
Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的
Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。通过使用capacity和ensurecapacity操作以及capacityIncrement域可以优化存储操作,这个前面讲过,(Vector的Iterator和listIterator方法翻译的迭代器支持fail-fast机制,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。官方对此的说明是&java.util&包中的集合类都返回&fail-fast迭代器,这意味着它们假设线程在集合内容中进行迭代时,集合不会更改它的内容。如果&fail-fast迭代器检测到在迭代过程中进行了更改操作,那么它会抛出&ConcurrentModificationException,这是不可控异常。)
2.4.Stack类
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
stack&有几个比较实用的方法
测试堆栈是否为空。
查看堆栈顶部的对象,但不从堆栈中移除它。
移除堆栈顶部的对象,并作为此函数的值返回该对象。
push(item)
把项压入堆栈顶部。
返回对象在堆栈中的位置,以
1 为基数。
3.&set接口:
Set具有与Collection完全一样的接口,因此没有任何额外的功能,不像前面有两个不同的List。实际上Set就是Collection,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)Set不保存重复的元素(至于如何判断元素相同则较为负责)
Set : 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。(我变换黄色背景那里的名称得到如下特点)
HashSet : 它不允许出现重复元素;不保证和政集合中元素的顺序,可以自己做个例子可以看出加入的字段顺序跟遍历出的不一样,允许包含值为null的元素,但最多只能有一个null元素(不允许重复嘛!)。
TreeSet : 可以实现排序等功能的集合,它在讲对象元素添加到集合中时会自动按照某种比较规则将其插入到有序的对象序列中,并保证该集合元素组成按照“升序”排列。
a)(在对大量信息进行检索的时候,TreeSet比AraayList更有效率,能保证在log(n)的时间内完成)。
b)TreeSet是实用树形结构来存储信息的,每个节点都会保存一下指针对象,分别指向父节点,左分支,右分支,相比较而言,ArrayList就是一个含有元素的简单数组了,正因为如此,它占的内存也要比ArrayList多一些。
c)想TreeSet插入元素也比ArrayList要快一些,因为当元素插入到ArrayList的任意位置时,平均每次要移动一半的列表,需要O(n)的时间,
而TreeSet深度遍历查询花费的实施只需要O(log(n))(普遍的都是,set查询慢,插入快,list查询快,插入满,
.TODO:这一点我会写一个算法测试文章具体分析一下…)
LinkedHashSet :
具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
PS:set有几个比较好的方法:
removeAll(Collection&?& c)
中那些包含在指定 collection
中的元素(可选操作)。
boolean retainAll(Collection&?& c)
仅保留 set
中那些包含在指定 collection
中的元素(可选操作)。
containsAll(Collection&?& c)
如果此 set
包含指定 collection
的所有元素,则返回 true。
4.Queue数据结构
这方面知识涉及到线程比较多,有线程基础的口语参考这篇文章
http://blog.csdn.net/a/article/details/
5.Map的功能方法
java为数据结构中的映射定义了一个接口java.util.M它有四个实现类,分别是HashMap
Hashtable LinkedHashMap&和TreeMap
Map主要用于存储健值对,根据键得到值,因此不允许键重复,但允许值重复。
Hashmap 是一个 最常用的Map,它根据键的HashCode
值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为N允许多条记录的值为
NHashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashM可能会导致数据的不一致。如果需要同步,可以用
Collections的synchronizedMap方法使HashMap具有同步的能力.
Hashtable 与HashMap类似,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢。
LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.在遍历
的时候会比HashMap慢。
TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator
遍历TreeMap时,得到的记录是排过序的。
package com.hxw.T2;
import java.util.HashM
import java.util.H
import java.util.I
import java.util.LinkedHashM
import java.util.M
import java.util.TreeM
public class MapTester {
public static void init(Map map) {
if (map != null) {
String key =
for (int i = 5; i & 0; i--) {
key = new Integer(i).toString() + &.0&;
map.put(key, key.toString());
// Map中的键是不重复的,如果插入两个键值一样的记录,
// 那么后插入的记录会覆盖先插入的记录
map.put(key, key.toString() + &0&);
public static void output(Map map) {
if (map != null) {
Object key =
Object value =
// 使用迭代器遍历Map的键,根据键取值
Iterator it = map.keySet().iterator();
while (it.hasNext()) {
key = it.next();
value = map.get(key);
System.out.println(&key: & + key + &; value: & + value);
// 或者使用迭代器遍历Map的记录Map.Entry
Map.Entry entry =
it = map.entrySet().iterator();
while (it.hasNext()) {
// 一个Map.Entry代表一条记录
entry = (Map.Entry) it.next();
// 通过entry可以获得记录的键和值
// System.out.println(&key: & + entry.getKey() + &; value: & +
// entry.getValue());
public static boolean containsKey(Map map, Object key) {
if (map != null) {
return map.containsKey(key);
public static boolean containsValue(Map map, Object value) {
if (map != null) {
return map.containsValue(value);
public static void testHashMap() {
Map myMap = new HashMap();
init(myMap);
// HashMap的键可以为null
myMap.put(null, &ddd&);
// HashMap的值可以为null
myMap.put(&aaa&, null);
output(myMap);
public static void testHashtable() {
Map myMap = new Hashtable();
init(myMap);
// Hashtable的键不能为null
// myMap.put(null,&ddd&);
// Hashtable的值不能为null
// myMap.put(&aaa&, null);
output(myMap);
public static void testLinkedHashMap() {
Map myMap = new LinkedHashMap();
init(myMap);
// LinkedHashMap的键可以为null
myMap.put(null, &ddd&);
// LinkedHashMap的值可以为null
myMap.put(&aaa&, null);
output(myMap);
public static void testTreeMap() {
Map myMap = new TreeMap();
init(myMap);
// TreeMap的键不能为null
// myMap.put(null,&ddd&);
// TreeMap的值不能为null
// myMap.put(&aaa&, null);
output(myMap);
public static void main(String[] args) {
System.out.println(&采用HashMap&);
MapTester.testHashMap();
System.out.println(&采用Hashtable&);
MapTester.testHashtable();
System.out.println(&采用LinkedHashMap&);
MapTester.testLinkedHashMap();
System.out.println(&采用TreeMap&);
MapTester.testTreeMap();
Map myMap = new HashMap();
MapTester.init(myMap);
System.out.println(&新初始化一个Map: myMap&);
MapTester.output(myMap);
// 清空Map
myMap.clear();
System.out.println(&将myMap clear后,myMap空了么? && + myMap.isEmpty());
MapTester.output(myMap);
myMap.put(&aaa&, &aaaa&);
myMap.put(&bbb&, &bbbb&);
// 判断Map是否包含某键或者某值
System.out.println(&myMap包含键aaa? &&
+ MapTester.containsKey(myMap, &aaa&));
System.out.println(&myMap包含值aaaa? &&
+ MapTester.containsValue(myMap, &aaaa&));
// 根据键删除Map中的记录
myMap.remove(&aaa&);
System.out.println(&删除键aaa后,myMap包含键aaa? &&
+ MapTester.containsKey(myMap, &aaa&));
// 获取Map的记录数
System.out.println(&myMap包含的记录数: && + myMap.size());
遍历的四种方法:
public static void main(String[] args) {
&&Map&String, String& map = new HashMap&String, String&();
&&map.put(&1&, &value1&);
&&map.put(&2&, &value2&);
&&map.put(&3&, &value3&);
&&//第一种:普遍使用,二次取值
&&System.out.println(&通过Map.keySet遍历key和value:&);
&&for (String key : map.keySet()) {
&&&System.out.println(&key= &+ key + & and value= & + map.get(key));
&&//第二种
&&System.out.println(&通过Map.entrySet使用iterator遍历key和value:&);
&&Iterator&Map.Entry&String, String&& it = map.entrySet().iterator();
&&while (it.hasNext()) {
&&&Map.Entry&String, String& entry = it.next();
&&&System.out.println(&key= & + entry.getKey() + & and value= & + entry.getValue());
&&//第三种:推荐,尤其是容量大时
&&System.out.println(&通过Map.entrySet遍历key和value&);
&&for (Map.Entry&String, String& entry : map.entrySet()) {
&&&System.out.println(&key= & + entry.getKey() + & and value= & + entry.getValue());
&&//第四种
&&System.out.println(&通过Map.values()遍历所有的value,但不能遍历key&);
&&for (String v : map.values()) {
&&&System.out.println(&value= & + v);
3、其他特征
*List,Set,Map将持有对象一律视为Object型别。
*Collection、List、Set、Map都是接口,不能实例化。
继承自它们的 ArrayList, Vector, HashTable, HashMap是具象class,这些才可被实例化。
*vector容器确切知道它所持有的对象隶属什么型别。vector不进行边界检查。
三、Collections
Collections是针对集合类的一个帮助类。提供了一系列静态方法实现对各种集合的搜索、排序、线程完全化等操作。
相当于对Array进行类似操作的类——Arrays。
如,Collections.max(Collection coll);
取coll中最大的元素。
Collections.sort(List list);
对list中元素排序
四、如何选择?
1、容器类和Array的区别、择取
* 容器类仅能持有对象引用(指向对象的指针),而不是将对象信息copy一份至数列某位置。
* 一旦将对象置入容器内,便损失了该对象的型别信息。
* 在各种Lists中,最好的做法是以ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList();
Vector总是比ArrayList慢,所以要尽量避免使用。
* 在各种Sets中,HashSet通常优于TreeSet(插入、查找)。只有当需要产生一个经过排序的序列,才用TreeSet。
TreeSet存在的唯一理由:能够维护其内元素的排序状态。
* 在各种Maps中
HashMap用于快速查找。
* 当元素个数固定,用Array,因为Array效率是最高的。
结论:最常用的是ArrayList,HashSet,HashMap,Array。而且,我们也会发现一个规律,用TreeXXX都是排序的。
1、Collection没有get()方法来取得某个元素。只能通过iterator()遍历元素。
2、Set和Collection拥有一模一样的接口。
3、List,可以通过get()方法来一次取出一个元素。使用数字来选择一堆对象中的一个,get(0)...。(add/get)
4、一般使用ArrayList。用LinkedList构造堆栈stack、队列queue。
put(k,v) / get(k),还可以使用containsKey()/containsValue()来检查其中是否含有某个key/value。
HashMap会利用对象的hashCode来快速找到key。
哈希码就是将对象的信息经过一些转变形成一个独一无二的int值,这个值存储在一个array中。
我们都知道所有存储结构中,array查找速度是最快的。所以,可以加速查找。
发生碰撞时,让array指向多个values。即,数组每个位置上又生成一个梿表。
6、Map中元素,可以将key序列、value序列单独抽取出来。
使用keySet()抽取key序列,将map中的所有keys生成一个Set。
使用values()抽取value序列,将map中的所有values生成一个Collection。
为什么一个生成Set,一个生成Collection?那是因为,key总是独一无二的,value允许重复。
Java提供了数种持有对象的方式,包括语言内置的Array,还有就是utilities中提供的容器类(container
classes),又称群集类(collection classes)。集合在java中非常重要,在讨论之前,先来看几个面试中的经典问题。&
1 Collection 和 Collections的区别。&
2 List, Set, Map是否继承自Collection接口。&
3 ArrayList和Vector的区别。&
4 HashMap和Hashtable的区别。&
篇尾有答案,我们开始正题。&&&
集合Collection接口&&
--Collection 是任何对象组,元素各自独立,通常拥有相同的套用规则。Set List由它派生。
基本操作& 增加元素add(Object obj); addAll(Collection c);&&
删除元素 remove(Object obj);&removeAll(Collection c);&
求交集 retainAll(Collection c);&&
删除元素 remove(Object obj);&removeAll(Collection c);&
求交集 retainAll(Collection c);&&
访问/遍历集合元素的好办法是使用Iterator接口(迭代器用于取代Enumeration)
Public&interface&Iterator{&
&&&Public&Boolean&hasNext(};&
&&&Public&Object&next(};&
&&&Public&void&remove(};&
--没有重复项目的集合&&
有三种特定类型的集可用&
HashSet-基于散列表的集,加进散列表的元素要实现hashCode()方法&&
LinkedHashSet-对集迭代时,按增加顺序返回元素&
TreeSet-基于(平衡)树的数据结构&&&&
--位置性集合。加进清单的元素可以加在清单中特定位置或加到末尾&&
有两个特定版本
ArrayList(数组表)-类似于Vector,都用于缩放数组维护集合。区别:
一.同步性:Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同步的&
二.数据增长:当需要增长时,Vector默认增长为原来一培,而ArrayList却是原来的一半&
LinkedList(链表)-是双向链表,每个节点都有两个指针指向上一节点和下一节点。&
用在FIFO,用addList()加入元素
removeFirst()删除元素
用在FILO,用addFirst()/removeLast()&&
ListIterator提供双向遍历next() previous(),可删除、替换、增加元素&&
映射表Map&&
--用于关键字/数值对,像个Dictionary&&
处理Map的三种集合&&
关键字集KeySet()&&
数值集value()&
项目集enrySet()&&
四个具体版本
HashMap-散列表的通用映射表&
LinkedHashMap-扩展HashMap,对返回集合迭代时,维护插入顺序&&
WeakHashMap-基于弱引用散列表的映射表,如果不保持映射表外的关键字的引用,则内存回收程序会回收它&
TreeMap-基于平衡树的映射表&&&
HashMap-散列表的通用映射表&&
LinkedHashMap-扩展HashMap,对返回集合迭代时,维护插入顺序&&
WeakHashMap-基于弱引用散列表的映射表,如果不保持映射表外的关键字的引用,则内存回收程序会回收它&
TreeMap-基于平衡树的映射表&&
Collections类,用于同步集合,还能改变集合只读方式的类&
Map&mp=new&HashMap();
mp=Collections.synchronizedMap(mp);&//生成线程安全的映射表&
mp=Collections.unmodifiableMap(mp);&//生成只读映射表&
Comparable 自然顺序的排序类&Comparator
面向树的集合排序类&&
容器分类学(Container taxonomy)&&
集合接口: Collection List SMap Iterator ListIterator。&
抽象类: AbstractCollection AbstractList AbstractSet AbstractMap AbstractSequentiaList。
老版本中的集合类型&
Vector类&&
Vector,就是向量。一种异构的混合体,可以动态增加容量。对它的操作简要如下&
比如我们有一个Vector: Vector myVec=new Vector(a_Array.length)&&
取得vector的长度:myVec.size();&
赋值:set(int position,Object obj) / setElementAt(Object obj, int position) –不支持动态增长&&
&& &&&add(Object obj )/ addElement(Object obj) 在Vector末尾加入对象&&
e.g.:myVec.add(new a_Array[0]);&
取出元素:get(int position) / getElement(int position)&
是Vector的子类。就是数据结构里讲滥了的堆栈(这个词可简称栈,不要混淆于heap-堆)。后进先出的存取方式。&&
Stack()构造空栈&
Empty()叛空&&
Search()检查堆栈是否有元素&
Peek()取得栈顶元素&
Pop()弹栈&
Push()入栈&&&
Enumeration接口&&&
Dictionary类&&
字典。关键字/数值方式存取数据,如果映射没有此关键字,取回null。&
Hashtable类&
是Dictionary结构的具体实现。&&&
面试题答案&&
Collection 和 Collections的区别。&&
Collections是个java.util下的类,它包含有各种有关集合操作的静态方法。&&
Collection是个java.util下的接口,它是各种集合结构的父接口
List, Set, Map是否继承自Collection接口?&List,Set是&Map不是&&
ArrayList和Vector的区别。&
一.同步性:Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同步的&
二.数据增长:当需要增长时,Vector默认增长为原来一培,而ArrayList却是原来的一半&&
HashMap和Hashtable的区别&&
一.历史原因:Hashtable是基于陈旧的Dictionary类的,HashMap是Java&1.2引进的Map接口的一个实现&
二.同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的&
三.值:只有HashMap可以让你将空值作为一个表的条目的key或value&
一、多线程
1、操作系统有两个容易混淆的概念,进程和线程。
进程:一个计算机程序的运行实例,包含了需要执行的指令;有自己的独立地址空间,包含程序内容和数据;不同进程的地址空间是互相隔离的;进程拥有各种资源和状态信息,包括打开的文件、子进程和信号处理。
线程:表示程序的执行流程,是CPU调度执行的基本单位;线程有自己的程序计数器、寄存器、堆栈和帧。同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源。
2、Java标准库提供了进程和线程相关的API,进程主要包括表示进程的java.lang.Process类和创建进程的java.lang.ProcessBuilder类;
表示线程的是java.lang.Thread类,在虚拟机启动之后,通常只有Java类的main方法这个普通线程运行,运行时可以创建和启动新的线程;还有一类守护线程(damon
thread),守护线程在后台运行,提供程序运行时所需的服务。当虚拟机中运行的所有线程都是守护线程时,虚拟机终止运行。
3、线程间的可见性:一个线程对进程中共享的数据的修改,是否对另一个线程可见
可见性问题:
a、CPU采用时间片轮转等不同算法来对线程进行调度
1.&public&class&IdGenerator{&&
2.&&&&private&int&value&=&0;&&
3.&&&&public&int&getNext(){&&
4.&&&&&&&return&value++;&&
对于IdGenerator的getNext()方法,在多线程下不能保证返回值是不重复的:各个线程之间相互竞争CPU时间来获取运行机会,CPU切换可能发生在执行间隙。
以上代码getNext()的指令序列:CPU切换可能发生在7条指令之间,多个getNext的指令交织在一起。
1.&aload_0&&
3.&getfield&#12&&
4.&dup_x1&&
5.&iconst_1&&
7.&putfield&#12&&
b、CPU缓存:
目前CPU一般采用层次结构的多级缓存的架构,有的CPU提供了L1、L2和L3三级缓存。当CPU需要读取主存中某个位置的数据时,会一次检查各级缓存中是否存在对应的数据。如果有,直接从缓存中读取,这比从主存中读取速度快很多。当CPU需要写入时,数据先被写入缓存中,之后再某个时间点写回主存。所以某些时间点上,缓存中的数据与主存中的数据可能是不一致。
c、指令顺序重排
出行性能考虑,编译器在编译时可能会对字节代码的指令顺序进行重新排列,以优化指令的执行顺序,在单线程中不会有问题,但在多线程可能产生与可见性相关的问题。
二、Java内存模型(Java Memory Model)
屏蔽了CPU缓存等细节,只关注主存中的共享变量;关注对象的实例域、静态域和数组元素;关注线程间的动作。
1、volatile关键词:用来对共享变量的访问进行同步,上一次写入操作的结果对下一次读取操作是肯定可见的。(在写入volatile变量值之后,CPU缓存中的内容会被写回内存;在读取volatile变量时,CPU缓存中的对应内容会被置为失效,重新从主存中进行读取),volatile不使用锁,性能优于synchronized关键词。
用来确保对一个变量的修改被正确地传播到其他线程中。
例子:A线程是Worker,一直跑循环,B线程调用setDone(true),A线程即停止任务
1.&public&class&Worker{&&
2.&&&&private&volatile&boolean&&&
3.&&&&public&void&setDone(boolean&done){&&
4.&&&&&&&this.done&=&&&
6.&&&&public&void&work(){&&
7.&&&&&&&while(!done){&&
8.&&&&&&&&&&//执行任务;&&
9.&&&&&&&}&&
10.&&&&}&&
例子:错误使用。因为没有锁的支持,volatile的修改不能依赖于当前值,当前值可能在其他线程中被修改。(Worker是直接赋新值与当前值无关)
1.&public&class&Counter&{&&
2.&&&&&public&volatile&static&int&count&=&0;&&
3.&&&&&public&static&void&inc()&{&&
4.&&&&&&&&&//这里延迟1毫秒,使得结果明显&&
5.&&&&&&&&&try&{&&
6.&&&&&&&&&&&&&Thread.sleep(1);&&
7.&&&&&&&&&}&catch&(InterruptedException&e)&{&&
8.&&&&&&&&&}&&
9.&&&&&&&&&count++;&&
10.&&&&&}&&
11.&&&&&public&static&void&main(String[]&args)&{&&
12.&&&&&&&&&//同时启动1000个线程,去进行i++计算,看看实际结果&&
13.&&&&&&&&&for&(int&i&=&0;&i&&&1000;&i++)&{&&
14.&&&&&&&&&&&&&new&Thread(new&Runnable()&{&&
15.&&&&&&&&&&&&&&&&&@Override&&
16.&&&&&&&&&&&&&&&&&public&void&run()&{&&
17.&&&&&&&&&&&&&&&&&&&&&Counter.inc();&&
18.&&&&&&&&&&&&&&&&&}&&
19.&&&&&&&&&&&&&}).start();&&
20.&&&&&&&&&}&&
21.&&&&&&&&&//这里每次运行的值都有可能不同,可能不为1000&&
22.&&&&&&&&&System.out.println(&运行结果:Counter.count=&&+&Counter.count);&&
23.&&&&&}&&
2、final关键词
final关键词声明的域的值只能被初始化一次,一般在构造方法中初始化。。(在多线程开发中,final域通常用来实现不可变对象)
当对象中的共享变量的值不可能发生变化时,在多线程中也就不需要同步机制来进行处理,故在多线程开发中应尽可能使用不可变对象。
另外,在代码执行时,final域的值可以被保存在寄存器中,而不用从主存中频繁重新读取。
3、java基本类型的原子操作
1)基本类型,引用类型的复制引用是原子操作;(即一条指令完成)
2)long与double的赋值,引用是可以分割的,非原子操作;
3)要在线程间共享long或double的字段时,必须在synchronized中操作,或是声明成volatile
三、Java提供的线程同步方式
1、synchronized关键字
方法或代码块的互斥性来完成实际上的一个原子操作。(方法或代码块在被一个线程调用时,其他线程处于等待状态)
所有的Java对象都有一个与synchronzied关联的监视器对象(monitor),允许线程在该监视器对象上进行加锁和解锁操作。
a、静态方法:Java类对应的Class类的对象所关联的监视器对象。
b、实例方法:当前对象实例所关联的监视器对象。
c、代码块:代码块声明中的对象所关联的监视器对象。
注:当锁被释放,对共享变量的修改会写入主存;当活得锁,CPU缓存中的内容被置为无效。编译器在处理synchronized方法或代码块,不会把其中包含的代码移动到synchronized方法或代码块之外,从而避免了由于代码重排而造成的问题。
例:以下方法getNext()和getNextV2()
都获得了当前实例所关联的监视器对象
1.&public&class&SynchronizedIdGenerator{&&
2.&&&&private&int&value&=&0;&&
3.&&&&public&synchronized&int&getNext(){&&
4.&&&&&&&return&value++;&&
6.&&&&public&int&getNextV2(){&&
7.&&&&&&&synchronized(this){&&
8.&&&&&&&&&&return&value++;&&
9.&&&&&&&}&&
10.&&&&}&&
2、Object类的wait、notify和notifyAll方法
生产者和消费者模式,判断缓冲区是否满来消费,缓冲区是否空来生产的逻辑。如果用while
和 volatile也可以做,不过本质上会让线程处于忙等待,占用CPU时间,对性能造成影响。
wait: 将当前线程放入,该对象的等待池中,线程A调用了B对象的wait()方法,线程A进入B对象的等待池,并且释放B的锁。(这里,线程A必须持有B的锁,所以调用的代码必须在synchronized修饰下,否则直接抛出java.lang.IllegalMonitorStateException异常)。
notify:将该对象中等待池中的线程,随机选取一个放入对象的锁池,当当前线程结束后释放掉锁, 锁池中的线程即可竞争对象的锁来获得执行机会。
notifyAll:将对象中等待池中的线程,全部放入锁池。
(notify锁唤醒的线程选择由虚拟机实现来决定,不能保证一个对象锁关联的等待集合中的线程按照所期望的顺序被唤醒,很可能一个线程被唤醒之后,发现他所要求的条件并没有满足,而重新进入等待池。因为当等待池中包含多个线程时,一般使用notifyAll方法,不过该方法会导致线程在没有必要的情况下被唤醒,之后又马上进入等待池,对性能有影响,不过能保证程序的正确性)
工作流程:
a、Consumer线程A
来 看产品,发现产品为空,调用产品对象的wait(),线程A进入产品对象的等待池并释放产品的锁。
b、Producer线程B获得产品的锁,执行产品的notifyAll(),Consumer线程A从产品的等待池进入锁池,Producer线程B生产产品,然后退出释放锁。
c、Consumer线程A获得产品锁,进入执行,发现有产品,消费产品,然后退出。
1.&public&synchronized&String&pop(){&&
2.&&&this.notifyAll();//&唤醒对象等待池中的所有线程,可能唤醒的就是&生产者(当生产者发现产品满,就会进入对象的等待池,这里代码省略,基本略同)&&
3.&&&&while(index&==&-1){//如果发现没产品,就释放锁,进入对象等待池&&
4.&&&&&&&this.wait();&&
5.&&&&}//当生产者生产完后,消费者从this.wait()方法再开始执行,第一次还会执行循环,万一产品还是为空,则再等待,所以这里必须用while循环,不能用if&&
6.&&&&String&good&=&buffer[index];&&
7.&&&&buffer[index]&=&null;&&
8.&&&&index--;&&
9.&&&&return&//&消费完产品,退出。&&
注:wait()方法有超时和不超时之分,超时的在经过一段时间,线程还在对象的等待池中,那么线程也会推出等待状态。
3、线程状态转换:
已经废弃的方法:stop、suspend、resume、destroy,这些方法在实现上时不安全的。
线程的状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING(有超时的等待)、TERMINATED。
a、方法sleep()进入的阻塞状态,不会释放对象的锁(即大家一起睡,谁也别想执行代码),所以不要让sleep方法处在synchronized方法或代码块中,否则造成其他等待获取锁的线程长时间处于等待。
b、方法join()则是主线程等待子线程完成,再往下执行。例如main方法新建两个线程A和B
1.&public&static&void&main(String[]&args)&throws&InterruptedException&{&&&&
2.&Thread&t1&=&new&Thread(new&ThreadTesterA());&&&&
3.&Thread&t2&=&new&Thread(new&ThreadTesterB());&&&&
4.&t1.start();&&&&
5.&t1.join();&//&等t1执行完再往下执行&&
6.&t2.start();&&&&
7.&t2.join();&//&在虚拟机执行中,这句可能被忽略&&
c、方法interrupt(),向被调用的对象线程发起中断请求。如线程A通过调用线程B的d的interrupt方法来发出中断请求,线程B来处理这个请求,当然也可以忽略,这不是必须的。Object类的wait()、Thread类的join()和sleep方法都会抛出受检异常java.lang.InterruptedException,通过interrupt方法中断该线程会导致线程离开等待状态。对于wait()调用来说,线程需要重新获取监视器对象上的锁之后才能抛出InterruptedException异常,并致以异常的处理逻辑。
可以通过Thread类的isInterrupted方法来判断是否有中断请求发生,通常可以利用这个方法来判断是否退出线程(类似上面的volatitle修饰符的例子);
Thread类还有个方法Interrupted(),该方法不但可以判断当前线程是否被中断,还会清楚线程内部的中断标记,如果返回true,即曾被请求中断,同时调用完后,清除中断标记。
如果一个线程在某个对象的等待池,那么notify和interrupt
都可以使该线程从等待池中被移除。如果同时发生,那么看实际发生顺序。如果是notify先,那照常唤醒,没影响。如果是interrupt先,并且虚拟机选择让该线程中断,那么即使nofity,也会忽略该线程,而唤醒等待池中的另一个线程。
e、yield(),尝试让出所占有的CPU资源,让其他线程获取运行机会,对操作系统上的调度器来说是一个信号,不一定立即切换线程。(在实际开发中,测试阶段频繁调用yeid方法使线程切换更频繁,从而让一些多线程相关的错误更容易暴露出来)。
四、非阻塞方式
线程之间同步机制的核心是监视对象上的锁,竞争锁来获得执行代码的机会。当一个对象获取对象的锁,然后其他尝试获取锁的对象会处于等待状态,这种锁机制的实现方式很大程度限制了多线程程序的吞吐量和性能(线程阻塞),且会带来死锁(线程A有a对象锁,等着获取b对象锁,线程B有b对象锁,等待获取a对象锁)和优先级倒置(优先级低的线程获得锁,优先级高的只能等待对方释放锁)等问题。
如果能不阻塞线程,又能保证多线程程序的正确性,就能有更好的性能。
在程序中,对共享变量的使用一般遵循一定的模式,即读取、修改和写入三步组成。之前碰到的问题是,这三步执行中可能线程执行切换,造成非原子操作。锁机制是把这三步变成一个原子操作。
目前CPU本身实现 将这三步 合起来 形成一个原子操作,无需线程锁机制干预,常见的指令是“比较和替换”(compare
and swap,CAS),这个指令会先比较某个内存地址的当前值是不是指定的旧指,如果是,就用新值替换,否则什么也不做,指令返回的结果是内存地址的当前值。通过CAS指令可以实现不依赖锁机制的非阻塞算法。一般做法是把CAS指令的调用放在一个无限循环中,不断尝试,知道CAS指令成功完成修改。
java.util.concurrent.atomic包中提供了CAS指令。(不是所有CPU都支持CAS,在某些平台,java.util.concurrent.atomic的实现仍然是锁机制)
atomic包中提供的Java类分成三类:
1、支持以原子操作来进行更新的数据类型的Java类(AtomicBoolean、AtomicInteger、AtomicReference),在内存模型相关的语义上,这四个类的对象类似于volatile变量。
类中的常用方法:
a、compareAndSet:接受两个参数,一个是期望的旧值,一个是替换的新值。
b、weakCompareAndSet:效果同compareAndSet(JSR中表示weak原子方式读取和有条件地写入变量但不创建任何
happen-before 排序,但在源代码中和compareAndSet完全一样,所以并没有按JSR实现)
c、get和set:分别用来直接获取和设置变量的值。
d、lazySet:与set类似,但允许编译器把lazySet方法的调用与后面的指令进行重排,因此对值得设置操作有可能被推迟。
1.&public&class&AtomicIdGenerator{&&
2.&&&&private&final&AtomicInter&counter&=&new&AtomicInteger(0);&&
3.&&&&public&int&getNext(){&&
4.&&&&&&&return&counter.getAndIncrement();&&
7.&//&getAndIncrement方法的内部实现方式,这也是CAS方法的一般模式,CAS方法不一定成功,所以包装在一个无限循环中,直到成功&&
8.&public&final&int&getAndIncrement(){&&
9.&&&&for(;;){&&
10.&&&&&&&int&current&=&get();&&
11.&&&&&&&int&next&=&current&+1;&&
12.&&&&&&&if(compareAndSet(current,next))&&
13.&&&&&&&&&&return&&&
14.&&&&}&&
2、提供对数组类型的变量进行处理的Java类,AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray类。(同上,只是放在类数组里,调用时也只是多了一个操作元素索引的参数)
3、通过反射的方式对任何对象中包含的volatitle变量使用CAS方法,AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater。他们提供了一种方式把CAS的功能扩展到了任何Java类中声明为volatitle的域上。(灵活,但语义较弱,因为对象的volatitle可能被非atomic的其他方式被修改)
1.&public&class&TreeNode{&&
2.&&&&private&volatile&TreeNode&&&
3.&//&静态工厂方法&&
4.&&&&private&static&final&AtomicReferenceFieldUpdater&TreeNode,&TreeNode&&parentUpdater&=&AtomicReferenceFieldUpdater.newUpdater(TreeNode.class,TreeNode.class,&parent&);&&
5.&public&boolean&compareAndSetParent(TreeNode&expect,&TreeNode&update){&&
6.&&&&&&&return&pareAndSet(this,&expect,&update);&&
注:java.util.concurrent.atomic包中的Java类属于比较底层的实现,一般作为java.util.concurrent包中很多非阻塞的数据结构的实现基础。
比较多的用AtomicBoolean、AtomicInteger、AtomicLong和AtomicReference。在实现线程安全的计数器时,AtomicInteger和AtomicLong类时最佳的选择。
五、高级同步机制(比synchronized更灵活的加锁机制)
synchronized和volatile,以及wait、notify等方法抽象层次低,在程序开发中使用比较繁琐,易出错。
而多线程之间的交互来说,存在某些固定的模式,如生产者-消费者和读者-写者模式,把这些模式抽象成高层API,使用起来会非常方便。
java.util.concurrent包为多线程提供了高层的API,满足日常开发中的常见需求。
1、Lock接口,表示一个锁方法:
a、lock(),获取所,如果无法获取所锁,会处于等待状态
b、unlock(),释放锁。(一般放在finally代码块中)
c、lockInterruptibly(),与lock()类似,但允许当前线程在等待获取锁的过程中被中断。(所以要处理InterruptedException)
d、tryLock(),以非阻塞方式获取锁,如果无法获取锁,则返回false。(tryLock()的另一个重载可以指定超时,如果指定超时,当无法获取锁,会等待而阻塞,同时线程可以被中断)
2、ReadWriteLock接口,表示两个锁,读取的共享锁和写入的排他锁。(适合常见的读者--写者场景)
ReadWriteLock接口的readLock和writeLock方法来获取对应的锁的Lock接口的实现。
在多数线程读取,少数线程写入的情况下,可以提高多线程的性能,提高使用该数据结构的吞吐量。
如果是相反的情况,较多的线程写入,则接口会降低性能。
3、ReentrantLock类和ReentrantReadWriteLock,分别为上面两个接口的实现类。
他们具有重入性:即允许一个线程多次获取同一个锁(他们会记住上次获取锁并且未释放的线程对象,和加锁的次数,getHoldCount())
同一个线程每次获取锁,加锁数+1,每次释放锁,加锁数-1,到0,则该锁被释放,可以被其他线程获取。
1.&public&class&LockIdGenrator{&&
2.&//new&ReentrantLock(true)是重载,使用更加公平的加锁机制,在锁被释放后,会优先给等待时间最长的线程,避免一些线程长期无法获得锁&&
3.&&&&private&int&ReentrantLock&lock&=&ReentrantLock();&&
4.&&&&privafte&int&value&=&0;&&
5.&&&&public&int&getNext(){&&
6.&&&&&&&lock.lock();&&&&&&//进来就加锁,没有锁会等待&&
7.&&&&&&&try{&&
8.&&&&&&&&&&return&value++;//实际操作&&
9.&&&&&&&}finally{&&
10.&&&&&&&&&&lock.unlock();//释放锁&&
11.&&&&&&&}&&
12.&&&&}&&
注:重入性减少了锁在各个线程之间的等待,例如便利一个HashMap,每次next()之前加锁,之后释放,可以保证一个线程一口气完成便利,而不会每次next()之后释放锁,然后和其他线程竞争,降低了加锁的代价,
提供了程序整体的吞吐量。(即,让一个线程一口气完成任务,再把锁传递给其他线程)。
4、Condition接口,Lock接口代替了synchronized,Condition接口替代了object的wait、nofity。
a、await(),使当前线程进入等待状态,知道被唤醒或中断。重载形式可以指定超时时间。
b、awaitNanos(),以纳秒为单位等待。
c、awaitUntil(),指定超时发生的时间点,而不是经过的时间,参数为java.util.Date。
d、awaitUninterruptibly(),前面几种会响应其他线程发出的中断请求,他会无视,直到被唤醒。
注:与Object类的wait()相同,await()会释放其所持有的锁。
e、signal()和signalAll, 相当于
notify和notifyAll
1.&Lock&lock&=&new&ReentrantLock();&&
2.&Condition&condition&=&lock.newCondition();&&
3.&lock.lock();&&
5.&&&&while(/*逻辑条件不满足*/){&&
6.&&&&&&&condition.await();&&&&&
8.&}finally{&&
9.&&&&lock.unlock();&&
六、底层同步器
多线程程序中,线程之间存在多种不同的同步方式。除了Java标准库提供的同步方式之外,程序中特有的同步方式需要由开发人员自己来实现。
常见的一种需求是 对有限个共享资源的访问,比如多台个人电脑,2台打印机,当多个线程在等待同一个资源时,从公平角度出发,会用FIFO队列。
& 如果程序中的同步方式可以抽象成对有限个资源的访问,那么可以使用java.util.concurrent.locks包中的AbstractQueuedSynchronizer类和AbstractQueuedLongSynchronizer类作为实现的基础,前者用int类型的变量来维护内部状态,而后者用long类型。(可以将这个变量理解为共享资源个数)
通过getState、setState、和compareAndSetState3个方法更新内部变量的值。
AbstractQueuedSynchronizer类是abstract的,需要覆盖其中包含的部分方法,通常做法是把其作为一个Java类的内部类,外部类提供具体的同步方式,内部类则作为实现的基础。有两种模式,排他模式和共享模式,分别对应方法
tryAcquire()、tryRelease
和 tryAcquireShared、tryReleaseShared,在这些方法中,使用getState、setState、compareAndSetState3个方法来修改内部变量的值,以此来反应资源的状态。
1.&public&class&SimpleResourceManager{&&
2.&&&&private&final&InnerSynchronizer&&&
3.&&&&private&static&class&InnerSynchronizer&extends&AbstractQueuedSynchronizer{&&
4.&&&&&&&InnerSynchronizer(int&numOfResources){&&
5.&&&&&&&&&&setState(numOfResources);&&
6.&&&&&&&}&&
7.&&&&&&&protected&int&tryAcquireShared(int&acquires){&&
8.&&&&&&&&&&for(;;){&&
9.&&&&&&&&&&&&&int&available&=&getState();&&
10.&&&&&&&&&&&&&int&remain&=&available&-&&&
11.&&&&&&&&&&&&&if(remain&&0&||&comapreAndSetState(available,&remain){&&
12.&&&&&&&&&&&&&&&&return&&&
13.&&&&&&&&&&&&&}&&
14.&&&&&&&&&&}&&
15.&&&&&&&}&&
16.&&&&&&&protected&boolean&try&ReleaseShared(int&releases){&&
17.&&&&&&&&&&for(;;){&&
18.&&&&&&&&&&&&&int&available&=&getState();&&&
19.&&&&&&&&&&&&&int&next&=&available&+&&&&
20.&&&&&&&&&&&&&if(compareAndSetState(available,next){&&
21.&&&&&&&&&&&&&&&&return&true;&&
22.&&&&&&&&&&&&&}&&
23.&&&&&&&&&&}&&
24.&&&&&&&}&&
25.&&&&}&&
26.&&&&public&SimpleResourceManager(int&numOfResources){&&
27.&&&&&&&synchronizer&=&new&InnerSynchronizer(numOfResources);&&
28.&&&&}&&
29.&&&&public&void&acquire()&throws&InterruptedException{&&
30.&&&&&&&synchronizer.acquireSharedInterruptibly(1);&&
31.&&&&}&&&&&&&&
32.&&&&pubic&void&release(){&&&&&&
33.&&&&&&&synchronizer.releaseShared(1);&&
34.&&&&&}&&
七、高级同步对象(提高开发效率)
atomic和locks包提供的Java类可以满足基本的互斥和同步访问的需求,但这些Java类的抽象层次较低,使用比较复杂。
更简单的做法是使用java.util.concurrent包中的高级同步对象。
1、信号量。
信号量一般用来数量有限的资源,每类资源有一个对象的信号量,信号量的值表示资源的可用数量。
在使用资源时,需要从该信号量上获取许可,成功获取许可,资源的可用数-1;完成对资源的使用,释放许可,资源可用数+1; 当资源数为0时,需要获取资源的线程以阻塞的方式来等待资源,或过段时间之后再来检查资源是否可用。(上面的SimpleResourceManager类实际上时信号量的一个简单实现)
java.util.concurrent.Semaphore类,在创建Semaphore类的对象时指定资源的可用数
a、acquire(),以阻塞方式获取许可
b、tryAcquire(),以非阻塞方式获取许可
c、release(),释放许可。
d、accquireUninterruptibly(),accquire()方法获取许可以的过程可以被中断,如果不希望被中断,使用此方法。
1.&public&class&PrinterManager{&&
2.&&&&private&final&Semphore&&&
3.&&&&private&final&List&Printer&&printers&=&new&ArrayList&&():&&
4.&&&&public&PrinterManager(Collection&?&extends&Printer&&printers){&&
5.&&&&&&&this.printers.addAll(printers);&&
6.&&&&&&&//这里重载方法,第二个参数为true,以公平竞争模式,防止线程饥饿&&
7.&&&&&&&this.semaphore&=&new&Semaphore(this.printers.size(),true);&&
9.&&&&public&Printer&acquirePrinter()&throws&InterruptedException{&&
10.&&&&&&&semaphore.acquire();&&
11.&&&&&&&return&getAvailablePrinter();&&
12.&&&&}&&
13.&&&&public&void&releasePrinter(Printer&printer){&&
14.&&&&&&&putBackPrinter(pinter);&&
15.&&&&&&&semaphore.release();&&
16.&&&&}&&
17.&&&&private&synchronized&Printer&getAvailablePrinter(){&&
18.&&&&&&&printer&result&=&printers.get(0);&&
19.&&&&&&&printers.remove(0);&&
20.&&&&&&&return&&&
21.&&&&}&&
22.&&&&private&synchronized&void&putBackPrinter(Printer&printer){&&
23.&&&&&&&printers.add(printer);&&
24.&&&&}&&
2、倒数闸门
多线程协作时,一个线程等待另外的线程完成任务才能继续进行。
java.util.concurrent.CountDownLatch类,创建该类时,指定等待完成的任务数;当一个任务完成,调用countDonw(),任务数-1。等待任务完成的线程通过await(),进入阻塞状态,直到任务数量为0。CountDownLatch类为一次性,一旦任务数为0,再调用await()不再阻塞当前线程,直接返回。
1.&public&class&PageSizeSorter{&&
2.&&&&//&并发性能远远优于HashTable的&Map实现,hashTable做任何操作都需要获得锁,同一时间只有有个线程能使用,而ConcurrentHashMap是分段加锁,不同线程访问不同的数据段,完全不受影响,忘记HashTable吧。&&
3.&&&&private&static&final&ConcurrentHashMap&String&,&Interger&&sizeMap&=&new&ConcurrentHashMap&&();&&
4.&&&&private&static&class&GetSizeWorker&implements&Runnable{&&
5.&&&&&&&private&final&String&urlS&&
6.&&&&&&&public&GetSizeWorker(String&urlString&,&CountDownLatch&signal){&&
7.&&&&&&&&&&this.urlString&=&urlS&&
8.&&&&&&&&&&this.signal&=&&&
9.&&&&&&&}&&
10.&&&&&&&public&void&run(){&&
11.&&&&&&&&&&try{&&
12.&&&&&&&&&&&&&InputStream&is&=&new&URL(urlString).openStream();&&
13.&&&&&&&&&&&&&int&size&=&IOUtils.toByteArray(is).&&
14.&&&&&&&&&&&&&sizeMap.put(urlString,&size);&&
15.&&&&&&&&&&}catch(IOException&e){&&
16.&&&&&&&&&&&&&sizeMap.put(urlString,&-1);&&
17.&&&&&&&&&&}finally{&&
18.&&&&&&&&&&&&&signal.countDown()://完成一个任务&,&任务数-1&&
19.&&&&&&&&&&}&&
20.&&&&&&&}&&
21.&&&&}&&
22.&&&&private&void&sort(){&&
23.&&&&&&&List&Entry&String,&Integer&&list&=&new&ArrayList&sizeMap.entrySet());&&
24.&&&&&&&Collections.slort(list,&new&Comparator&Entry&String,Integer&&(){&&
25.&&&&&&&&&&public&int&compare&(Entry&String,&Integer&&o1,&Entry&Sting&,&Integer&&o2){&&
26.&&&&&&&&&&&&&return&pare(o2.getValue(),o1.getValue());&&
27.&&&&&&&};&&
28.&&&&&&&System.out.println(Arrays.deepToString(list.toArray()));&&
29.&&&&}&&
30.&&&&public&void&sortPageSize(Collection&String&&urls)&throws&InterruptedException{&&
31.&&&&&&&CountDownLatch&sortSignal&=&new&CountDownLatch(urls.size());&&
32.&&&&&&&for(String&url:&urls){&&
33.&&&&&&&&&&new&Thread(new&GetSizeWorker(url,&sortSignal)).start();&&
34.&&&&&&&}&&
35.&&&&&&&sortSignal.await()://主线程在这里等待,任务数归0,则继续执行&&
36.&&&&&&&sort();&&
37.&&&&}&&
3、循环屏障
循环屏障在作用上类似倒数闸门,不过他不像倒数闸门是一次性的,可以循环使用。另外,线程之间是互相平等的,彼此都需要等待对方完成,当一个线程完成自己的任务之后,等待其他线程完成。当所有线程都完成任务之后,所有线程才可以继续运行。
当线程之间需要再次进行互相等待时,可以复用同一个循环屏障。
类java.uti.concurrent.CyclicBarrier用来表示循环屏障,创建时指定使用该对象的线程数目,还可以指定一个Runnable接口的对象作为每次循环后执行的动作。(当最后一个线程完成任务之后,所有线程继续执行之前,被执行。如果线程之间需要更新一些共享的内部状态,可以利用这个Runnalbe接口的对象来处理)。
每个线程任务完成之后,通过调用await方法进行等待,当所有线程都调用await方法之后,处于等待状态的线程都可以继续执行。在所有线程中,只要有一个在等待中被中断,超时或是其他错误,整个循环屏障会失败,所有等待中的其他线程抛出java.uti.concurrent.BrokenBarrierException。
例:每个线程负责找一个数字区间的质数,当所有线程完成后,如果质数数目不够,继续扩大范围查找
1.&public&class&PrimeNumber{&&
2.&&&&private&static&final&int&TOTAL_COUTN&=&5000;&&
3.&&&&private&static&final&int&RANGE_LENGTH=&200;&&
4.&&&&private&static&final&int&WORKER_NUMBER&=&5;&&
5.&&&&private&static&volatitle&boolean&done&=&false;&&
6.&&&&private&static&int&rangeCount&=&0;&&
7.&&&&private&static&final&List&Long&&results&=&new&ArrayList&Long&():&&
8.&&&&private&static&final&CyclicBarrier&barrier&=&new&CyclicBarrier(WORKER_NUMBER,&new&Runnable(){&&
9.&&&&&&&public&void&run(){&&
10.&&&&&&&&&&if(results.size()&&=&TOTAL_COUNT){&&
11.&&&&&&&&&&&&&done&=&true;&&
12.&&&&&&&&&&}&&
13.&&&&&&}&&
14.&&&&});&&
15.&&&&private&static&class&PrimeFinder&implements&Runnable{&&
16.&&&&&&&public&void&run(){&&
17.&&&&&&&&&&while(!done){//&整个过程在一个&while循环下,await()等待,下次循环开始,会再次判断&执行条件&&
18.&&&&&&&&&&&&&int&range&=&getNextRange();&&
19.&&&&&&&&&&&&&long&start&=&rang&*&RANGE_LENGTH;&&
20.&&&&&&&&&&&&&long&end&=&(range&+&1)&*&RANGE_LENGTH;&&
21.&&&&&&&&&&&&&for(long&i&=&&i&i++){&&
22.&&&&&&&&&&&&&&&&if(isPrime(i)){&&
23.&&&&&&&&&&&&&&&&&&&updateResult(i);&&
24.&&&&&&&&&&&&&&&&}&&
25.&&&&&&&&&&&&&}&&
26.&&&&&&&&&&&&&try{&&
27.&&&&&&&&&&&&&&&&barrier.await();&&
28.&&&&&&&&&&&&&}catch&(InterruptedException&|&BokenBarrierException&e){&&
29.&&&&&&&&&&&&&&&&done&=&&true;&&
30.&&&&&&&&&&&&&}&&
31.&&&&&&&&&&}&&
32.&&&&&&&}&&
33.&&&&}&&
34.&&&&private&synchronized&static&void&updateResult(long&value){&&
35.&&&&&&&results.add(value);&&
36.&&&&}&&
37.&&&&private&synchronized&static&int&getNextRange(){&&
38.&&&&&&&return&rangeCount++;&&
39.&&&&}&&
40.&&&&private&static&boolean&isPrime(long&number){&&
41.&&&&&&&//找质数的代码&&
42.&&&&}&&
43.&&&&public&void&calculate(){&&
44.&&&&&&&for(int&i=0;i&WORKER_NUMBER;i++){&&
45.&&&&&&&&&&new&Thread(new&PrimeFinder()).start();&&
46.&&&&&&&}&&
47.&&&&&&&while(!done){&&
49.&&&&&&&}&&
50.&&&&&&&//计算完成&&
51.&&&&}&&
4、对象交换器
适合于两个线程需要进行数据交换的场景。(一个线程完成后,把结果交给另一个线程继续处理)
java.util.concurrent.Exchanger类,提供了这种对象交换能力,两个线程共享一个Exchanger类的对象,一个线程完成对数据的处理之后,调用Exchanger类的exchange()方法把处理之后的数据作为参数发送给另外一个线程。而exchange方法的返回结果是另外一个线程锁提供的相同类型的对象。如果另外一个线程未完成对数据的处理,那么exchange()会使当前线程进入等待状态,直到另外一个线程也调用了exchange方法来进行数据交换。
1.&public&class&SendAndReceiver{&&
2.&&&&private&final&Exchanger&StringBuilder&&exchanger&=&new&Exchanger&StringBuilder&();&&
3.&&&&private&class&Sender&implements&Runnable{&&
4.&&&&&&&public&void&run(){&&
5.&&&&&&&&&&try{&&
6.&&&&&&&&&&&&&StringBuilder&content&=&new&StringBuilder(&Hello&);&&
7.&&&&&&&&&&&&&content&=&exchanger.exchange(content);&&
8.&&&&&&&&&&}catch(InterruptedException&e){&&
9.&&&&&&&&&&&&&Thread.currentThread().interrupt();&&
10.&&&&&&&&&&}&&
11.&&&&&&&}&&
12.&&&&}&&
13.&&&&private&class&Receiver&implements&Runnable{&&
14.&&&&&&&public&void&run(){&&
15.&&&&&&&&&&try{&&
16.&&&&&&&&&&&&&StringBuilder&content&=&new&StringBuilder(&World&);&&
17.&&&&&&&&&&&&&content&=&exchanger.exchange(content);&&
18.&&&&&&&&&&}catch(InterruptedException&e){&&
19.&&&&&&&&&&&&&Thread.currentThread().interrupt();&&
20.&&&&&&&&&&}&&
21.&&&&&&&}&&
22.&&&&}&&
23.&&&&public&void&exchange(){&&
24.&&&&&&&new&Thread(new&Sender()).start();&&
25.&&&&&&&new&Thread(new&Receiver()).start();&&
26.&&&&}&&
八、数据结构(多线程程序使用的高性能数据结构)
java.util.concurrent包中提供了一些适合多线程程序使用的高性能数据结构,包括队列和集合类对象等。
a、BlockingQueue接口:线程安全的阻塞式队列;当队列已满时,想队列添加会阻塞;当队列空时,取数据会阻塞。(非常适合消费者-生产者模式)
阻塞方式:put()、take()。
非阻塞方式:offer()、poll()。
实现类:基于数组的固定元素个数的ArrayBolockingQueue和基于链表结构的不固定元素个数的LinkedBlockQueue类。
b、BlockingDeque接口: 与BlockingQueue相似,但可以对头尾进行添加和删除操作的双向队列;方法分为两类,分别在队首和对尾进行操作。
实现类:标准库值提供了一个基于链表的实现,LinkedBlockgingDeque。
在多线程程序中,如果共享变量时集合类的对象,则不适合直接使用java.util包中的集合类。这些类要么不是线程安全,要么在多线程下性能比较差。
应该使用java.util.concurrent包中的集合类。
a、ConcurrentMap接口: 继承自java.util.Map接口
putIfAbsent():只有在散列表不包含给定键时,才会把给定的值放入。
remove():删除条目。
replace(key,value):把value
替换到给定的key上。
replace(key, oldvalue, newvalue):CAS的实现。
实现类:ConcurrentHashMap:
创建时,如果可以预估可能包含的条目个数,可以优化性能。(因为动态调整所能包含的数目操作比较耗时,这个HashMap也一样,只是多线程下更耗时)。
创建时,预估进行更新操作的线程数,这样实现中会根据这个数把内部空间划分为对应数量的部分。(默认是16,如果只有一个线程进行写操作,其他都是读取,那么把值设为1
可以提高性能)。
注:当从集合中创建出迭代器遍历Map元素时,不一定能看到正在添加的数据,只能和集合保证弱一致性。(当然使用迭代器不会因为查看正在改变的Map,而抛出java.util.ConcurrentModifycationException)
b、CopyOnWriteArrayList接口:继承自java.util.List接口。
顾名思义,在CopyOnWriteArrayList的实现类,所有对列表的更新操作都会新创建一个}

我要回帖

更多关于 sream159 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信