java [内部类]-[异常]-[多线程]-[线程池]

本文详细介绍了Java中的内部类,包括成员内部类、局部内部类和匿名内部类的特性和使用场景。接着深入讨论了异常处理机制,包括异常的分类、自定义异常以及异常处理的基本原则。然后讲解了多线程的创建方式,如继承Thread类和实现Runnable接口,并探讨了线程安全问题和同步机制。最后提到了线程池的概念和ExecutorService的使用,以及不同类型的线程池创建方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

一 内部类

 

  • 内部类的特点:


  1),内部类可以直接访问外部类的成员,包括私有的
         若内部类将外部类成员变量覆盖,要访问外部类成员变量 采用格式 :外部类名.this.成员变量
  2),外部类要访问内部类的成员必须创建内部类对象
  3),内部类的权限可以是private 默认,protected public  ,外部类只能是public 默认

  4),  内部类能被static 修饰,外部类不可以
  5),  内部类和外部类都可以的final 和 abstract 的

  6),  内部类可以继承其他类和实现其他接口

内部类根据其定义的位置分为成员内部类,局部内部类,匿名内部类

不能位置的内部类能够使用的修饰符和访问范围也各不相同

 

  • 内部类的意义:

    •  -->通过修饰符protected和private来控制访问级别,将一个类中的内部某部分的实现细节隐藏,体现了java封装性
    •  -->内部类对象可以访问创建它的外部类对象的内容,包括私有成员!
    •  -->每个内部类都能独立地继承一个接口,而无论外围类是否已经继承了某个接口。
            因此,内部类使多重继承的解决方案变得更加完整。集合中的Map.Entry也是内部类的应用
  •  成员内部类的修饰符及访问方式:


  1 , 当内部类定义的外部类成员位置上,而且非私有,可以在外部其他类中直接建立内部类对象
      格式:
     外部类.内部类  引用变量 = new 外部类().new 内部类();

      内部类具有指向外部类的引用,所以能够访问外部类的成员包括私有的

  2,  当内部类定义在成员的位置上被成员修饰符所修饰
    比如

     private 将内部类在外部类中进行封装 除了它的外部类以外其他类中都无法访问。
                 通过这种方式可以完全阻止任何依赖于类型的编码,并完全隐藏实现的细节。 
     static 修饰内部类 内部类就具备static的特性 也叫静态内部类或嵌套类或静态成员类, 静态内部类中

                只能直接访问外部类中的static成员,静态内部类没有了指向外部的引用,出现了访问局限,静态

                的内部类对象不依赖于外部类对象
               

                静态内部类中可以有static字段与静态内部类,静态内部类中的也可以有非静态的成员

                非静态内部类不允许定义静态成员


      内部类其他修饰符:
      public, friendly ,protected,
      也可以将内部类声明为: abstract 和 final


      (a) 在外部的其他类中如何直接访问static内部类非静态成员?


                new Outer.Inner().function();

           通过该方式可以访问到静态的和非静态的成员
     (b) 在外部类其他类中,如何直接访问static内部类的静态成员?
               Outer.Inner.function();

           通过该方式只能访问到静态成员


    注意:
    当内部类中定义了静态成员,该内部类必须是static的
    当外部类中的静态方法访问内部类时,内部类也必须是static的,

    静态只能访问静态 ,非静态可以访问静态也可以访问非静态

 

  •  局部(方法中定义)内部类


    1)局部内部类不能被静态修饰,因为static是成员修饰符,所以局部内部类中不可能有静态成员。

    3)  局部内部类权限只能是默认的,不能是public 或 protected 或 private

    4)  局部内部类可以被final 和 abstract修饰

    5)局部内部类可以直接访问外部类中的成员,因为还持有外部类中的引用,但不可以

         访问它所在的方法的局部变量,只能访问被final修饰的局部变量因为这样可以使变量不再是局部而是全局化
   
    6) 局部内部类只能在所定义的外部类的方法中创建对象 , 不能在其外部类或外部类以外的其他类创建对象

    7) 局部内部类的生命周期跟其所在的外部类方法一样,局部内部类跟方法一样随着外部类的加载而加载,但其中的成员

        变量跟其所在的外部类方法中的局部变量一样在使用到的时候才分配到内存空间,随着方法的调用结束而释放内存

  • 匿名内部类

  • 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或继承一个类以便得到一个实例,没有类名,格式

         new 接口\抽象类(){匿名内部类类体}

  • 匿名内部类是唯一一种无构造方法类(可以添加匿名构造方法)。
  • 匿名类用于继承一个类或是实现接口并得到一个实例, 可以跟普通的实现接口和普通的继承类一样, 可以对继承方法的实现或覆盖,也可以添加新的成员。
  • 匿名内部类不能定义任何静态成员、方法和类。   
  • 匿名内部类不能被权限修饰符public,protected,private 和 static 修饰。  
  • 只能创建匿名内部类的一个实例。
  • 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或继承一个类。  
  • 因匿名内部类为局部内部类,所以跟局部内部类的一样的限制

 

  • 匿名内部类适用于类在定义后马上用到,如:
    • 只是为了获得一个继承来的类的对象或获得一个接口的实现类实例
    • 只是想调用一次一个接口的实现类的一个方法或调用一次继承来的类的一个方法
    • 只是想作为方法的实际参数
    • 类非常小(SUN推荐是在4行代码以下)  

 

  • 定义匿名内部类的前提:内部类必须是且只能继承一个类或者实现了一个接口
     

      

 

