java 学习总结

java学习总结

java 语言特性相关

  1. 构造器相关
    1. 继承时候,如果默认构造器无任何参数,编译器会默认调用父类构造器,如果子类构造器 有参数,那就必须显式调用父类构造器
    2. 构造器有多种时候,常用的是调用已有构造器,若构造器实在太多时候,应考虑工厂方法(多用于父类子类一系列),再设置参数
  2. java 内存回收相关

    1. Java 中只需简单地忘记对象,不需强行破坏它们。垃圾收集器会在必
      要的时候自动回收内存。 这里忘记他们,应该把指向对象的引用设置为null

    2. 假如垃圾收集器发现一个句柄仍在使用,就不会清除对象。若认为自己的句柄可能象现在这
      样被挂起,那么最好将其设为 null ,使垃圾收集器能够正常地清除它们。 一般来说,这类事情,GC会帮你管理,自己不用显式地去设置变量为null。但是如果调用了非java类代码,如C类语言new了内存,这时候需要显示编写finilize函数来释放内存了

    3. 垃圾回收这里其实还有比较多深入的内容这里并没有过度展开

  3. final 相关

    1. 关于final的解释:对于基本数据类
      型, final 会将值变成一个常数;但对于对象句柄, final 会将句柄变成一个常数。进行声明时,必须将句柄
      初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。然而,对象本身是可以修改的。

    2. 如果说整个类都是 final(在它的定义前冠以 final 关键字),就表明自己不希望从这个类继承,或者不允
      许其他任何人采取这种操作。换言之,出于这样或那样的原因,我们的类肯定不需要进行任何改变;或者出
      于安全方面的理由,我们不希望进行子类化(子类处理)。

    3. 注意数据成员既可以是 final,也可以不是,取决于我们具体选择 . 应用于 final 的规则同样适用于数据成
      员,无论类是否被定义成 final。将类定义成 final 后,结果只是禁止进行继承—— 没有更多的限制。然
      而,由于它禁止了继承,所以一个 final 类中的所有方法都默认为 final。因为此时再也无法覆盖它们。
      为什么要把一个方法声明成 final 呢?正如上一章指出的那样,它能防止其他人覆盖那个方法。但也许更重
      要的一点是,它可有效地“关闭”动态绑定,或者告诉编译器不需要进行动态绑定。这样一来,编译器就可
      为 final 方法调用生成效率更高的代码。
      单例模式,构造器设为私有,然后通过静态方法才能创建实例

  4. 抽象类和接口的意义

    1. “过载”是指同一样东西在不同的地方具有多种含义,类似于多态子类和父类方法不同;而“覆盖”是指它随时随地都
      只有一种含义,只是原先的含义完全被后来的含义取代了。

    2. 继承抽象类必须实现抽象类里面所有的抽象方法,不然继承的也是抽象类。抽象类不能实例化,这也是它的意义所在,为了调用子类的方法而实现的很多接口,不能用于实例化.抽象类里面可以有非抽象方法

    3. 接口是更加纯粹的抽象类,并且里面不能有实例化方法。
      接口中定义的字段会自动具有 static 和 final 属性。它们不能是“空白 final”,但可初始化成非常数表达
      式。 由于置入一个接口的所有字段都自动具有 static 和 final 属性,所以接口是对常数值进行分组的一个好工
      具,它具有与 C 或 C++的 enum 非常相似的效果 。 接口里面的方法即为static public属性

  5. 关于java的访问控制

    1. protected 不仅可以让子类可以访问到,还能让同一个包内的类访问到,默认的私有性比protected还高,意思是不是同一个包内的类即使是父类的子类也不能接触到父类的默认私有属性类,只有一个包内的能接触到
  6. 使用内部类简化操作

    1. 内部类使用的很大一个原因是因为它方便

    2. 普通内部类会默认引用外部类的句柄,这在某些情况可能会带来内存泄露的问题,这里可以考虑使用weakReference等方式使得内部类对外部类的引用减轻

    3. 匿名内部类不能有构造器,因为它是匿名的,而且要接触的局部变量应该是final类型的
      为正确理解 static 在应用于内部类时的含义,必须记住内部类的对象默认持有创建它的那个封装类的一个句柄

    4. 静态内部类的效果

      1. 为创建一个 static 内部类的对象,我们不需要一个外部类对象。这里也就是说静态内部类不持有外部类的句柄

      2. 当创建一个内部类并且这个内部类与外部类无紧密联系时候考虑静态内部类,因为它不会默认持有外部类的引用,因此也不会存在内存回收的问题。

      内部类相关知识:

      1. 内部类分为多种,从位置上来分,有方法内部类,类内部类,匿名内部类。
      2. 内部类分为静态内部类和非静态内部类
        非静态内部类能直接访问外部类的私有变量,从而使得使用便利,这是它最有用的一个特点。非静态内部类的初始化要依赖与外部实例的初始化
      3. 如果一个类要被声明为static的,只有一种情况,就是静态内部类。如果在外部类声明为static,程序会编译都不会过。在一番调查后个人总结出了3点关于内部类和静态内部类(俗称:内嵌类)

        • 静态内部类跟静态方法一样,只能访问静态的成员变量和方法,不能访问非静态的方法和属性,但是普通内部类可以访问任意外部类的成员变量和方法

        • 静态内部类可以声明普通成员变量和方法,而普通内部类不能声明static成员变量和方法。

        • 静态内部类可以单独初始化:

Inner i = new Outer.Inner();
 普通内部类初始化:
Outer o = new Outer();
Inner i = o.new Inner();
    4. 静态内部类使用场景一般是当外部类需要使用内部类,而内部类无需外部类资源,并且内部类可以单独创建的时候会考虑采用静态内部类的设计,在知道如何初始化静态内部类,在《Effective Java》第二章所描述的静态内部类builder阐述了如何使用静态内部类:
一个常见的使用方式,在effective java里面有描述到:
public class Outer {
private String name;
private int age;

public static class Builder {
private String name;
private int age;

public Builder(int age) {
this.age = age;
}

public Builder withName(String name) {
this.name = name;
return this;
}

public Builder withAge(int age) {
this.age = age;
return this;
}

public Outer build() {
return new Outer(this);
}
}

private Outer(Builder b) {
this.age = b.age;
this.name = b.name;
}
}
对于静态类总结是:
  1. 如果类的构造器或静态工厂中有多个参数,设计这样类时,最好使用Builder模式,特别是当大多数参数都是可选的时候。
  2. 如果现在不能确定参数的个数,最好一开始就使用构建器即Builder模式。
    还有静态类的一些特点:
  3. 嵌套类的对象,并不需要其外围类的对象。 即它可以不依赖于外部类实例被实例化。
  4. 不能从嵌套类的对象中访问非静态的外围类对象。 这是由Java语法中”静态方法不能直接访问非静态成员”所限定
  5. 外部类访问内部类的的成员有些特别, 不能直接访问, 但可以通过内部类实例来访问, 这是因为静态嵌套内的所有成员和方法默认为静态的了.同时注意, 内部静态类Person只在类StaticTest 范围内可见, 若在其它类中引用或初始化, 均是错误的.
  6. 静态内部类可以有静态成员,而非静态内部类则不能有静态成员。
  7. 静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量;
  8. 非静态内部类的非静态成员可以访问外部类的非静态变量。
  9. 生成一个静态内部类不需要外部类成员:这是静态内部类和成员内部类的区别。静态内部类的对象可以直接生成:Outer.Inner in = new Outer.Inner();而不需要通过生成外部类对象来生成。这样实际上使静态内部类成为了一个顶级类(正常情况下,你不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分,因为它是static 的。只是将嵌套类置于接口的命名空间内,这并不违反接口的规则)

  10. java集合类

    1. 集合类,主要用的就是Set,List,Map等抽象类接口,具体实现类的选择可以根据不同情况仔细选择。

    2. 集合类都可以考虑用父类抽象调用子类具体方法,这里的意思是可以用父类的api来调用子类的方法,可替换性更强

    3. Collects,和Arrays等类有许多有用的静态方法,好多常见的找最大最小,使Collect变为线程安全或是变成不可改变的方法都有。要实现自己的集合类时候,可以从AbstractList等类中继承,能达到减少代码的作用。

    4. 集合类里面也有分为线程安全的集合类和非线程安全的集合类,这些在多线程保护数据有效性时候也是比较重要的事情

  11. Exception相关

    1. 违例分为Exception和Error,其中Exception才是我们应该管的

    2. IOException和RuntimeException又是其中两大类,IOException需要我们自己去捕获处理,而RuntimeException往往是编译器能帮我们捕获而无须自己捕获。构造器要特别注意违例的问题,子类Exception的捕获要在父类之前才可以更准确捕获Exception

  12. 创建对象clone和hashCode的注意事项

    1. 创建一个对象时候,如果希望该对象具有clone能力应该做如下事情

      1. 实现 Cloneable 接口
      2. 覆盖 clone()
      3. 在自己的 clone()中调用 super.clone()
      4. 在自己的 clone()中捕获违例
    2. 实现 Cloneable 接口。这个接口使人稍觉奇怪,因为它是空的

       interface Cloneable {}

      这里是为了考虑到标记是不是这个类真的有实现Cloneable接口
      if(myHandle instanceof Cloneable)

    3. 第二个原因是考虑到我们可能不愿所有对象类型都能克隆。所以 Object.clone()会验证一个类是否真的是实
      现了 Cloneable 接口。若答案是否定的,则“掷”出一个 CloneNotSupportedException 违例。所以在一般情
      况下,我们必须将“implement Cloneable”作为对克隆能力提供支持的一部分。

    4. 克隆过程的第一个部分通常都应该是调用 super.clone()。通过进行一次准确的复制,
      这样做可为后续的克隆进程建立起一个良好的基础。随后,可采取另一些必要的操作,以完成最终的克隆。
      这里的clone,Object.clone()只有基本类型才会做深拷贝,其它都是做浅拷贝,只是拷贝句柄

    5. 有时候clone方法也是一件比较麻烦的事,其实可以通过duplicate()
      的函数封装其实也可以
  13. try-catch

    1. 关于try-catch-finally 的问题,try这里讲的很清楚,finally的执行顺序在return之前,总之进入try块以后,finally一定会被执行,除非exit(0)停掉jvm,如果finally里面有return 那么直接覆盖try或catch里面的return句子,如果finally里面没有return子句,他不会改变try或catch块里面的返回值。

    java多线程

    java多线程基本知识

  14. 首先了解java线程的调度机制

    1. 主要机制如下图:
      详细内容参考java生命周期
  15. 线程的让步是通过Thread.yield()来实现的。yield()方法的作用是:暂停当前正在执行的线程对象,并执行其他线程。

    1. 要理解yield(),必须了解线程的优先级的概念。线程总是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况。

    2. yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
      结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

  16. join()方法,Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前,B不能工作。

     Thread t = new MyThread();
        t.start();
        t.join();
    Thread t2=new MyThread().start();
    

    这里表示t执行完后,t2才能执行,多线程就变成有顺序执行了.
    另外,join()方法还有带超时限制的重载版本。 例如t.join(5000);则让线程等待5000毫秒,如果超过这个时间,则停止等待,变为可运行状态。

  17. 除了以上三种方式外,还有下面几种特殊情况可能使线程离开运行状态:

    1. 线程的run()方法完成。
    2. 在对象上调用wait()方法(不是在线程上调用)
    3. 线程不能在对象上获得锁定,它正试图运行该对象的方法代码
    4. 线程调度程序可以决定将当前运行状态移动到可运行状态,以便让另一个线程获得运行机会,而不需要任何理由。
  18. 多个线程之间的管理和复用以前的线程资源可以使用ExcutorService,Excutor等线程管理类,具体详看java并发编程

  19. 线程之间的相互作用的许多使用类在concurrent包里面,里面有例如ThreadLocal等线程局部变量,还有Java并发编程:CountDownLatch、CyclicBarrier和Semaphore这些类的使用等。具体详看java并发编程

