Objcet类知识整合

2018-04-22  本文已影响86人  遇见你的故事

Object类位于java.lang包中,java.lang包有最基础的和核心的类,在编译时会自动导入;

Object类是所有java类的祖先,每个类都使用Object作为超类,所有对象(数组)都实现这个类的方法.可以使用类型为Objcet的变量指向任意类型的对象

1.clone方法

保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里讲参数改变,这是就需要在类中复写clone方法。

直接调用Object的clone()方法来copy一个副本,但是发现myEclipse的提示中并没有该方法,以为在jdk1.7中取消了该方法,然后我直接敲上clone()后:

[java] view plain copy

public class TestObject {  

public static void main(String[] args) {  

Student s =new Student(1, "小时");  

s.clone();//报错  

    }  

}  

编译报错:The method clone() from the type Object is not visible

后来看了看源码,发现原来是这样:

Object源码中对于clone()方法的描述:

[java] view plain copy

protected native Object clone() throws CloneNotSupportedException;  

native修饰词解析:native的方法就是一个java调用非java代码的接口,是一个其他语言实现的方法。

问题1:Object类是所有 类的父类,那么为什么子类不能访问父类的protected修饰的方法呢?

以前对protected的理解是错误的,protected的方法在自己的包中与public相同

在不同包,指的是通过自身实例(自身的引用)访问,而不能通过父类实例(引用)访问

所以上端代码中调用Student类的clone()方法会包编译错误,下面的代码(通过自身引用调)就OK的

[java] view plain copy

public class TestObject {  

public static void main(String[] args) {  

TestBoject t =new TestObject();  

t.clone();//ok  

    }  

}  

问题2:要想实现类似文章开头的代码功能该怎么办?

在Object的源码中注释到:

首先,如果这个类没有实现Cloneable这个接口,将会抛出CloneNotSupportedException,但是所有的数组都被看成实现了这个接口。此方法执行的是该对象的“浅表复制”,而不“深层复制”操作。

Object 类本身不实现接口 Cloneable,所以在类为 Object 的对象上调用 clone 方法将会导致在运行时抛出异常。

在Cloneable的源码中注释到:

此类实现了 Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制。 如果在没有实现 Cloneable 接口的实例上调用 Object 的 clone 方法,则会导致抛出 CloneNotSupportedException异常。 

按照惯例,实现此接口的类应该使用公共方法重写 Object.clone(它是受保护的)。请参阅 Object.clone(),以获得有关重写此方法的详细信息。 

注意,此接口不包含 clone 方法。因此,因为某个对象实现了此接口就克隆它是不可能的。即使 clone 方法是反射性调用的,也无法保证它将获得成功