二  异常

 

  • 异常的定义:


   程序运行时出现的不正常情况  ,如:想打开的文件不存在、网络连接中断、操作数超出预定范围、正在装载的类文件丢失
   表达式的计算违反了Java语言的语义 内存溢出等

   需要阻止当前方法或作用域继续执行 , java中有异常处理机制

   异常处理机制,就是要告诉开发人员,你的程序出现了不正常的情况,请注意。

 

  • 异常体系

java中所有的异常都由类来表示,所有的异常类都从一个名为Throwable的类派生类出来的。

Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。

当发生一个异常时,就会生成 一个Throwable类或其子类的对象。

  • Throwable有两个直接子类:Exception和Error。


   1)对于一般的问题通过Excepttion描述


    程序运行所导致的错误由Exception类来表示,该异常类定义了程序中可能遇到的轻微的错误条件。可以编写代码处理异
    常并继续执行程序,而不是让程序中断。它代表轻微的故障,可以恢复的故障,接收到异常信号后,调用方法捕获抛出的异
    常,在可能时,再恢复回来。这类异常一般由程序员来处理。


   2)对于严重的问题通过error描述


    error类型的异常相关的错误发生在Java虚拟机中,而不是在程序中。Error类定义了被认为是不能恢复的严重错误。在大多数
    情况下,当遇到这样的错误时,建议让该程序中断。这样的异常超出了程序可控制的范围,因此,程序员一般不处理这类异常。

 

  • 运行时Exception 和 编译时 Exception

 

    • 异常的特殊子类异常:运行时异常 RuntimeException
    •  Exception分两种:


   1,编译时被检测到的异常
     函数内抛出编译时异常,函数上必须声明;

     函数上声明编译时异常,调用者必须处理(继续往外抛或try...catch处理).
     抛出去或try{}catch(){}, 否则编译失败。该异常被标识,代表这可以被处理。


   2,编译时不能被检测到的异常(运行时异常,RuntimeException及其子类)
      不用声明的;声明了也不用处理。

    •   RuntimeException 特点:不抛不处理
         
          (1)如果在函数内抛出 RuntimeException 异常(含子类异常),函数上可以不用声明,编译一样通过。
          (2)如果在函数上声明了 RuntimeException 异常(含子类异常),调用者可以不处理,编译一样通过。
              对于函数内抛出非 RuntimeException 异常函数上必须做声明
          (3)之所以不用在函数上声明,是因为不需要让调用者处理,只希望程序停止,因为运行时出现了无法
             继续进行的情况,必须对代码修正才行,Java运行系统能自动抛出,处理, 所以调用者可以不抛不处理

 

    • 常见的RuntimeException异常:
         

     ArithmeticException :算术运算异常 
     IndexOutOfBoundsException:角标越界异常 
                          |--ArrayIndexOutOfBoundsException: 数组角标越界异常
                          |_StringIndexOutOfBoundsException:字符串角标越界异常
     ClassCastException:类型转换异常
     NullPointerException :空指针异常
    SecurityException:安全侵犯异常

  • 自定义异常时,如果该异常的发生,无法再继续进行运算,就让自定义异常继承RuntimeException

 

  •   怎么处理异常呢?


   java在运行时环境发生异常而产生异常对象时,会寻找处理该异常的catch块,如果找到则将该异常对象交给catch块进行处理
   这个过程就捕获异常,如果java运行时环境找不到异常的catch块则运行时环境终止,java程序退出

  •   异常处理机制


  程序中,把需要被检查的代码段放在一个以关键字try开头的代码块中。try代码块如果有异常发生就会抛出该异常。

  若有异常抛出则用catch捕获这个异常,并在catch的语句中加以适当的处理或者可以使用关键字throw手动抛出自定义异常。

  而从一个方法抛出异常必须用throws。异常可能导致一个终止当前方法的错误,造成其方法返回。finally语句块在退出

  try/catch异常处理代码块时执行 ,可以将退出时必须的操作可放在finally代码块中。

  所以finally语句块是一定会执行的无论是否有异常发生 , 除非在try catch中用exit()函数终止程序
  

  •  异常处理语句:
        try
        {
         需要被检测的代码;
        }
        catch (异常类型)
        {
         处理异常的代码;
        }
        finally
        {
         一定会执行的代码(除非在其他地方调用了exit());//用于关闭释放资源
        }

 

  • 异常处理语句的三种格式


   第一个格式:try{}catch (){}
   第二个格式:try{}catch (){}finally{}
   第三个格式:try{}finally{}

   catch必须配合try使用,finally也必须配合try使用
   

  • catch