同步锁

  1. 同步锁:
    一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。释放锁是指持锁线程退出了synchronized同步方法或代码块。

  2. 同步锁一些细节需要注意

    • 只能同步方法,而不能同步变量和类
    • 每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步
    • 不必同步类中所有的方法,类可以同时拥有同步和非同步方法
    • 如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法
    • 如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制
    • 线程睡眠时,它所持的任何锁都不会释放
    • 线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁,这里也就是说锁具有重入性
    • 同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块
    • 在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如
  3. 线程的锁有哪些,除了synchronized语言特性上的锁以外,还有在concurrent包里面的lock对象,这里是在api层面对资源进行同步

    1. sychonized是java内置的关键字,通过给方法加上同步快来实现同步,而当一个线程拥有锁的时候,其他线程必须等待,如一个线程进行同步的IO操作,其他线程就不能进行读取IO操作,
      这时候效率就比较低,因为其他线程必须等待正在执行的线程完成同步后才能进行操作,而且不能中断线程等待状态,只能一直位于等待状态。

    2. 由于在前面讲到如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:

    Lock lock = ...;
    if(lock.tryLock()) {
     try{
         //处理任务
     }catch(Exception ex){
    
     }finally{
         lock.unlock();   //释放锁
     } 
    }else {
    //如果不能获取锁,则直接做其他事情
    }
    
    1.  tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。

    2. tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

    3. 注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为本身在前面的文章中讲过单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。
       因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

    4. lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。这就是说这是sychonized可中断版本,在没获得锁时候,线程B会默认等待,
      假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。然而也可以通过自己调用interrupt()方法进行中断
      由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。

    所以,一般情况下通过tryLock来获取锁时是这样使用的:

    public void method() throws InterruptedException {
    lock.lockInterruptibly();
    try {  
     //.....
    }
    finally {
        lock.unlock();
    }  
    }
    1. 这里的lock是可中断锁,而synchronized语言特性确是不可中断的。

    2. lock还有些子类, ReentrantLock,意思是“可重入锁”,ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。
      ReentrantReadWriteLock可以让读操作非同步进行,从而提高效率

      1. 锁能依照特性分为几类

      2. 可重入锁

      3. 如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
    public synchronized void method1() {
        method2();
    }
    
    public synchronized void method2() {
    
    }
    }

    上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。
    而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。

    1. 可中断锁

      • 可中断锁:顾名思义,就是可以相应中断的锁。
      • 在Java中,synchronized就不是可中断锁,而Lock是可中断锁。如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。在前面演示lockInterruptibly()的用法时已经体现了Lock的可中断性。
          
          

        1. 公平锁
      • 公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
      • 非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
      •  在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
      • 而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
      • 我们可以在创建ReentrantLock对象时,通过以下方式来设置锁的公平性:
      • ReentrantLock lock = new ReentrantLock(true)
        ReentrantLock lock = new ReentrantLock(true);

        1. 读写锁
      • 读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
      • ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。

        1. Lock和synchronized的选择
      • Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

      • synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁
      • Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
      • 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
      • Lock可以提高多个线程进行读操作的效率。
      •  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
  4. 如果线程不能不能获得锁会怎么样

    1. 如果线程试图进入同步方法,而其锁已经被占用,则线程在该对象上被阻塞。实质上,线程进入该对象的的一种池中,必须在哪里等待,直到其锁被释放,该线程再次变为可运行或运行为止。

    2. 当考虑阻塞时,一定要注意哪个对象正被用于锁定:

      1. 调用同一个对象中非静态同步方法的线程将彼此阻塞。如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。

      2. 调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上。

      3. 静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。

      4. 对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会彼此阻塞。

  5. 何时需要同步

    1. 在多个线程同时访问互斥(可交换)数据时,应该同步以保护数据,确保两个线程不会同时修改更改它。对于非静态字段中可更改的数据,通常使用非静态方法访问。对于静态字段中可更改的数据,通常使用静态方法访问。
  6. 线程同步小结

    1. 线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。

    2. 线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。

    3. 对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。

    4. 对于同步,要时刻清醒在哪个对象上同步,这是关键。

    5. 编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。

    6. 当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。

  7. volatile变量的使用:

    1. volatile 变量的情形。(不清楚建议不要使用)锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。