解读:Cloneable接口没有任何方法,仅是个标志接口(tagging interface),若要具有克隆能力,实现Cloneable接口的类必须重写从Object继承来的clone方法,并调用Object的clone方法(见下面Object#clone的定义),重写后的方法应为public 的。For example(标准写法):

示例代码:

[java] view plain copy

public class TestObject {  

public static void main(String[] args) {  

Student s =new Student(1, "小时");  

        System.out.println(s.clone().getClass());  

        System.out.println(s.clone().equals(s));  

    }  

}  

class Student implements Cloneable{  

public int id;  

public String name;  

Student(int id, String name){  

this.id = id;  

this.name = name;  

    }  

public Object clone(){  

Student s =null;  

try{  

s = (Student)super.clone();  

}catch(CloneNotSupportedException e){  

                e.printStackTrace();  

            }  

return s;   

    }  

}  

2.getClass方法

final方法,获得运行时类型。

一、

getClass方法:

类型:public final Class getClass()

功能:返回该对象的运行时类的java.lang.Class对象(API上的解释)

有方法类型可以知道,该方法只能由类的实例变量调用

例子:

[java]view plaincopy

JButton b1 = new JButton("button1");  

System.out.println(b1.getClass());  

输出:

class javax.swing.JButton

class属性

当你要获得一个类的Class对象时(作函数参数的时候),你不能调用getClass方法,那你只能用类名.class来达到效果

例子:

[java]view plaincopy

System.out.println(JButton.class);  

输出:

class javax.swing.JButton

getName方法:

类型:public String getName()

功能:以String形式返回次Class对象所表示的实体名称

例子:

[java]view plaincopy

JButton b1 = new JButton("button1");  

System.out.println(b1.getName());  

输出:

javax.swing.JButton

可以发现用class属性和getClass返回的输出是一样的,用getName返回的比前面两种少了class和一个空格。

 .eclipse工具 可以按"."然后马上提示很多方法 供你选择

那他如何知道"."了以后有哪些方法?

他用的语法就是getClass().getMethods();

二、

.class其实是在java运行时就加载进去的

getClass()是运行程序时动态加载的

下面以例子说明:

首先建一个基类Baseclass  

packageclassyongfa;

publicclassBaseclass {

privateString height;

publicString getHeight(){  

returnheight;

}  

publicvoidsetHeight(String height){  

this.height=height;

}  

下面是继承Baseclass类Extendclass  

packageclassyongfa;

publicclassExtendclassextendsBaseclass {

privateString width;

publicString getWidth(){  

returnwidth;

}  

publicvoidsetWidth(String width){  

this.width=width;

}  

publicstaticvoidmain(String[] arg0){  

Baseclass baseclass1=newExtendclass();

Baseclass baseclass2=newBaseclass();

System.out.println(baseclass1.getClass().getSimpleName());//实际运行的是继承类Extendclass

System.out.println(baseclass2.getClass().getSimpleName());//实际运行的是Baseclass

System.out.println(Baseclass.class.getSimpleName());//加载时类名

System.out.println(Extendclass.class.getSimpleName());//加载时类名

}  

结果是  

Extendclass  

Baseclass  

Baseclass  

Extendclass

三、四种获取Class对象的方法 Java反射机制

下面以一个具体的实例来说明。此实例来自《精通Hibernate 3.0 Java数据库持久层开发实践》一书。

先在com.hqh.reflect下建一个文件UseInfojava

package com.hqh.reflect;

public classUseInfo{

private Integer id;

private String userName;

private String password;

public Integer getId() {

return id;

}

public void setId(Integer id) {

this.id = id;

}

public String getUserName() {

return userName;

}

public void setUserName(String userName) {

this.userName = userName;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

}

package com.hqh.reflect;

public classGetClassTest{

public static void main(String[] args) {

GetClassTest test = new GetClassTest();

if(test.ClassCheck())

System.out.println("OK");

}

public boolean ClassCheck() {

try {

System.out.println("通过类本身获得对象");

Class userClass =this.getClass();

System.out.println(userClass.getName());

System.out.println("===========");

System.out.println("通过子类的实例获得父类对象");

UseInfo useInfo = new UseInfo();

userClass = useInfo.getClass();

System.out.println(userClass.getName());

Class subUserClass = userClass.getSuperclass();

System.out.println(subUserClass.getName());

System.out.println("===========");

System.out.println("通过类名.class获取对象");

Class forClass =com.hqh.reflect.UseInfo.class;

System.out.println(forClass.getName());

System.out.println("===========");

System.out.println("通过类名的字符串获取对象");

Class forName =Class.forName("com.hqh.reflect.UseInfo");

System.out.println(forName.getName());

System.out.println("=============");

} catch (Exception e) {

e.printStackTrace();

return false;

}

return true;

}

}

结果:

通过类本身获得对象

com.hqh.reflect.GetClassTest

===========

通过子类的实例获得父类对象

com.hqh.reflect.UseInfo

java.lang.Object

===========

通过类名.class获取对象

com.hqh.reflect.UseInfo

===========

通过类名的字符串获取对象

com.hqh.reflect.UseInfo

=============

3.toString方法

返回该对象的字符串表示。通常,toString方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂的信息表达式。建议所有子类都重写此方法。

Object类的toString方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:

getClass().getName() + '@' + Integer.toHexString(hashCode())

返回:

该对象的字符串表示形式。

因为它是Object里面已经有了的方法,而所有类都是继承Object,所以“所有对象都有这个方法”。

它通常只是为了方便输出,比如System.out.println(xx),括号里面的“xx”如果不是String类型的话,就自动调用xx的toString()方法

总而言之,它只是sun公司开发java的时候为了方便所有类的字符串操作而特意加入的一个方法

写这个方法的用途就是为了方便操作,所以在文件操作里面可用可不用

例子1:

publicclassOrc{

public static class A {

public String toString() {

return"this is A";

}

}

public static void main(String[] args) {

        A obj = newA( );

        System.out.println(obj);

}

}

如果某个方法里面有如下句子: 

A obj=new A();

System.out.println(obj);

会得到输出:this is A

例子2:

public class Orc{

public static class A {

public String getString() {

return"this is A";             

}     

}

public static void main(String[] args)  {     

        A obj =newA();             

         System. out. println(obj);             

         System. out. println(obj.getString());     

}

}

会得到输出:xxxx@xxxxxxx的类名加地址形式

System.out.println(obj.getString());

会得到输出:this is A

看出区别了吗,toString的好处是在碰到“println”之类的输出方法时会自动调用,不用显式打出来。

public class Zhang {

public static void main(String[] args) { 

     StringBuffer MyStrBuff1 =newStringBuffer();        

     MyStrBuff1.append("Hello,Guys!");

     System.out.println(MyStrBuff1.toString());

     MyStrBuff1.insert(6, 30);

    System.out.println(MyStrBuff1.toString());  

}

}

值得注意的是, 若希望将StringBuffer在屏幕上显示出来, 则必须首先调用toString方法把它变成字符串常量,因为PrintStream的方法println()不接受StringBuffer类型的参数.

public class Zhang{

public static void main(String[] args){

    String MyStr =new StringBuffer(); 

    MyStr =new  StringBuffer().append(MyStr).append(" Guys!").toString();

    System.out.println(MyStr); 

 }

}

toString()方法在此的作用是将StringBuffer类型转换为String类型.

public class Zhang{

public static void main(String[] args){

     String MyStr =new StringBuffer().append("hello").toString();

     MyStr =new StringBuffer().append(MyStr).append(" Guys!").toString();

     System.out.println(MyStr);

}}

1.toString()方法

Object类具有一个toString()方法,你创建的每个类都会继承该方法。它返回对象的一个String表示,并且对于调试非常有帮助。然而对于默认的toString()方法往往不能满足需求,需要覆盖这个方法。

toString()方法将对象转换为字符串。看以下代码:

package sample;

class Villain {

    private String name;

    protected void set(String nm) {

       name = nm;

    }

    public Villain(String name) {

       this.name = name;

    }

    public String toString() {

       return "I'm a Villain and my name is " + name;

    }

}

public class Orc extends Villain {

    private int orcNumber;

    public Orc(String name, int orcNumber) {

       super(name);

       this.orcNumber = orcNumber;

    }

    public void change(String name, int orcNumber) {

       set(name);

       this.orcNumber = orcNumber;

    }

    public String toString() {

       return "Orc" + orcNumber + ":" + super.toString();

    }

    public static void main(String[] args) {

       Orc orc = new Orc("Limburger", 12);

       System.out.println(orc);

       orc.change("Bob", 19);

       System.out.println(orc);

    }

}

结果:

sample.Orc@11b86e7

sample.Orc@11b86e7

如果去掉注释,即加入2个toString()方法后,得到

结果:

Orc12:I'm a Villain and my name is Limburger

Orc19:I'm a Villain and my name is Bob

2.在容器类中使用toString()

编写一个工具类,用于在控制台输出Iterator。

import java.util.Iterator;

public class Printer {

    static void printAll(Iterator e){

       while(e.hasNext()){

           System.out.println(e.next());

       }

    }

}

在Hamster类中重写父类的toString()方法。

public class Hamster {

    private int hamsterNumber;

    public Hamster(int hamsterNumber){

       this.hamsterNumber=hamsterNumber;

    }

    public String toString(){

       return "This is Hamster #"+hamsterNumber;

    }

}

在HamsterMaze类中使用容器类加载Hamster类对象并输出结果。

import java.util.ArrayList;

import java.util.List;

public class HamsterMaze {

    @SuppressWarnings("unchecked")

    public static void main(String[] args){

       List list=new ArrayList();

       for(int i=0;i<3;i++)

           list.add(new Hamster(i));

       Printer.printAll(list.iterator());

    }

}

结果:

This is Hamster #0

This is Hamster #1

This is Hamster #2

3.一个实现toString()的通用的Bean

在作一个项目时发现,许多bean需要实现toString()方法,就实现一个通用的bean,然后通过其他继承即可。

import java.lang.reflect.Field;

public class BaseBean {

    public String toString() {

       StringBuffer sb = new StringBuffer();

       try {

           Class t = this.getClass();

           Field[] fields = t.getDeclaredFields();

           for (int i = 0; i < fields.length; i++) {

              Field field = fields[i];

              field.setAccessible(true);

              sb.append("{");

              sb.append(field.getName());

              sb.append(":");

              if (field.getType() == Integer.class) {

                  sb.append(field.getInt(this));

              } else if (field.getType() == Long.class) {

                  sb.append(field.getLong(this));

              } else if (field.getType() == Boolean.class) {

                  sb.append(field.getBoolean(this));

              } else if (field.getType() == char.class) {

                  sb.append(field.getChar(this));

              } else if (field.getType() == Double.class) {

                  sb.append(field.getDouble(this));

              } else if (field.getType() == Float.class) {

                  sb.append(field.getFloat(this));

              } else

                  sb.append(field.get(this));

              sb.append("}");

           }

       } catch (Exception e) {

           e.printStackTrace();

       }

       return sb.toString();

    }

}

测试类

public class TestBean extends BaseBean {

    private int id;

    public int getId() {

       return id;

    }

    public void setId(int id) {

       this.id = id;

    }

    public static void main(String[] args) {

       TestBean testBean = new TestBean();

       testBean.setId(9);

       System.out.println(testBean.toString());

    }

}

结果

{id:9}

关于String ,StringBuffer的性能

博客分类: java语言

通过使用一些辅助性工具来找到程序中的瓶颈,然后就可以对瓶颈部分的代码进行优化。一般有两种方案:即优化代码或更改设计方法。我们一般会选择后者,因为不去调用以下代码要比调用一些优化的代码更能提高程序的性能。而一个设计良好的程序能够精简代码,从而提高性能。

下面将提供一些在JAVA程序的设计和编码中,为了能够提高JAVA程序的性能,而经常采用的一些方法和技巧。

1.对象的生成和大小的调整。

JAVA程序设计中一个普遍的问题就是没有好好的利用JAVA语言本身提供的函数,从而常常会生成大量的对象(或实例)。由于系统不仅要花时间生成对象,以后可能还需花时间对这些对象进行垃圾回收和处理。因此,生成过多的对象将会给程序的性能带来很大的影响。

例1:关于String ,StringBuffer,+和append

JAVA语言提供了对于String类型变量的操作。但如果使用不当,会给程序的性能带来影响。如下面的语句:

String name=new String("HuangWeiFeng");

System.out.println(name+"is my name");

看似已经很精简了,其实并非如此。为了生成二进制的代码,要进行如下的步骤和操作:

(1) 生成新的字符串 new String(STR_1);

(2) 复制该字符串;

(3) 加载字符串常量"HuangWeiFeng"(STR_2);

(4) 调用字符串的构架器(Constructor);

(5) 保存该字符串到数组中(从位置0开始);

(6) 从java.io.PrintStream类中得到静态的out变量;

(7) 生成新的字符串缓冲变量new StringBuffer(STR_BUF_1);

(8) 复制该字符串缓冲变量;

(9) 调用字符串缓冲的构架器(Constructor);

(10) 保存该字符串缓冲到数组中(从位置1开始);

(11) 以STR_1为参数,调用字符串缓冲(StringBuffer)类中的append方法;

(12) 加载字符串常量"is my name"(STR_3);

(13) 以STR_3为参数,调用字符串缓冲(StringBuffer)类中的append方法;

(14) 对于STR_BUF_1执行toString命令;

(15) 调用out变量中的println方法,输出结果。

由此可以看出,这两行简单的代码,就生成了STR_1,STR_2,STR_3,STR_4和STR_BUF_1五个对象变量。这些生成的类的实例一般都存放在堆中。堆要对所有类的超类,类的实例进行初始化,同时还要调用类极其每个超类的构架器。而这些操作都是非常消耗系统资源的。因此,对对象的生成进行限制,是完全有必要的。

经修改,上面的代码可以用如下的代码来替换。

StringBuffer name=new StringBuffer("HuangWeiFeng");

System.out.println(name.append("is my name.").toString());

系统将进行如下的操作:

(1) 生成新的字符串缓冲变量new StringBuffer(STR_BUF_1);

(2) 复制该字符串缓冲变量;

(3) 加载字符串常量"HuangWeiFeng"(STR_1);

(4) 调用字符串缓冲的构架器(Constructor);

(5) 保存该字符串缓冲到数组中(从位置1开始);

(6) 从java.io.PrintStream类中得到静态的out变量;

(7) 加载STR_BUF_1;

(8) 加载字符串常量"is my name"(STR_2);

(9) 以STR_2为参数,调用字符串缓冲(StringBuffer)实例中的append方法;

(10) 对于STR_BUF_1执行toString命令(STR_3);

(11)调用out变量中的println方法,输出结果。

由此可以看出,经过改进后的代码只生成了四个对象变量:STR_1,STR_2,STR_3和STR_BUF_1.你可能觉得少生成一个对象不会对程序的性能有很大的提高。但下面的代码段2的执行速度将是代码段1的2倍。因为代码段1生成了八个对象,而代码段2只生成了四个对象。

代码段1:

String name= new StringBuffer("HuangWeiFeng");

name+="is my";

name+="name";

代码段2:

StringBuffer name=new StringBuffer("HuangWeiFeng");

name.append("is my");

name.append("name.").toString();

因此,充分的利用JAVA提供的库函数来优化程序,对提高JAVA程序的性能时非常重要的.其注意点主要有如下几方面; 

4.finalize方法

该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。

1. finalize的作用

finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。

finalize()与C++中的析构函数不是对应的。C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性

不建议用finalize方法完成“非内存资源”的清理工作,但建议用于:① 清理本地对象(通过JNI创建的对象);② 作为确保某些非内存资源(如Socket、文件等)释放的一个补充:在finalize方法中显式调用其他资源释放方法。其原因可见下文[finalize的问题]

2. finalize的问题

一些与finalize相关的方法,由于一些致命的缺陷,已经被废弃了,如System.runFinalizersOnExit()方法、Runtime.runFinalizersOnExit()方法

System.gc()与System.runFinalization()方法增加了finalize方法执行的机会,但不可盲目依赖它们

Java语言规范并不保证finalize方法会被及时地执行、而且根本不会保证它们会被执行

finalize方法可能会带来性能问题。因为JVM通常在单独的低优先级线程中完成finalize的执行

对象再生问题:finalize方法中,可将待回收对象赋值给GC Roots可达的对象引用,从而达到对象再生的目的

finalize方法至多由GC执行一次(用户当然可以手动调用对象的finalize方法,但并不影响GC对finalize的行为)

3. finalize的执行过程(生命周期)

(1) 首先,大致描述一下finalize流程:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。

(2) 具体的finalize流程:

对象可由两种状态,涉及到两类状态空间,一是终结状态空间 F = {unfinalized, finalizable, finalized};二是可达状态空间 R = {reachable, finalizer-reachable, unreachable}。各状态含义如下:

unfinalized: 新建对象会先进入此状态,GC并未准备执行其finalize方法,因为该对象是可达的

finalizable: 表示GC可对该对象执行finalize方法,GC已检测到该对象不可达。正如前面所述,GC通过F-Queue队列和一专用线程完成finalize的执行

finalized: 表示GC已经对该对象执行过finalize方法

reachable: 表示GC Roots引用可达

finalizer-reachable(f-reachable):表示不是reachable,但可通过某个finalizable对象可达

unreachable:对象不可通过上面两种途径可达

状态变迁图:

变迁说明:

新建对象首先处于[reachable, unfinalized]状态(A)

随着程序的运行,一些引用关系会消失,导致状态变迁,从reachable状态变迁到f-reachable(B, C, D)或unreachable(E, F)状态

若JVM检测到处于unfinalized状态的对象变成f-reachable或unreachable,JVM会将其标记为finalizable状态(G,H)。若对象原处于[unreachable, unfinalized]状态,则同时将其标记为f-reachable(H)。

在某个时刻,JVM取出某个finalizable对象,将其标记为finalized并在某个线程中执行其finalize方法。由于是在活动线程中引用了该对象,该对象将变迁到(reachable, finalized)状态(K或J)。该动作将影响某些其他对象从f-reachable状态重新回到reachable状态(L, M, N)

处于finalizable状态的对象不能同时是unreahable的,由第4点可知,将对象finalizable对象标记为finalized时会由某个线程执行该对象的finalize方法,致使其变成reachable。这也是图中只有八个状态点的原因

程序员手动调用finalize方法并不会影响到上述内部标记的变化,因此JVM只会至多调用finalize一次,即使该对象“复活”也是如此。程序员手动调用多少次不影响JVM的行为

若JVM检测到finalized状态的对象变成unreachable,回收其内存(I)

若对象并未覆盖finalize方法,JVM会进行优化,直接回收对象(O)

注:System.runFinalizersOnExit()等方法可以使对象即使处于reachable状态,JVM仍对其执行finalize方法

4. 一些代码示例

(1) 对象复活

[java]view plaincopy

publicclass GC {  

publicstatic GC SAVE_HOOK =null;  

publicstaticvoid main(String[] args)throws InterruptedException {  

SAVE_HOOK =new GC();  

SAVE_HOOK =null;  

        System.gc();  

Thread.sleep(500);  

if (null != SAVE_HOOK) {//此时对象应该处于(reachable, finalized)状态  

System.out.println("Yes , I am still alive");  

}else {  

System.out.println("No , I am dead");  

        }  

SAVE_HOOK =null;  

        System.gc();  

Thread.sleep(500);  

if (null != SAVE_HOOK) {  

System.out.println("Yes , I am still alive");  

}else {  

System.out.println("No , I am dead");  

        }  

    }  

@Override  

protectedvoid finalize()throws Throwable {  

super.finalize();  

System.out.println("execute method finalize()");  

SAVE_HOOK =this;  

    }  

5.equals方法

该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。

6.hashCode方法

该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。

一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。

如果不重写hashcode(),在HashSet中添加两个equals的对象,会将两个对象都加入进去。

7.wait方法

wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。

调用该方法后当前线程进入睡眠状态,直到以下事件发生。

(1)其他线程调用了该对象的notify方法。

(2)其他线程调用了该对象的notifyAll方法。

(3)其他线程调用了interrupt中断该线程。

(4)时间间隔到了。

此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

wait方法是Object对象的方法。线程与锁是分不开的,线程的同步、等待、唤醒都与对象锁是密不可分的。wait方法会将当前线程放入wait set,等待被唤醒,并放弃lock对象上的所有同步声明,当前线程会因为线程调度的原因处于休眠状态而不可用。只有通过以下四个方法可以主动唤醒: 

1. notify 

2. notifyAll 

3. Thread.interrupt() 

4. 等待时间过完。

当线程被唤醒后,线程就从wait set中移除了并且重新获得线程调度能力,同时像其它线程一样持有object的锁。

一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限, 

在Java里边就是拿到某个同步对象的锁(一个对象只有一把锁); 

如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池等待队列中)。 

取到锁后,他就开始执行同步代码(被synchronized修饰的代码); 

线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中等待的某个线程就可以拿到锁执行同步代码了。 

这样就保证了同步代码在统一时刻只有一个线程在执行。

这里就需要补充一下对象锁和类锁的区别。 

事实上,synchronized修饰非静态方法、同步代码块的synchronized (this)用法和synchronized (非this对象)的用法锁的是对象,线程想要执行对应同步代码,需要获得对象锁。

线程正常结束后,会使以这个线程对象运行的wait()等待,退出等待状态!而如果在运行wait()之前,线程已经结束了,则这个wait就没有程序唤醒了。 

原码里的join()方法,实际上就是运行的 wait(). 需要运行join的线程运行join方法,实际上是在此线程上调用了需要加入的线程对象的wait()方法,加入的线程运行完后,自然从wait退出了。

到此,就得出了我的结论:

1 线程对象的wait()方法运行后,可以不用其notify()方法退出,会在线程结束后,自动退出。

2 线程间的等待唤醒机制,最好不要用线程对象做同步锁!

首先我们看一个实例:

public class TestDemo{

public static void main(String [ ]args) throws Interrupted Exception{ 

       MyThread myThread =new MyThread(); 

       System.out.println("before"); 

       myThread.start();        

       System.out.println("after");   

 }

    static class MyThread extends Thread{public void run(){

            synchronized (this) {

                   for(inti=0;i<3;i++){                   

                        System.out.println("number:"+ i);              

  }          

  }      

  }   

 }}

输出的结果是: 

before 

after 

number:0 

number:1 

number:2

首先是Main线程具有抢占cpu资源,然后执行完后,在开始执行子线程。

实例2:

public class TestDemo{

 public static void main(String []args) throws Interrupted Exception{        

        MyThread myThread =new MyThread();        

        System.out.println("before");        

        myThread.start();        

        synchronized (myThread) {  

        myThread.wait();       

  }       

 System.out.println("after");   

 }

 static class MyThread extends Thread{

    public void run(){          

      synchronized (this) {

        for(int i=0;i<3;i++){

             System.out.println("number:"+ i);               

 }           

 }        

}  

  }}

before 

number:0 

number:1 

number:2 

after

我们调用wait方法,Main线程会释放当前锁,进入wait set。然后子线程开始运行,当子线程运行完毕后,会把锁归还。

8.notify方法

该方法唤醒在该对象上等待的某个线程。

对于wait()和notify()的理解,还是要从jdk官方文档中开始,在Object类方法中有:

void notify() 

Wakes up a single thread that is waiting on this object’s monitor. 

译:唤醒在此对象监视器上等待的单个线程

void notifyAll() 

Wakes up all threads that are waiting on this object’s monitor. 

译:唤醒在此对象监视器上等待的所有线程

void wait( ) 

Causes the current thread to wait until another thread invokes the notify() method or the notifyAll( ) method for this object. 

译:导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法

void wait(long timeout) 

Causes the current thread to wait until either another thread invokes the notify( ) method or the notifyAll( ) method for this object, or a specified amount of time has elapsed. 

译:导致当前的线程等待,直到其他线程调用此对象的notify() 方法或 notifyAll() 方法,或者指定的时间过完。

void wait(long timeout, int nanos) 

Causes the current thread to wait until another thread invokes the notify( ) method or the notifyAll( ) method for this object, or some other thread interrupts the current thread, or a certain amount of real time has elapsed. 

译:导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法,或者其他线程打断了当前线程,或者指定的时间过完。

上面是官方文档的简介,下面我们根据官方文档总结一下:

wait( ),notify( ),notifyAll( )都不属于Thread类,而是属于Object基础类,也就是每个对象都有wait( ),notify( ),notifyAll( ) 的功能,因为每个对象都有锁,锁是每个对象的基础,当然操作锁的方法也是最基础了。

当需要调用以上的方法的时候,一定要对竞争资源进行加锁,如果不加锁的话,则会报 IllegalMonitorStateException 异常

当想要调用wait( )进行线程等待时,必须要取得这个锁对象的控制权(对象监视器),一般是放到synchronized(obj)代码中。

在while循环里而不是if语句下使用wait,这样,会在线程暂停恢复后都检查wait的条件,并在条件实际上并未改变的情况下处理唤醒通知

调用obj.wait( )释放了obj的锁,否则其他线程也无法获得obj的锁,也就无法在synchronized(obj){ obj.notify() } 代码段内唤醒A。

notify( )方法只会通知等待队列中的第一个相关线程(不会通知优先级比较高的线程)

notifyAll( )通知所有等待该竞争资源的线程(也不会按照线程的优先级来执行)

假设有三个线程执行了obj.wait( ),那么obj.notifyAll( )则能全部唤醒tread1,thread2,thread3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,tread1,thread2,thread3只有一个有机会获得锁继续执行,例如tread1,其余的需要等待thread1释放obj锁之后才能继续执行。

当调用obj.notify/notifyAll后,调用线程依旧持有obj锁,因此,thread1,thread2,thread3虽被唤醒,但是仍无法获得obj锁。直到调用线程退出synchronized块,释放obj锁后,thread1,thread2,thread3中的一个才有机会获得锁继续执行。

2.wait和notify简单使用示例

public class WaitNotifyTest{

 // 在多线程间共享的对象上使用wait private String[ ]  shareObj = { "true" };

    public static void main(String[] args) {

        WaitNotifyTest test = new WaitNotifyTest();

        ThreadWait threadWait1 = test.new ThreadWait("wait thread1");

        threadWait1.setPriority(2);

        ThreadWait threadWait2 = test.new ThreadWait("wait thread2");

        threadWait2.setPriority(3);

        ThreadWait threadWait3 = test.new ThreadWait("wait thread3");

        threadWait3.setPriority(4);

        ThreadNotify threadNotify = test.new ThreadNotify("notify thread");

        threadNotify.start();

        threadWait1.start();

        threadWait2.start();

        threadWait3.start();

    }

    class ThreadWait extends Thread {

        public ThreadWait(String name){

            super(name);

        }

        public void run() {

            synchronized (shareObj) {

                while ("true".equals(shareObj[0])) {

                    System.out.println("线程"+ this.getName() + "开始等待");

                    long startTime = System.currentTimeMillis();

                    try {

                        shareObj.wait();

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    long endTime = System.currentTimeMillis();

                    System.out.println("线程" + this.getName()

                            + "等待时间为:" + (endTime - startTime));

                }

            }

            System.out.println("线程" + getName() + "等待结束");

        }

    }

    class ThreadNotify extends Thread {

        public ThreadNotify(String name){

            super(name);

        }

        public void run() {

            try {

                // 给等待线程等待时间                sleep(3000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            synchronized (shareObj) {

                System.out.println("线程" + this.getName() + "开始准备通知");

                shareObj[0] = "false";

                shareObj.notifyAll();

                System.out.println("线程" + this.getName() + "通知结束");

            }

            System.out.println("线程" + this.getName() + "运行结束");

        }

    }

}

运行结果:

线程wait thread1开始等待

线程wait thread3开始等待

线程wait thread2开始等待

线程notify thread开始准备通知

线程notify thread通知结束

线程notify thread运行结束

线程wait thread2等待时间为:2998线程wait thread2等待结束

线程wait thread3等待时间为:2998线程wait thread3等待结束

线程wait thread1等待时间为:3000线程wait thread1等待结束

9.notifyAll方法

该方法唤醒在该对象上等待的所有线程

在Java语言中notifyAll()方法的实际作用如下:

1.notifyAll(): Wakes up all threads that are waiting on this object's monitor;

2.当一个线程使用的同步方法中用到某个变量,而此变量又需要其它线程修改后才能符合本线程的需要,则可以在同步方法中调用wait()方法,使本线程等待,并允许其它线程调用这个同步方法

3.其它线程在使用这个同步方法不需要等待,当它使用完这个同步方法时,用notifyAll()通知所有由于使用这个同步方法而处于等待的线程结束,再次使用这个同步方法

4.如果使第一个处于等待的线程结束等待,则调用方法notify()

Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。

Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点  。Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等。

 在Java多线程中,notify() 与 notifyAll() 是比较重要的方法,通常与wait方法配合使用,它们只能在同步域里面调用否则会出现异常IllegalMonitorStateException

下面说说他们的功能:

notify :作用是唤醒指定某个线程,在一般实际使用情况下此方法用的不多,因为一般不只会存在1、2个线程,当线程多的时候使用此方法维护起来就很麻烦,很容易造成死锁。

notifyAll : 作用是唤醒指定锁上等待执行的所有线程,打个比方,此时有线程 T1 线程 T2 线程 T3 现在假设T2与T3线程先执行并且是处于wait等待状态,他们要等T1去唤醒他们,由于他们两都是处于wait状态,除非有其他线程唤醒它们否则他们会一直处于等待状态,如果此时T1已经执行并且已经调用notifyAll方法,就会唤醒T2与T3,可能这时会有疑问线程T1是怎么能够唤醒其他两个线程的呢?首先会有个前提条件 T1、T2、T3线程都是处于同一个锁域,因为如果不是处于同一个锁域调用notifyAll或者是notify会报错的,代码例如这样:

T1:

// 之所以加个while 以及flag标示是因为可能存在T2执行完就执行T1的情况或者是先执行了T1,这样就会造成死锁,因为T1一旦先执行那就没有线程把T2、T3唤醒了,T2 T3则一直等待有个线程唤醒他,而此时已没有线程能唤醒他们了。

while(true){   

if(flag){

synchronized(lock){

println("执行了T1");

notifyAll();

flag = false;

          }

}

}

// 还能这样,将T2,T3线程传入T1中,调用它们的join方法等待他们执行完才执行notifyAll(),相比前一个代码这个代码更直接些,但是存在多个不同线程时会很麻烦,因为要把他们作为参入传入或者调用。

T1:

T2 t2;

T3 t3;

synchronized(lock){

t2.join();

t3.join();

println("执行了T1");

notifyAll();

}

T2:

synchronized(lock){

flag = true;

wait();

println("执行了T2");

}

T3:

synchronized(lock){

flag = true;

wait();

println("执行了T3");

}

注意:当某个线程调用wait()方法之后会自动释放当前线程占用的锁。



                                                                    注:本文收集于各大网络,一切只为了学习!谢谢各位大神.

上一篇下一篇

猜你喜欢

热点阅读