catch是用于捕获异常catch块处理异常。如果没有catch就代表异常没有被处理过,
   如果该异常是编译检测时异常。那么必须声明。

   catch中异常信息输出:
   catch(Exception e)
   e.getMessage(); //返回throwable的详细的字符串。
   e.toString(); //返回异常对象名称及异常信息简述
   e.printStackTrace();//返回追踪到的异常位置信息

  •    多异常


1,声明异常时,建议声明更具体的异常,使处理可以更具体

2,对方声明几个异常,就对应有几个catch块,不要定义多余的catch块

3,  如果多个catch块中的异常出现继承关系,父类异常catch块放在最下面

4,  catch处理时。catch一定要定义具体处理方式
     不要简单定义异常信息的提示语句 e.printStackTrace()
     也不要简单的就一条输出语句

  • 自定义异常

  • 将项目中的会出现的问题封装为对象
  • 自定义异常类必须继承 Exception
       继承的原因:
       1,为了让自定义异常具备可抛性
        异常体系有一特点:因为异常类和异常对象都被抛出
        都具有可抛性,这个可抛性是Exception 特有的,如果不具备可抛性
        则发生该类异常时无法通过虚拟机抛出,无法对其进行捕获处理

               2,让自定义异常类具备操作异常的共性方法。
                当要定义自定义异常的信息时,可以使用父类已经定义好的功能。
                将自定义异常信息传递给父类的构造函数。也可以复写Exception方法
                

  • 异常的好处:

   1,将问题进行封装。
   2,将正常流程代码和问题处理代码相分离,方便于阅读。

  • 抛出异常

   throw new 自定义异常()  //手动抛出自定义异常
   抛出异常后要给出对应的处理:

   要么调用内部的try{}  catch{}处理
   要么抛出

    •    throw 与 throws 的区别


   throw 是一个抛出动作,定义在函数内,用于抛出异常对象。抛出的异常必须是Throwable或其子类的实例
   throws 也是抛出异常动作,定义在函数上,用于声明抛出异常类,可以抛出多个异常类用逗号隔开。
               表明该方法可能会产生某类异常或者该方法产生自己不处理或无法处理的异常,若产生异常,则将其抛出
               让方法的调用者去处理


 异常的特殊子类异常:运行时异常 RuntimeException
  异常分两种:
   1,编译时被检测的异常
     函数内抛出编译时异常了,函数上必须声明;函数上声明编译时异常了,调用者必须处理,处理throws
     抛出去或try{}catch(){}, 否则编译失败。该异常被标识,代表这可以被处理。
   2,编译时不被检测的异常(运行时异常,RuntimeException及其子类)
      不用声明的;声明了也不用处理。

  • 异常在子父类方法覆盖中的体现:


   1),父类被覆盖的方法抛出异常,那么子类覆盖的方法只能抛出父类的异常或该异常的子类异常
   2),父类方法抛出多个异常,那么子类覆盖的该方法时,只能抛出父类异常的子集
   3),若父类方法没有异常抛出,那么子类覆盖的方法如果发生异常只能try{}catch(){}进行处理

  

 

三  多线程

 

  • 进程---线程

 

    • 什么是进程? 什么是线程? 线程与进程是什么关系呢?


 进程是执行中的程序,
 线程是进程中的一个执行单元,控制着进程的执行


 Java VM  启动的时候会有一个进程java.exe.该进程中至少一个线程负责java程序的执行。
 该线程叫主线程,该线程用来运行存在于main方法中的代码。 

  一个进程可以有多个线程,他们可以并发执行进程中的代码,完成不同的任务,实际上对应单核CPU,一个时刻只能有一个线程在执行任务,
 多线程间通过竞争来获取CPU的执行权,如果不对线程进行控制那么他们在执行顺序是不可预知的是随机的,同一进程内的线程可以共享
 该进程中的资源

线程属于进程,进程退出是该进程所产生的线程也强迫结束

  •  怎么创建线程?

Runnable是接口,只有一个方法 run() ,实现类实现了run方法或实现类的子类复写了run方法之后通过

Thread是Runnable的一个实现类

Thread的实例对象就是线程,每创建一个Thread对象都会创建一个线程,通过start方法来启动创建的线程


 线程创建时通常需要将一个Thread的子类对象创建或通过经Runnable的实现类对象传入Thread的构造方法

中来创建 ,因为创建线程的目的就是为能独立的并发的执行想执行的代码, 而创建了线程之后,是通过执行

start方法来告诉虚拟机要启动线程了,同时会调用run方法,如果没有在Thread的构造方法中将覆盖过run方法

的对象传入那么run方法不执行任何语句,只有传入了覆盖了Thread的或覆盖了Runnable的run方法的对象

时,才会去执行覆盖的run方法,所以想要在线程中运行的代码就放到了Thread的子类或Runnable的实现类

的run方法中

因此,可以通过两种方式,定义类或者通过实现Runnable或者通过继承Thread来覆盖run方法,以便传给线程