线程交互的基础知识

  • 线程交互知识点需要从java.lang.Object的类的三个方法来学习:
void notify() 
          唤醒在此对象监视器上等待的单个线程。 
 void notifyAll() 
          唤醒在此对象监视器上等待的所有线程。 
 void wait() 
          导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。

当然,wait()还有另外两个重载方法:
 void wait(long timeout) 
          导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。 
 void wait(long timeout, int nanos) 
          导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
  1. 以上这些方法是帮助线程传递线程关心的时间状态。

  2. 关于等待/通知,要记住的关键点是:必须从同步环境内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。

  3. wait()、notify()、notifyAll()都是Object的实例方法。与每个对象具有锁一样,每个对象可以有一个线程列表,他们等待来自该信号(通知)。线程通过执行对象上的wait()方法获得这个等待列表。从那时候起,它不再执行任何其他指令,直到调用对象的notify()方法为止。如果多个线程在同一个对象上等待,则将只选择一个线程(不保证以何种顺序)继续执行。如果没有线程等待,则不采取任何特殊操作
  4. 当在对象上调用wait()方法时,执行该代码的线程立即放弃它在对象上的锁。然而调用notify()时,并不意味着这时线程会放弃其锁。如果线程荣然在完成同步代码,则线程在移出之前不会放弃锁。因此,只要调用notify()并不意味着这时该锁变得可用。
  5. 实际上,上面这个代码中,我们期望的是读取结果的线程在计算线程调用notifyAll()之前等待即可。 但是,如果计算线程先执行,并在读取结果线程等待之前调用了notify()方法,那么又会发生什么呢?这种情况是可能发生的。因为无法保证线程的不同部分将按照什么顺序来执行。幸运的是当读取线程运行时,它只能马上进入等待状态—-它没有做任何事情来检查等待的事件是否已经发生。 —-因此,如果计算线程已经调用了notifyAll()方法,那么它就不会再次调用notifyAll(),—-并且等待的读取线程将永远保持等待。这当然是开发者所不愿意看到的问题。因此,当等待的事件发生时,需要能够检查notifyAll()通知事件是否已经发生。
  6. concurrent包里面有具有一些多线程较为高级的使用,具体详看concurrent

你可能感兴趣的:(java,java,线程,同步)