让线程执行


 创建线程方式之一:继承Thread类


  步骤:
  1,定义类继承Thread类 该类复写Thread中的run方法将自定义的要该线程执行的代码存储在run方法。

  2,创建继承了Thread的子类对象
  3,调用Thread子类对象的start方法,启动线程,Java 虚拟机会调用该线程的 run 方法

  注意:多次start启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。

 
 创建线程方式之二:实现Runnable接口


  步骤:
  1,定义类实现Runnable接口
  2,覆盖Runnable接口中的run方法
        将线程要运行的代码放在run方法中

  3,   创建该实现类的对象
  4,将创建的实现类对象作为实参传给Thread的构造方法创建线程对象
  5,调用Thread类的对象的start方法 ,开启线程并自动调用Runnable接口子类的run方法

 

  • 实现方式与继承方式有什么区别

继承:线程代码存放在Thread子类run方法中
实现:线程代码存放在接口顶接口Runnable的实现类run方法中

实现方式的好处:避免了单继承的局限性,在定义线程时建议使用实现方式

  • 为什么要覆盖run方法?


  Thread 类用于描述线程


  创建Thread线程的时候会将实现了Runnable的类的对象传入Thread的构造方法中创建线程

或通过创建继承了Thread的类的对象来创建线程

 Thread的构造方法中,通过Thread对象调用start方法以启动线程,并自动调用run方法

调用run方法时会去调用被传入的Thread子类对象或Runnable实现类的对象的run方法

 
  如果将覆盖了Thread的run或 Runnable的run的类对象传入Thread 类中,则run方法调用

 传入的对象的run方法,如果没传入则该run方法不执行任何语句,所以需要创建子类让其
  覆盖Thread中的run方法或实现Runnable覆盖其中的run方法 , 那么当调用Thread线程

  的start方法时,虚拟机就会自动执行线程的run方法

 

  •   调用直接调用run方法和通过启动线程并调用start方法来调用run方法有什么不一样呢?


  直接执行run方法 那么run方法就是被主线程所调用了,主线程需要等待执行完毕该run方法
  的代码才能往下执行主线程中的其他内容, 而通过启动线程则可以同时执行run方法和主函数其他语句

 

 线程都有自己的默认名称


 Thread- 编号  编号从0开始
 可以通过线程的setName方法或者Thread的构造函数来自定义线程名称
 通过线程的getName方法来获取线程名称
 

  • 线程的一些常用操作:

 

 

longgetId() 返回该线程的标识符。线程 ID 是一个正的 long 数,在创建该线程时生成。线程 ID 是唯一的,并终生不变 ,除非线程终止
 StringgetName() 返回该线程的名称。
 intgetPriority() 返回线程的优先级。

 

 voidjoin() 等待该线程终止。
 voidjoin(long millis) 等待该线程终止的时间最长为 millis 毫秒。
 voidjoin(long millis, int nanos)  等待该线程终止的时间最长为millis 毫秒 +nanos 纳秒。

 

static booleanholdsLock(Object obj) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回true
 voidinterrupt()  中断线程。
static booleaninterrupted() 测试当前线程是否已经中断。
 booleanisAlive() 测试线程是否处于活动状态。
booleanisInterrupted()  测试线程是否已经中断。
 voidstart() 使该线程开始执行;Java 虚拟机调用该线程的run 方法。
String toString()  返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
static voidyield() 暂停当前正在执行的线程对象,并执行其他线程。

 

voidsetName(String name) 改变线程名称,使之与参数name 相同。
 voidsetPriority(int newPriority) 更改线程的优先级。

 

static voidsleep(long millis)  在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
static voidsleep(long millis, int nanos) 在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行)

 

多线程的安全问题


当方法或代码块在操作多线程共享的数据时,一个线程对该方法或代码块只执行到了一部分
还没执行完,另一个线程就参与进来执行,导致共享数据的错误

                       解决办法: 对操作共享数据的语句块或方法,只能一个线程执行完,其他线程才允许进来执行
                                          通过同步锁来实现

  • 同步锁

同步锁是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问。

一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。

    • synchronized 关键字

该关键字用于保护共享数据,可以为参与共享数据的操作的方法或语句块加锁,一个线程执行到加有锁的语

句块或方法时需要检查其他线程是否正在持有该锁 , 如果被其他线程持有说明其他线程正在执行该方法或

代码块,有的话需要等待其他线程\执行完毕才能进去

 

    • synchronized 可以为方法或语句块加锁


 1)方法声明时使用,放在方法修饰符(public等)之后,返回类型声明(void等)之前,多线程中,该方法只能
  被同一个线程执行该方法,其他线程要执行该方法需要排队等候,当前线程执行完毕才允许别的线程进来
  同步的方法控制了类对成员变量的访问,每个类实例对应一把锁,每个线程必须调用该方法的类实例才能执行
  否则线程阻塞,线程获得该锁后就不允许别的线程进入,直到方法结束才释放锁,别的阻塞线程获得锁后
  进入可执行状态同一时刻类中所有声明为 synchronized 的成员函数至多只有一个获得该类实例对应的锁并处于可执行状态,

 

 2)静态成员函数被声明为 synchronized 该方法控制对类的静态成员变量的访问,如果有线程进入则其他线程在该类的
  所有操作都不能进行,
  synchronized 方法的局限性:当线程类的run()方法被声明为 synchronized 时 那么该线程结束之前 所有其他该类
  其他的 synchronized 方法都不能被调用


 3)synchronized 块
  synchronized (对象)
  {
   需要被同步的代码块
  }
  这里的对象可以是类,类实例,局部变量
  该代码段不会被多个线程同时执行,获得当前对象锁的线程才能进入该代码块,
  优点: 可以针对任意代码块 可以指定任意对象作为锁,灵活性高


 synchronized(this)
     当线程访问同一个对象的一个synchronized(this)同步的代码块时,其他线程对该对象的synchronized(this)
     同步代码块都不能访问,因为同一时刻只有一个线程拿到该对象锁

    •  线程同步前提:

需要两个或以上的线程,必须多个线程使用同一个锁
必须保证同步中只能有一个线程在运行

  • 多线程同步的优缺点

 好处:解决多线程的安全问题
 弊端:多线程需要判断锁,消耗资源

 

    •  Lock

      • Lock 提供功能


   lock() 获取锁  如果锁不可用, 将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态。
 
   tryLock() 如果获取了锁立即返回值 true。如果锁被其他线程持有,将立即返回值 false。
              此用法可确保如果获取了锁,则会释放锁,如果未获取锁,则不会试图将其释放。


   tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,
       会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;


   lockInterruptibly():如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,
              或者当前线程被别的线程中断

  unlock() 释放锁。


    Condition newCondition()   返回绑定到此 Lock 实例的新 Condition 实例。
       
   一般的使用方式:
    Lock l = ...;
    l.lock();
    try {
     // access the resource protected by this lock
    } finally {
     l.unlock();//释放锁的动作一定要执行。
    }

    • synchronized与Lock和Condition
    • JDK 1.4以前出现 synchronized JDK1.5后加入了 Lock  是java.util.concurrent.locks包下的接口
    • Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作

Lock 能提供无条件的lock、不可中断的tryLock()、定时的tryLock(long, TimeUnit)、 可中断的lockInterruptibly()锁获取操作

Lock 类还可以提供死锁检测

Lock:替代了Synchronized 
   lock
   unlock
   newCondition()

  • synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,

           但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须

           以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。

  • Lock 接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁

 

  • synchronized 在代码执行时出现异常,JVM会自动释放锁定,不容易出错,但是使用Lock,要保证锁定一定会被释放,
     就必须将unLock()放到finally{}中人工释放

 

  • Condition:替代了Object 的 wait notify notifyAll      

      await(); 造成当前线程在接到信号或被中断之前一直处于等待状态。
      boolean await(long time, TimeUnit unit)  造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
      signal();唤醒一个等待线程。
      signalAll(); 唤醒所有等待线程。


 Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过
 将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock
 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

      • 在多线程竞争激烈的情况下 Lock 具有比 synchronized 更高的性能

 

 

  • 死锁

  多个线程在抢夺资源的过程中,一个或多个都处在相互等待某资源被释放的同时被阻塞状态叫死锁状态
  简单的说就是:线程死锁时,第一个线程等待第二个线程释放资源,而同时第二个线程又在等待第一个线程释放资源。
  导致死锁的原因是嵌套使用 synchronized 关键词来管理线程对共享资源的操作,
  
  看一个死锁的实例:


 

 class Res implements Runnable
{
 int sum = 0; //共享资源
 //该类有两个不同的对象锁
 Object lock_1 = new Object();
 Object lock_2 = new Object();

 public void run()
 {
  while (Thread.currentThread().getName().equals("Thread-0"))//线程Thread-0执行块
  {
   try
    {
     Thread.sleep(100);//开始线程Thread-0先等待, 优先让线程Thread-1执行另一代码段
    } catch (InterruptedException e)
    {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }

   synchronized (lock_1)//线程Thread-0可以加的外层锁
   { 
    /*
    当线程Thread-0没醒来前,线程Thread-1已经执行了一会
    当线程Thread-0醒来后,线程Thread-0进入这里时获得了lock_1锁
    此时线程Thread-1只持有lock_2锁,需要等待线程Thread-0释放lock_1锁
    但线程Thread-0却又要等待线程Thread-1释放lock_2锁,
    两个线程相互进入相互等待即阻塞状态,都无法获取对方的锁,从而无法继续往下执行
    导致死锁的产生,两个线程成了死锁线程
    */
    System.out.println(Thread.currentThread().getName()
      + "锁定lock_1");
    synchronized (lock_2)//线程Thread-0可以加的内层锁
    {
     System.out.println(Thread.currentThread().getName()
       + "锁定lock_2----" + sum++);
    }

   }
   System.out.println(Thread.currentThread().getName()
     + "释放lock_1和lock_2");

  }


  while (Thread.currentThread().getName().equals("Thread-1"))
  {
   synchronized (lock_2)
   {
    System.out.println(Thread.currentThread().getName()
      + "锁定lock_1");
    synchronized (lock_1)
    {
     System.out.println(Thread.currentThread().getName()
       + "锁定lock_2----" + sum++);
    }

   }
   System.out.println(Thread.currentThread().getName()
     + "释放lock_1和lock_2");

  }
 }

}

public class DeadLockDemo
{
 public static void main(String[] args)
 {
  Res t = new Res();

  Thread t1 = new Thread(t);
  Thread t2 = new Thread(t);
  t1.start();
  t2.start();
 }
}


 

  • 线程间通信 -- 等待唤醒机制

 

首先根据同步的两大前提

(1) 存在多线程操作同一资源

(2) 多线程使用同一把锁

只要是同一个对象即可不要求具体哪个对象

涉及到资源操作的语句或方法加锁,而且是同一把锁

 

 

先看简单的例子 :  两线程轮流执行两种对共享资源的操作,分别对共享资源的设置和获取值 , 对他们进行了同步

class Resource
{
	public String name ;
	public int age ;
}

class Setter implements Runnable
{
	Resource resource;
	Setter(Resource resource)
	{
		this.resource = resource;
	}
	public void run()
	{
		while(true)
		{
			synchronized(resource)
			{
				System.out.println("inputting information ...");
				resource.name = "张三";
				resource.age = 23;
			}
		}
	}
}

class Getter implements Runnable
{
	Resource resource;
	Getter(Resource resource)
	{	
		this.resource = resource;
	}
	public void run()
	{
		while(true)
		{
			synchronized(resource)
			{
				System.out.println("outputting information :"+resource.name+resource.age);
			}
		}
	}
}

public class SetGetDome
{
	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		Resource resource = new Resource();
		Setter setter = new Setter(resource);
		Getter getter = new Getter(resource);
		Thread set = new Thread(setter);
		Thread get = new Thread(getter);
		set.start();
		get.start();
	}

}


输出结果 :

outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
outputting information :张三23
inputting information ...
inputting information ...
inputting information ...
inputting information ...
inputting information ...
inputting information ...
inputting information ...
inputting information ...
inputting information ...
inputting information ...
inputting information ...
inputting information ...
inputting information ...
inputting information ...
inputting information ...
inputting information ...

 

分析: 该代码虽然满足了同步的条件,但没有满足设置于获取值轮流进行的条件

 

为了达到轮流执行任务的目的,引用等待唤醒机制

 


 

package demo1;

class Resource
{
	 String name ;
	 int age ;
	 boolean flag = false ;//加入判断标志
}

class Setter implements Runnable
{
	private Resource resource;
	Setter(Resource resource)
	{
		this.resource = resource;
	}
	public void run()
	{
		while(true)
		{
			synchronized(resource)
			{
				/*当前线程获得锁具备CPU执行权的前提下,每次执行之前先判断flag标志状态
				     当为真时就表明另外的get线程对同步代码的操作没有完毕,所以当前线程放弃CPU执
				     行权 ,通过wait使当前线程进入线程池等待,set线程因为进入线程池等待
				     而释放掉持有的锁,此时get线程已经被唤醒并且在夺得CPU的执行权的前提
				    下可以去获取锁,获取到锁之后,在开始执行同步代码块之前也要先判断标志
				    如为false则可以往下执行 	
				 	*/
				if(resource.flag)
					try
					{
						resource.wait();
					} catch (InterruptedException e)
					{
						e.printStackTrace();
					}
				System.out.println("inputting information ...");
				resource.name = "张三";
				resource.age = 23;
				
				resource.flag = true;//每次执行完任务之后都设置标志为相反状态
				resource.notify();
				/*到此set线程执行完任务,在退出同步代码块之前,唤醒等待池中get线程,get线程可以去争夺CPU执行权,
				    但还不能立马去执行操作同步代码块的任务,当抢夺到了CUP执行权之后,还要等待set线程释放同步锁,
				  因为set线程进入等待池释放了锁,get线程可以获取到锁,持有了锁就可以执行任务了
				    
				*/
			}
		}
	}
}

class Getter implements Runnable
{
	private Resource resource;
	Getter(Resource resource)
	{	
		this.resource = resource;
	}
	public void run()
	{
		while(true)
		{
			
			synchronized(resource)
			{
				if(resource.flag==false)
					try
					{
						resource.wait();
					} catch (InterruptedException e)
					{
						e.printStackTrace();
					}
				System.out.println("outputting information :"+resource.name+resource.age);
			
				resource.flag = false;
				resource.notify();
			}
		}
	}
}

public class SetGetDome
{
	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		Resource resource = new Resource();
		Setter setter = new Setter(resource);
		Getter getter = new Getter(resource);
		Thread set = new Thread(setter);
		Thread get = new Thread(getter);
		set.start();
		get.start();
	}

}


输出结果:

outputting information :张三23
inputting information ...
outputting information :张三23
inputting information ...
outputting information :张三23
inputting information ...
outputting information :张三23
inputting information ...
outputting information :张三23
inputting information ...
outputting information :张三23
inputting information ...
outputting information :张三23
inputting information ...
outputting information :张三23
inputting information ...
outputting information :张三23
inputting information ...
outputting information :张三23

 

 

 


1)JAVA中任何对象内部有两个等待队列:一个是wait等待队列,另一个是对象锁等待队列.

 

2)当用wait(timeout)等待时:第一步:释放锁,第二步:该线程进入该对象的wait等待队列之中.

当wait(timeout)时间已到时,该线程一定会醒来,从wait等待队列中移出来,此时(该线程已是运行态,

wait的任务已经完成了):该线程将试图再次获取对象的锁,(目的:从上次的wait的断点处,试图继续运行下去),

若锁获取成功,则从上次的wait的断点处,继续运行下去.若锁获取不成功,则该线程将进入该对象的锁等待队列去

等待对象锁了.这是两种不同的等待

情况1:如果当甲在等待的时间结束后,wait就返回。
     但由于同步的约束,
     甲如果这个时候获得了对象的锁,那么可以向下执行。获得锁的途径就是乙此时已经释放了对象锁。
     甲如果这个时候没有获得对象的锁,则一直在对象锁等待队列中等待,直到能获得乙释放的对象锁,得以向下继续。如果不能,就一直等待。
 
 情况2:如果当甲还没有到达等待的时间,
     而此时线程乙已经退出,并释放了对象的锁。
     如果这个时候,乙调用了锁的notify()方法,则甲不需要再去等待剩余的时间过去,直接醒来,继续向下。
 
    如果这个时候,乙并没有调用notify()方法,则甲就要等待剩余的时间过去,自己醒来。
    (因为这时候乙已经释放了锁,甲就得到了锁,可以向下。如果在甲wait剩余时间的时候,丙获得了锁,则甲还得等。)


   1、sleep()
   
    使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。
    也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据。注意该方法要捕捉异常。
    例如有两个线程同时执行(没有synchronized),一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,
    如果没有Sleep()方法,只有高优先级的线程执行完毕后,低优先级的线程才能够执行;
    但是高优先级的线程sleep(500)后,低优先级就有机会执行了。
    总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
    2、join()
    join()方法使调用该方法的线程在此之前执行完毕,也就是等待该方法的线程执行完毕后再往下继续执行。
    注意该方法也需要捕捉异常。
   
   
   
   3、yield()
   
    该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。
    
   4、wait()和notify()、notifyAll()
    这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用,要对持有监视器(锁)的线程操作,只有同步才有锁的概念。
    synchronized 关键字用于保护共享数据,阻止其他线程对共享数据的存取,
    但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?
    此时就用这三个方法来灵活控制。
    wait()方法使当前线程暂停执行并释放对象锁,让其他线程可以进入synchronized数据块,
    当前线程被放入对象等待池中。当其他线程调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,
    只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。
   
    notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。
   
    注意 这三个方法都是java.lang.Object的方法。

   5 , run和start()
   把需要线程执行的代码放到run()方法中,start()方法启动线程将自动调用run()方法,这个由java的内存机制规定的。
   并且run()方法必需是public访问权限,返回值类型为void。

wait()和notify(),notifyAll()是Object类的方法,sleep()和yield()是Thread类的方法。
    (1)、常用的wait方法有wait()和wait(long timeout);
    void wait() 在其他线程调用此对象的 notify() 方法或者 notifyAll()方法前,导致当前线程等待。
    void wait(long timeout)在其他线程调用此对象的notify() 方法 或者 notifyAll()方法,或者超过指定的时间量前,
    导致当前线程等待。
    wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其他shnchronized数据可被别的线程操作。
    wait()h和notify()因为会对对象的“锁标志”进行操作,所以他们必需在Synchronized函数或者
    synchronized 块 中进行调用。如果在非 synchronized 函数或 非-synchronized 块 中进行调用,
    虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。。

    (2)、Thread.sleep(long millis)必须带有一个时间参数。
    sleep(long)使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;
    sleep(long)可使优先级低的线程得到执行的机会,当然也可以让同优先级的线程有执行的机会;
    sleep(long)是不会释放锁标志的。
   (3)、yield()没有参数
    sleep 方法使当前运行中的线程睡眠一段时间,进入不可以运行状态,这段时间的长短是由程序设定的,
    yield方法使当前线程让出CPU占有权,但让出的时间是不可设定的。
    yield()也不会释放锁标志。
   
    实际上,yield()方法对应了如下操作;先检测当前是否有相同优先级的线程处于同可运行状态,如有,
    则把CPU的占有权交给次线程,否则继续运行原来的线程,所以yield()方法称为“退让”,
    它把运行机会让给了同等级的其他线程。
    sleep 方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,
    所以不可能让出较低优先级的线程此时获取CPU占有权。在一个运行系统中,如果较高优先级的线程
    没有调用sleep方法,也没有受到I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,
    方可有机会运行。
    yield()只是使当前线程重新回到可执行状态,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行,
    所以yield()方法只能使同优先级的线程有执行的机会。
 
   

    

 

  •  线程阻塞

      • 阻塞状态是正在运行的线程遇到某个特殊情况而暂停,以等待某个条件的触发。例如,延迟、挂起、等待I/O操作完成等。 进入阻塞状态的线程让出CPU,并暂时停止自己的执行。线程进入阻塞状态后,就一直等待,直到引起阻塞的原因被消除,线程又转入就绪状态,重新进入就绪队列排队。
      • 线程Thread类的方法
                                   join:阻塞调用线程直到某个线程终止时为止
                                          当A线程执行到B线程的join()方法时,A就会等待,直到B线程执行完毕,A线程才能继续执行
                                          join可以用来临时加入线程执行
    • 怎么结束线程


 调用一个线程的interrupt方法会把线程状态改为中断状态,但并没有终止线程,只是用于
 调用了sleep,wait,join方法而休眠的线程.使他们不再休眠同时抛出InterruptedException
 异常.
 stop方法由于不安全,已经过时.那么怎么停止线程呢?
 可以在线程中引入一个状态属性来控制,当需要停止线程时,只需要执行改变属性状态操作即可
 在线程的run()方法中循环的检查该属性值,为true时正常运行,为false时,退出循环,线程就会结束

 

后台线程(守护线程)
前提线程(主线程)

将一个线程设置为后台线程后 , 前提线程结束后台线程也会跟着结束

通过在线程启动前调用setDaemon(true)可以将线程设置为守护线程

 

四  、线程池

 

为什么使用线程池

减少刺激和销毁先的次数 , 每个工作线程都可以重复使用,可以执行多个任务

可以根据系统承受能力调整线程池中的工作线程的数目,防止过多线程导致

 

 

 ExecutorService中有一个execute方法,这个方法的参数是Runnable类型。也就是说,将一个实现了Runnable类型的类的实例作为参数传入execute方法并执行,那么线程就相应的执行了。

 

 

大接口

interface ThreadFactory ----线程工厂

interface Executor  ----唯一的方法 execute(Runnable command)

                     |--interface ExecutorService ----真正的线程池接口

                                                     |--abstract classAbstractExecutorService

                                                                                          |--classThreadPoolExecutor ----ExecutorService的默认实现。

                                                                                                                    |--classScheduledThreadPoolExecutor

                                                     |--interfaceScheduledExecutorService ----和Timer/TimerTask 类似 ,设置延时,定时执行任务

                                                                                          |--classScheduledThreadPoolExecutor  ----实现周期性任务调度

 

ExecutorService

 

用线程池启动定时器
调用ScheduledExecutorService的schedule方法,返回的ScheduleFuture对象可以取消任务。
支持间隔重复任务的定时方式,不直接支持绝对定时方式,需要转换成相对时间方式。

ScheduledExecutorService

 

三种调度方法

 

(1)立马执行任务,之后每次执行时间为上一次任务结束起向后推一个时间间隔

schedule(Runnable command, 

                   long delay,  // 从现在开始延迟执行的时间

                   TimeUnit unit)

 

(2)经过一定的时间之后开始首次执行,之后每次执行时间为上一次任务开始起向后推一个时间间隔

scheduleAtFixedRate(Runnable command,   //需要执行的任务

                                          long initialDelay, //首次执行任务的延迟时间

                                          long period,  //首次执行之后每次执行需要间隔的时间

                                         TimeUnit unit  //initialDelay 和 period 参数的时间单位

                                         )

执行特点:

initialDelay unit  之后开始第一次执行

initialDelay+period unit  之后第二次执行

initialDelay + 2 * period unit  之后第三次执行

...

initialDelay + (n-1) * period unit 之后第n次执行

(3)经过一定的时间之后开始首次执行,之后每次执行时间为上一次任务结束起向后推一个时间间隔

 

scheduleWithFixedDelay(Runnable command, long initialDelay,

                                          long delay,  //一次执行终止和下一次执行开始之间的延迟

                                         TimeUnit unit)

执行特点:

initialDelay unit  之后开始第一次执行

initialDelay+executeTime+delay, unit  之后第二次执行

initialDelay + 2 * executeTime+delayunit  之后第三次执行

...

initialDelay + ( n- 1) * executeTime+delay unit  之后第n次执行

其中executeTime为每次执行任务所消耗的时间


cache,fiexed,single三种池

创建固定大小的线程池
创建缓存线程池
创建单一线程池 

 

类 Executors

定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。

 

所有方法都是静态,利用该类可以直接获得ThreadFactory , ExecutorService , ScheduledExecutorService , Callable

ThreadFactory

               defaultThreadFactory()

               privilegedThreadFactory()

ExecutorService

               newCachedThreadPool()

               newCachedThreadPool(ThreadFactory threadFactory)

               newSingleThreadExecutor() 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。

               newSingleThreadExecutor(ThreadFactory threadFactory)

               newFixedThreadPool(int nThreads)     创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。

               newFixedThreadPool(int nThreads,ThreadFactory threadFactory)

ScheduledExecutorService

              newSingleThreadScheduledExecutor() 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
              newSingleThreadScheduledExecutor(ThreadFactory threadFactory) 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
              newScheduledThreadPool(int corePoolSize) 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
              newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
          

Callable

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值