java基础(五)

来源:互联网 发布:thriller mv 知乎 编辑:程序博客网 时间:2024/05/18 15:51

有两种方法可以获得自身的Class对象引用(对每一个被装载的类型(类或接口),虚拟机都会为它创建一个java.lang.Class的实例):

1) Class c = Class.forName(“com.briup.ch06.Student”); //虚拟机中没有该类的Class的实例对象

2) Class c = stu.getClass();                               //虚拟机已经存在Class的实例对象

注意:类和它所创建的所有对象通过反射获得的Class对象都是同一个.

反射可以让我们利用这个Class对象来获取和修改私有的变量和方法,不通过共有的方法去获得(原来我们例子都是通过一个public的方法(get)来设置和获取私有的变量),可以破坏数据的封装性。

常用到反射方式:

           1) 可以创建对象

           2) 可以访问对象中的属性

           3) 可以访问对象中的方法。

           4) 可以访问对象中的构造器。

反射机制通过在运行时探查字段和方法,从而可以帮助写出通用性很好的程序,这项能力对系统编程来说特别有用,但它并不适合于应用编程。

Book book = (Book)forName.newInstance();//调用无参构造器

Field field2 =forName.getDeclaredField("id");

field2.setAccessible(true);

field2.set(book, 20);//用反射设置私有属性

System.out.println(book.getId());

//Class.getDeclaredMethod(String name,Class<?>... parameterTypes)

Method declaredMethod =forName.getDeclaredMethod("getName",String.class,int.class);

declaredMethod.setAccessible(true);

declaredMethod.invoke(book,"hello",10);//用反射调方法

JDK5.0之后出现的新的特性:

三、加强的for循环

         JDK1.5之后提供了一种更简洁的遍历方法语法:

                     for(元素类型 变量名:数组/集合变量名) {

                          //.....

                     }

                     如果要遍历的对象是集合,那么

                     要求集合要求实现了Iterable接口

                     其实Collection接口已经继承了Iterable,

                     所以所有Collection接口的实现都可以用在增强的for循环中

                     还可以用增强for循环遍历数组

可变参数

JDK1.5引入了可变参数,一个方法可以接受任意个数的参数(但是必须是同一类型的),在接收时候使用数组来接收

语法: test(int... args) {}其实args表示的就是一个数组

不传递值时,接收的可变参数数组并不是null,而是一个长度为0的数组,可变参数可传多个参数

注意:如果方法存在多个参数,可变参数放在最后一个

泛型Generic

如果能够在构建类时候传入一个参数,类中某些变量的类型根据这个参数确定就能解决这些问题,泛型就是来解决这个问题的:

                       泛型可以使类中的属性的类型可以由外部决定

                       不需要强制类型转换

                       泛型可以在*编译期间*进行类型检查

                       提供了类型安全的操作

编译器的泛型兼容性检查:

I.泛型类型和原始类型的兼容性,原始类型可以引用一个泛型类型

                           eg:List list = new ArrayList<String>();

泛型类型可以引用一个原始类型的对象

                           eg:List<String> list = new ArrayList();

II.类型参数不同的泛型类型不能互相引用,不考虑类型参数间的继承性(和继承无关)

                        List<Object> l = newArrayList<String>(); //错误

枚举类型

JDK1.5增加了枚举类型,可以使用enum来定义

eg:

public enum Gender{

                  MALE,FEMALE;

}其中每一个枚举元素都是该枚举类型的一个实例

使用

Gender gender = Gender.MALE;//直接用类名遍历枚举类型中的对象

for(Gender s : Gender.values()) {

}

Enum类和enum关键字的区别

1.使用enum关键字定义的枚举类型,实际上就相当于定义了一个类,此类继承了java.lang.Enum类

2.每一个枚举值都是一个枚举类型的实例,它们被预设为publicstatic final

Enum类的只有一个受保护的构造方法protectdEnum(String name,int ordinal)

                        ordinal: 枚举元素的编号

实际上对于每一个枚举元素一旦声明之后,就表示自动调用此构造方法,所有的编号采用自动编号的方式进行(编号从0开始);

获得枚举类型对象的俩种方式:

第一种方式:类名.ValueOf(“对象名”),比较灵活

第二种方式:类名.对象(直接用类名调用),只能调用指定的对象

枚举类型可以有属性和方法

属性和方法必须定义在元素列表声明之后

枚举类型中定义构造方法

1.枚举的构造方法只能定义在元素列表之后

2.枚举类型的构造方法只能是private的,不写默认也是private

3.元素如果需要调用有参构造方法,则在元素后面加上"(参数)"

枚举隐式继承于Enum因此,无法再继承其他类

枚举实现接口

枚举可以实现接口,有以下两种方式

1.与普通class类一样,在枚举类中实现接口一样

2.枚举中单独实现接口中的抽象方法或者各个枚举元素对象中分别实现这个接口中的抽象方法

枚举中定义抽象方法

在一个枚举中可以定义多个抽象方法,但枚举中的每个元素必须分别实现这些抽象方法

所谓程序的健壮性,指程序在多数情况下能够正常运行,返回预期的正确结果;

Java异常处理机制具有以下优点:

. 把各种不同类型的异常情况进行分类,用Java类来表示异常情况,这种类被称为异常类。把异常情况表示成异常类,可以充分发挥类的可扩展和可重用的优势。

. 异常流程的代码和正常流程的代码分离,提高了程序的可读性,简化了程序的结构。

. 可以灵活地处理异常,如果当前方法有能力处理异常,就捕获并处理它,否则只需要抛出异常,由方法调用者来处理它。

1. 异常产生的条件或者称为异常情况。

     a. 整数相除运算中,分母为0;

     b. 通过一个没有指向任何具体对象的引用去访问对象的方法;

     c. 使用数组长度作为下标访问数组元素;

     d. 将一个引用强制转化成不相干的对象;

2. 异常会改变正常程序流程;

异常产生后,正常的程序流程被打破了,要么程序中止,要么程序被转向异常处理的语句;

3. 当一个异常的事件发生后,该异常被虚拟机封装形成异常对象抛出。

4. 用来负责处理异常的代码被称为异常处理器

5. 通过异常处理器来捕获异常

在Java语言中,用try...catch语句来捕获处理异常。格式如下:

try {

                可能会出现异常情况的代码;

   }catch(异常类型异常参数) {

                异常处理代码或者自定义异常

   }finally{

                一定要执行的代码

}

1. 如果try代码块中没有抛出异常,try代码块中语句会顺序执行完,catch代码块内容不会被执行;

2. 如果try代码块中抛出catch代码块所声明的异常类型对象,程序跳过try代码块中接下来代码,直接执行catch代码块中对应内容;

a. 可以存在多个catch代码块,究竟执行哪个,看抛出的异常对象是否是catch代码块中异常类型;

b. 异常只能被一个异常处理器所处理, 不能声明两个异常处理器处理相同类型的异常;

c. 多个catch语句块所声明的异常类型不能越来越小;

d. 不能捕获一个在try语句块中没有抛出的异常;

3. 如果try代码块中抛出catch代码块未声明的异常类型对象,异常被抛给调用者;哪个调用了这段语句块哪个负责处理这个异常;

finally语句: 任何情况下都必须执行的代码由于异常会强制中断正常流程,这会使得某些不管在任何情况下都必须执行的步骤被忽略,从而影响程序的健壮性。

异常调用栈

异常处理时所经过的一系列方法调用过程被称为异常调用栈。

1. 异常的传播

a. 异常情况发生后,发生异常所在的方法可以处理;

b. 异常所在的方法内部没有处理,该异常将被抛给该方法调用者,调用者可以处理;

c. 如调用者没有处理,异常将被继续抛出;如一直没有对异常处理,异常将被抛至虚拟机;

2. 如果异常没有被捕获,那么异常将使你的程序将被停止。

异常产生后,如果一直没有进行捕获处理,该异常被抛给虚拟机。程序将被终止。

3. 经常会使用的异常API

         getMessage:获得具体的异常出错信息,可能为null。

         printStatckTrace():打印异常在传播过程中所经过的一系列方法的信息,简称异常处理方法调用栈信息;在程序调试阶段,此方法可用于跟踪错误。

异常层级关系

所有异常类的祖先类为java.lang.Throwable类。它有两个直接的子类:

1. Error类:表示仅靠程序本身无法恢复的严重错误,比如内存空间不足,或者Java虚拟机的方法调用栈溢出。

2. Exception类:表示程序本身可以处理的异常。Exception还可以分为两种:运行时异常和编译时异常。

a. 运行时异常

RuntimeException类及其子类都被称为运行时异常,这种异常的特点是Java编译器不会检查它,如divide()方法的参数b为0, 执行a/b操作时会出现ArithmeticException异常,数组下标越界,空指针,它属于运行时异常,Java编译器不会检查它。

2) 深入解析

运行时异常表示无法让程序恢复运行的异常,导致这种异常的原因通常是由于执行了错误操作。一旦出现了错误操作,建议终止程序,因此Java编译器不检查这种异常。

运行时异常应该尽量避免。在程序调试阶段,遇到这种异常时,正确的做法是改进程序的设计和实现方式,修改程序中的错误,从而避免这种异常。捕获它并且使程序恢复运行并不是明智的办法。

3) 对比

与Error类相比:

相同点:i. Java编译器都不会检查它们;

ii.当程序运行时出现它们, 都会终止程序;

不同点:

i. Error类及其子类表示的错误通常是由Java虚拟机抛出的,在JDK中预定义了一些错误类,比如OutOfMemoryError和StackOutofMemoryError。RuntimeException表示程序代码中的错误;

ii.Error类一般不会扩展来创建用户自定义的错误类;RuntimeException是可以扩展的,用户可以根据特定的问题领域来创建相关的运行时异常类;

b. 受检查异常。

Exception类及其子类都属于受检查异常(CheckedException)。这种异常的特点是Java编译器会检查它,当程序中可能出现这类异常时,要么用try...catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

一些未检查的异常RuntimeException

1.java.lang.ArithmeticException算术异常      如:除0;

2.java.lang.NullPointerException  空指针引用如:没初始化一个References便使用;

3.java.lang.ArrayIndexoutofBoundsException数组越界       如:调用一个有十个元素的Array的第十一个元素的内容;

4.java.lang.ClassCastException强制类型转换异常

5.java.lang.NumberFormatException数据格式异常     如:Integer.parseInt("a");

6.java.lang.NegativeArraySizeException 数组长度为负数异常

异常声明和处理

1. 自己主动使用throw语句的时候代码会抛出异常;

2. 使用try-catch-finally语句结构处理或在方法声明上声明throws继续抛出;

异常处理语句的语法规则:

 1.try代码块不能脱离catch代码块或finally代码块而单独存在。try代码块后面至少有一个catch代码块或finally代码块。

 2.try代码块后面可以有零个或多个catch代码块,还可以有零个或至多一个finally代码块。如果catch代码块和finally代码块并存,finally代码块必须在catch代码块后面。

3. try代码块后面可以只跟finally代码块。

4. 在try代码块中定义的变量的作用域为try代码块,在catch代码块和finally代码块中不能访问该变量。

5. 当try代码块后面有多个catch代码块时,Java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,

如果异常对象为某个异常或其子类的实例,就执行这个catch代码块,而不会再执行其他的catch代码块。

6. 如果一个方法可能出现受检查异常,要么用try...catch语句捕获,要么用throws子句声明将它抛出。

7. throw语句后面不允许紧跟其它语句,因为这些语句永远不会被执行。

八. 编写/使用自己的异常类,

在特定的问题领域,可以通过扩展Exception类或RuntimeException类来创建自定义的异常。异常类包含了和异常相关的信息,这有助于负责捕获异常的catch代码块,正确地分析并处理异常。

如果程序中有太多的检查,程序的运行就会慢好多。如果在测试阶段会有这种检查,而在发布阶段能自动删除这些东西。该多好!这就是断言机制。

断言使用

在JDK1.4中,Java语言引入一个新的关键字: assert。该关键字有两种形式:

assert 条件

以及

assert 条件:表达式

这两种形式都会对条件进行评估,如果结果为假则抛出AssertionError。在第二种形式中,表达式会传入AssertionError的构造器并转成一个消息字符串。表达式字符串部分的唯一目的就是生成一个消息字符串。AssertionError对象并不存储表达式的值,因此你不可能在以后获取它。

要断言x不是负数,只需要使用如下简单的语句:

2. 断言内容代码编译

因为assert是一个新的关键字,因此在使用时需要告诉编译器你编译所使用jdk的版本号。

javac -source 1.4 MyClass.java,在jdk的后续版本中,对断言的支持成为默认特性(我们使用的是JDK5.0以上,使用不需要使用这个编译,默认就支持的)。

3. 断言内容代码执行

默认情况下,断言是关闭的。要通过-enableassertions或者-ea选项来运行程序以打开断言:

java -enableassertions com.briup.ch07.Xxxx

java -ea com.briup.ch07.Xxxx

打开或关闭断言是类装载器的功能。当断言功能被关闭时,类装载器会跳过那些和断言相关的代码,因此不会降低程序运行速度。

注意:使用eclipse运行代码的时候也是可以传参数的(包括俩种参数)

java -xx com.briup.ch07.Test yy

xx是给JVM传的参数  yy是给Test类的main方法传的参数

一. 什么是线程:

进程是指运行中的应用程序,每一个进程都有自己独立的内存空间。一个应用程序可以同时启动多个进程。

线程是指进程中的一个执行流程。一个进程可以由多个线程组成。即在一个进程中可以同时运行多个不同的线程,它们分别执行不同的任务,当进程内的多个线程同时运行时,这种运行方式称为并发运行。

线程与进程的主要区别在于:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中工作,这些线程可以共享同一块内存和系统资源。比如共享一个对象或者共享已经打开的一个文件。

二. java中的线程

在java虚拟机进程中,执行程序代码的任务是由线程来完成的。每当用java命令启动一个Java虚拟机进程时,Java虚拟机都会创建一个主线程。该线程从程序入口main()方法开始执行。

计算机中机器指令的真正执行者是CPU,线程必须获得CPU的使用权,才能执行一条指令。

java中大致可以把线程分为前台线程(执行线程)、后台线程(守护线程或者叫精灵线程)

三. 线程的创建和启动

前面我们提到Java虚拟机的主线程,它从启动类的main()方法开始运行。此外,用户还可以创建自己的线程,它将和主线程并发运行。创建线程有两种方式,如下:

. 扩展java.lang.Thread类;

. 实现Runnable接口;

 

Thread类代表线程类,它的最主要的两个方法是:

. run()——包含线程运行时所执行的代码;

. start()——用于启动线程;

用户的线程类只需要继承Thread类,覆盖Thread类的run()方法即可。在Thread类中,run()方法的定义如下:

 public void run();    //没有抛异常,所以子类重写亦不能抛异常

1) 主线程与用户自定义的线程并发运行

a. Thread类的run()方法是专门被自身的线程执行的,主线程不能调用Thread类的run()方法,否则违背了Thread类提供run()方法的初衷;

b. Thread thread =Thread.currentThread();       返回当前正在执行这行代码的线程引用;

String name = thread.getName();               获得线程名字;

每个线程都有默认名字,主线程默认的名字为main, 用户创建的第一个线程的默认名字为"Thread-0",第二个线程的默认名字为"Thread-1",依引类推。Thread类的setName()方法可以显示地设置线程的名字;

2) 多个线程可以共享同一个对象的实例变量。

3) 不要覆盖Thread类的start()方法

创建了一个线程对象,线程并不自动开始运行,必须调用它自己的start()方法。

4) 一个线程只能被启动一次,//多次启动抛出IllegalThreadStateException异常

实现Runnable接口

Java不允许一个类继承多个类,因此一旦一个类继承了Thread类,就不能再继承其他的类。为了解决这一问题,Java提供了java.lang.Runnable接口,它有一个run()方法,定义如下:

public void run();

实现接口的类对象必须被包装成thread类对象才能start.

线程状态

1. 新建状态(New)

用new语句创建的线程对象处于新建状态,此时它和其他Java对象一样;仅在堆区中被分配了内存;

2. 就绪状态(Runnable)

当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,处于这个状态的线程位于可运行池中,等待获得CPU的使用权。

3. 运行状态(Running)

处于这个状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只有一个CPU, 那么任何时刻只会有一个线程处于这个状态。如果计算机有多个CPU, 那么同一时刻可以让几个线程占用不同的CPU,使它们都处于运行状态。只有处于就绪状态的线程才有机会转到运行状态。

4. 阻塞状态(Blocked)

指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态。

阻塞状态可分为三种:

. 位于对象等待池中的阻塞状态(Blockedin objects' wait pool): 运行状态时,执行某个对象的wait()方法;

. 位于对象锁池中的阻塞状态(Blockedin object's lock pool): 当线程处于运行状态,试图获得某个对象的同步锁时,如该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中;

. 其他阻塞状态(OtherwiseBlocked): 当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态。

当一个线程执行System.in.read()方法时,就会发出一个I/O请求,该线程放弃cpu, 进入阻塞状态,直到I/O处理完毕,该线程才会恢复运行。

5. 死亡状态(Dead)

当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。线程有可能是正常执行完run()方法退出,也有可能是遇到异常而退出。不管该线程正常结束还是异常结束,都不会对其他线程造成影响。

五. 线程调度

计算机通常只有一个CPU, 在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。在可运行池中,会有多个处于就绪状态的线程在等待CPU,Java虚拟机的一项任务就是负责线程的调度。线程的调度是指按照特定的机制为多个线程分配CPU 的使用权。有两种调度模型:

. 分时调度模型:让所有线程轮流获得CPU的使用权,并且平均分配每个线程占用CPU的时间片。

. 抢占式调度模型:优先让可运行池中优先级高的线程较多可能占用CPU(概率高),如果可运行池中线程的优先级相同,那么就随机选择一个线程,使其占用CPU。处于可运行状态的线程会一直运行,直至它不得不放弃CPU。Java虚拟机采用这种。

一个线程会因为以下原因而放弃CPU:

. Java虚拟机让当前线程暂时放弃CPU,转到就绪状态;

. 当前线程因为某些原因而进入阻塞状态;

. 线程运行结束;

线程的调度不是跨平台的,它不仅取决于Java虚拟机,还依赖于操作系统。在某些操作系统中,只要运行中的线程没有阻塞,就不会放弃CPU;在某些操作系统中,即使运行中的线程没有遇到阻塞,也会在运行一段时间后放弃CPU,给其他线程运行机会。

1. stop

Thread类的stop()方法可以强制终止一个线程,但从JDK1.2开始废弃了stop()方法。在实际编程中,一般是在受控制的线程中定义一个标志变量,其他线程通过改变标志变量的值,来控制线程的自然终止、暂停及恢复运行。

2. isAlive:

final boolean isAlive():判定某个线程是否是活着的(该线程如果处于可运行状态、运行状态和阻塞状态、对象等待队列和对象的锁池中返回true)

3. Thread.sleep(5000);

放弃CPU, 转到阻塞状态。当结束睡眠后,首先转到就绪状态,如有其它线程在运行,不一定运行,而是在可运行池中等待获得CPU。线程在睡眠时如果被中断,就会收到一个InterrupedException异常,线程跳到异常处理代码块。

4. void sleepingThread.interrupt():

中断某个线程

5. boolean otherThread.isInterrupted():

测试某个线程是否被中断,与static boolean interrupted()不同,对它的调用不会改变该线程的“中断”状态。

6. public void join();

 public void join(long timeout);

挂起当前线程(一般是主线程),直至它所调用的线程终止才被运行。线程A中调用线程B.join(),是使A线程阻塞,因为是A线程调用的B.join()这个方法谁调用谁阻塞。

线程的同步

多个线程在操纵共享资源——实例变量时,有可能引起共享资源的况争。为了保证每个线程能正常执行操作,保证共享资源能正常访问和修改。Java引入了同步进制,具体做法是在有可能引起共享资源竞争的代码前加上synchronized标记。这样的代码被称为同步代码块。

每个Java对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。当一个线程试图执行带有synchronized标记的代码块时,该线程必须首先获得this关键字引用的对象的锁。

. 如果这个锁已经被其他线程占用,Java虚拟机就会把这个线程放到this指定对象的锁池中,线程进入阻塞状态。在对象的锁池中可能会有许多等待锁的线程。等到其他线程释放了锁,Java虚拟机会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。

. 假如这个锁没有被其他线程占用,线程就会获得这把锁,开始执行同步代码块。在一般情况下,线程只有执行完同步代码块,才会释放锁,使得其他线程能够获得锁。

如果一个方法中的所有代码都属于同步代码,则可以直接在方法前用synchronized修饰。

           public synchronizedString pop(){...}

      等价于

           public String pop(){

                   synchronized(this){...}

           }

线程同步的特征:

1. 如果一个同步代码块和非同步代码块同时操纵共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他线程仍然可以执行对象的非同步代码块。

2. 每个对象都有唯一的同步锁。

3. 在静态方法前面也可以使用synchronized修饰符。此时该同步锁的对象为类对象(类的Class对象)。

4. 当一个线程开始执行同步代码块时,并不意味着必须以不中断的方式运行。进入同步代码块的线程也可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并没有释放锁,只是把运行机会(即CPU)让给了其他的线程。

 5.synchnozied声明不会被继承。

同步是解决共享资源竞争的有效手段。当一个线程已经在操纵共享资源时,其他共享线程只能等待。为了提升并发性能,应该使同步代码块中包含尽可能少的操作,使得一个线程能尽快释放锁,减少其他线程等待锁的时间。

线程的通信

锁对象.wait(): 执行该方法的线程释放对象的锁,Java虚拟机把该线程放到该对象的等待池中。该线程等待其它线程将它唤醒;

锁对象.notify(): 执行该方法的线程唤醒在对象的等待池中等待的一个线程。Java虚拟机从对象的等待池中随机选择一个线程,把它转到对象的锁池中。如果对象的等待池中没有任何线程,那么notify()方法什么也不做。

锁对象.notifyAll():会把对象的等待池中的所有线程都转到对象的锁池中。

注意:notify notifyAll只会唤醒等待池中等待同一个锁对象的线程,因为同一个时刻在等到池中可能会有多个线程,而这多个线程可能是在等待不同的锁对象

八. 线程的死锁

 

A线程等待B线程持有的锁,而B线程正在等待A持有的锁;

九. 线程让步

Thread.yield()静态方法,如果此时具有相同优先级的其他线程处于就绪状态,那么yield()方法将把当前运行的线程放到可运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。

sleep()和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程。区别:

. sleep()不考虑其他线程优先级; yield()只会给相同优先级或者更高优先级的线程一个运行的机会。

. sleep()转到阻塞状态; yield()转到就绪状态;

. sleep()会抛出InterruptedException异常, yield()不抛任何异常

. sleep()比yield方法具有更好的可移植性。对于大多数程序员来说,yield()方法的唯一用途是在测试期间人为地提高程序的并发性能,以帮助发现一些隐藏的错误,所以yield()并不常用。

调整线程优先级

注意:优先级高的线程只能获得较多运行的概率,但是实际中不一定真的有效果 

线程优先级的使用原则与操作系统有着密切的联系因此在JAVA中的线程的调度是完全受其所运行平台的操作系统的线程调度程序控制的。所有虽然我们可以设置线程的优先级但是在运行的时候不一定能够确切的体现出来。所有处于就绪状态的线程根据优先级存放在可运行池中,优先级低的线程获得较少的运行机会,优先级高的线程获得较多的运行机会。Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。优先级用整数来表示,取值范围是1-10,Thread类有以下3个静态常量。

. MAX_PRIORITY: 10, 最高;

. MIN_PRIORITY: 1, 最低;

. NORM_PRIORITY: 5, 默认优先级;

其它:stop(): 中止线程运行;已过时

resume(): 使暂停线程恢复运行   已过时

suspend(): 暂停线程,不释放锁; 已过时

释放对象的锁:

. 执行完同步代码块;

. 执行同步代码块过程中,遇到异常而导致线程终止,释放锁;

. 执行同步代码块过程中,执行了锁所属对象的wait()方法,释放锁进入对象的等待池;

线程不释放锁:

. Thread.sleep()方法,放弃CPU,进入阻塞状态;

. Thread.yield()方法,放弃CPU,进入就绪状态;

. suspend()方法,暂停当前线程,已过时;

关于interrupt、isInterrupted、interrupted三个方法的理解

interrupt和isInterrupted 是Thread类中的非静态方法可以用线程对象来访问t.interrupt()    t.isInterrupted()

interrupted 是Thread类中的静态方法  可以用类名来访问Thread.interrupted()

interrupt 可以用来中断线程

interrupted isInterrupted 可以用来判断线程是否被中断过

一个线程A是不可能调用线程B的interrupt方法来中断B线程的运行, 因为线程A调用线程B的interrupt方法的时候,线程A肯定是在使用cup运行这个中断方法的代码,这个时候线程B那就肯定没有在使用CPU运行代码了(同一时刻值只可能有一个线程使用cup在运行代码),所以就谈不上说线程A调用线程B的interrupt方法来把正在运行的线程B给中断了这里你要明白另外一个事情,那就是一个线程在使用cpu运行的时候,可以自己调用interrupt来中断自己,就是自己让自己立马交出cpu的使用权,然后回到线程的就绪状态。

既然线程A不能调用线程B的interrupt方法来中断线程B,那么我们在线程A中真的调用了线程B的interrupt方法的话,这个操作具有什么意义呢?

这个时候我们要了解另外一个事情,那就是每一个线程对象中有一个特别的标记,那就是可以标记出这个线程有没有被其他线程进行尝试中断过,因为线程A中是可以调用线程B的interrupt方法来尝试中断线程B的,虽然这样的中断并不能起真正的中断作用(这个上面已说明过了),但是可以把线程B对象中的按个标识从false改为true,表示在某个情况满足下有其他线程想中断你了。

那么也就是说我们可以在线程A中通知线程B,我在某个条件满足的情况下至少尝试着调用你的interrupt方法去中断你了,然后这个时候线程B在运行的时候过程中就可以通过另外俩个方法isInterrupted和 interrupted来知道是否有其他线程在试图中断自己了,然后就是线程B根据这个信息,来自己决定是否要中断自己(自己可以调用自己的 interrupted方法来中断自己),或者不理不睬这样的信息通知而去继续运行下去,具体是要进行那种选择,那么就要看你到时候的具体的业务逻辑需求了。

最后还有一个问题,那就是为什么线程在wait或者sleep期间被中断时候会抛出InterruptedException呢?

因为处于这些状态的线程是不可能拿到cup的使用权的,那么就意味着即使我调用你的interrupt方法去尝试的通知你说:"我试图在中断你",那么你也是不可能拿到cup的使用权去处理的我的这种中断的通知信息的,所以这时候就会抛出异常了(当然我们也可以利用这特点来改变一个阻塞状态线程的状态)。

当然这里所描述的一些细节情况可能学生们不能马上理解,因为它的实际用途和应用需要在具体业务逻辑业务中才能体现出其真正的作用和意义

流的概念

程序的主要任务是操纵数据。在Java中,把一组有序的数据序列称为流。根据操作的方向,可以把流分为输入流和输出流两种。程序从输入流读取数据,向输出流写出数据。

文件   输入流                     输出流           文件

           内存 ------------->  Java程序------------------>  内存

           键盘                                              控制台

            |                                                 |

           数据源                                            数据目的地

Java I/O系统负责处理程序的输入和输出,I/O类库位于java.io包中,它对各种常见的输入流和输出流进行了抽象。如果数据流中最小的数据单元是字节,那么称这种流为字节流;如果数据流中最小的数据单元是字符,那么称这种流为字符流。在I/O类库中,java.io.InputStream和java.io.OutputStream分别表示字节输入流和字节输出流,java.io.Reader和java.io.Writer分别表示字符输入流和字符输出流。

注意:它们四个都是抽象类

字节输入流和输出流概述

在java.io包中,java.io.InputStream表示字节输入流,java.io.OutputStream表示字节输出流,它们都是抽象类,不能被实例化。

InputStream类提供了一系列和读取数据有关的方法:

1. read(): 从输入流读取数据:有三种重载形式: 

 

a. int read(): 从输入流读取一个8位的字节(1字节是8位),把它转换为0-255之间的整数,并返回这一整数。如果遇到输入流的结尾,则返回-1;

b. int read(byte[] b): 从输入流读取若干个字节,把它们保存到参数b指定的字节数组中。返回的整数表示读取的字节数。如果遇到输入流的结尾,则返回-1;

c. int read(byte[] b, int off, int len): 从输入流读取若干个字节,把它们保存到参数b指定的字节数组中。返回的整数表示读取的字节数。参数off指定在字节数组中开始保存数据的起始下标,参数len指定读取的字节数目。返回的整数表示实现读取的字节数。如果遇到输入流的结尾,则返回-1;

以上第一个read方法从输入流读取一个字节,而其余两个read方法从输入流批量读取若干字节。在从文件或键盘读数据时,采用后面两个read方法可以减少进行物理读文件或键盘的次数,因此能提高I/O操作的效率。

2. void close(): 关闭输入流,InputStream类本身的close()方法不执行任何操作。它的一些子类覆盖了close()方法,在close()方法中释放和流有关的系统资源。

3. int available(): 返回可以从输入流中读取的字节数目;

4. skip(long): 从输入流中跳过参数n指定数目的字节。

5. boolean markSupported(),voidmark(int),void reset(): 如果要从流中重复读入数据,先用markSupported()方法来判断这个流是否支持重复读入数据,如果返回true,则表明可以在流上设置标记。接下来调用mark(int readLimit) 方法从流的当前位置开始设置标记。最后调用reset()方法,该方法使输入流重新定位到刚才做了标记的起始位置。这样就可以重复读取做过标记的数据了。

OuputStream类提供了一系列和写数据有关的方法:

1. write(): 向输出流写入数据:有三种重载形式:

a. void write(int b): 向输出流写入一个字节;

b. void write(byte[] b): 把参数b指定的字节数组中的所有字节写到输出流;

c. void write(byte[] b, int off, int len): 把参数b指定的字节数组中的所有字节写到输出流,参数off指定字节数组的起始下标,从这个位置开始输出由参数len指定数目的字节;以上第一个write方法从输出流写入一个字节,而其余两个write方法从输出流批量写出若干字节。在向文件或控制台写数据时,采用后面两个write方法可以减少进行物理读文件或键盘的次数,因此能提高I/O操作的效率。

2. void close(): 关闭输出流,OutputStream类本身的close()方法不执行任何操作。它的一些子类覆盖了close()方法,在close()方法中释放和流有关的系统资源。

3. void flush(): OutputStream类本身的flush()方法不执行任何操作,它的一些带有缓冲区的子类(比如BufferedOutputStream和PrintStream类)覆盖了flush()方法。通过带缓冲区的输出流写数据时,数据先保存在缓冲区中,积累到一定程度才会真正写到输出流中。缓冲区通常用字节数组实现,实际上是指一块内存空间。flush()方法强制把缓冲区内的数据写到输出流中。

三. 常用到的字节输入流和输出流

            in:输入流

           1. ByteArrayInputStream:    读取byte类型的数组中的数据

           2. FileInputStream:         从文件中读取数据;

           3. PipedInputStream:        (管道流)连接一个PipedOutputStream;

           4. ObjectInputStream:       对象输入流;

           5. StringBufferInputStream: 可以读取一个字符串,在API中已经过时

           out: 输出流

           6.ByteArrayOutputStream:

           7.FileOutputStream

                      8.PipedoutputStream:

                      9.ObjectoutputStream

四. BufferedInputStream类

BufferedInputStream类覆盖了被过滤的输入流的读数据行为,利用缓冲区来提高读数据的效率。BufferedInputStream类先把一批数据读入到缓冲区,接下来 read()方法只需要从缓冲区内获取数据,就能减少物理性读取数据的次数。

. BufferedInputStream(InputStream in)——参数in指定需要被过滤的输入流。

. BufferedInputStream(InputStream in, intsize)——参数in指定需要被过滤的输入流。参数size指定缓冲区的大小,以字节为单位。

DataInputStream 类

DataInputStream 实现了DataInput接口,用于读取基本类型数据,如int, float, long, double和boolean等。

. readByte()——从输入流中读取1个字节,指它转换为byte类型的数据;

. readLong()——从输入流中读取8个字节,指它转换为long类型的数据;

. readFloat()——从输入流中读取4个字节,指它转换为float类型的数据;

. readUTF()—— 从输入流中读取1到3个字节,指它转换为UTF-8字符编码的字符串;

六. 管道输入类:PipedInputStream 类

管道输入流从一个管理输出流中读取数据。通常由一个线程向管理输出流写数据,由另一个线程从管理输入流中读取数据,两个线程可以用管理来通信。

七. Reader and Writer概述

InputStream和OutputStream类处理的是字节流,也就是说,数据流中的最小单元为一个字节,它包括8个二进制位。在许多应用场合,Java应用程序需要读写文本文件。在文本文件中存放了采用特定字符编码的字符。为了便于读于各种字符编码的字符,java.io包中提供了Reader/Writer类,它们分别表示字符输入流和字符输出流。

在处理字符流时,最主要的问题是进行字符编码的转换。Java语言采用Unicode字符编码。对于每一个字符,Java虚拟机会为其分配两个字节的内存。而在文本文件中,字符有可能采用其他类型的编码,比如GBK和UTF-8字符编码等。

Reader类能够将输入流中采用其他编码类型的字符转换为Unicode字符,然后在内存中为这些Unicode字符分配内存。Writer类能够把内存中的Unicode字符转换为其他编码类型的字符,再写到输出流中。

在默认情况下,Reader和Writer会在本地平台的字符编码和Unicode字符编码之间进行编码转换。

                            Writer的write()方法

使用Unicode字符编码的字---------------------------------------->   使用本地平台的字符编码的字符串

符串(内存中)  <---------------------------------------    (数据源/数据目的地) Reader的read()方法

如果要输入或输出采用特定类型编码的字符串,可以使用InputStreamReader类和OutputStreamWriter类。在它们的构造方法中可以指定输入流或输出流的字符编码。

                             OutputStreamWriter的write()方法

使用Unicode字符编码的字     ---------------------------------------->   使用本地平台的字符编码的字符串

符串(内存中) <--------------------------------------- (数据源/数据目的地)InputStreamReader的read()方法

由于Reader和Writer采用了字符编码转换技术,Java I/O系统能够正确地访问采用各种字符编码的文本文件,另一方面,在为字符分配内存时,虚拟机对字符统一采用Unicode字符编码,因此Java程序处理字符具有平台独立性。

CharArrayReader        : 把字符数组转换为Reader,从字符数组中读取字符;

BufferedReader         : 过滤器,为其他Reader提供读缓冲区,此外,它的readLine()方法能够读入一行字符串;

StringReader           : 把字符串转换为Reader,从字符串中读取字符;

PipedReader            : 连接一个PipedWriter;

PushBackReader         : 能把读到的字符压回到缓冲区中,通常用做编译器的扫描器,在程序中一般很少使用它。

InputStreamReader      : 过滤器,把InputStream转换为Reader,可以指定字符编码;

FileReader             : 从文件中读取字符;

InputStreamReader类 

InputStreamReader类把InputStream类型转换为Reader类型,构造方法:

.InputStreamReader(InputStream in): 按照本地平台的字符编码读取输入流中的字符;

.InputStreamReader(InputStream in, String charsetName): 按照指定的字符编码读取输入流中的字符;

InputStreamReader的一个子类,用于从文件中读取字符数据。该类只能按照本地平台的字符编码来读取数据,用户不能指定其他字符编码类型。

. FileReader(File file):   参数file指定需要读取的文件;

. FileReader(String name): 参数name指定需要读取的文件的路径;

BufferedReader类

Reader类的read()方法每次都从数据源读入一个字符,BufferedReader带有缓冲区,它可以先把一批数据读到缓冲区内,接下来的操作都从缓冲区内获取数据,避免每次都从数据源读取数据并进行字符编码转换,从而提高读操作的效率。

BufferedReader的readLine()方法可以一次读入一行字符,以字符形式返回。

. BufferedReader(Reader in): 指定被修饰的Reader类;

. BufferedReader(Reader in, int sz): 参数in指定被装饰的Reader类,参数sz指定缓冲区的大小,以字符为单位。

File类

File类提供管理文件或目录的方法。File实例表示真实文件系统中的一个文件或者目录。

1. 构造方法

. File(String pathname): 参数pathname表示文件路径或者目录路径;

. File(String parent, String child): 参数parent表示根路径,参数child表示子路径;

. File(File parent, String child): 参数parent表示根路径,参数child表示子路径;

只处理一个文件,使用第一个构造方法;如处理一个公共目录的若干子目录或文件,那么使用第二个或者第三个更方便。

. boolean createNewFile():创建一个新的文件,如果文件已经存在,则创建失败(返回false),否则创建成功(返回true)。

. boolean delete():删除文件或者空目录

. boolean mkdir()/mkdirs():创建一个或者多个目录(连续创建)->如果该目录的父目录不存在话,那么还会创建所有的父目录;

. boolean renameTo(File destination):文件的改名

. boolean canRead()/canWrite():判断指定的文件是否能读取或者写入数据

. boolean exists():判断指定的文件或者目录是否存在

. String[] list():返回指定目录下所有文件名或者子目录名所组成的字符串数组

. long lastModified():返回指定文件最后一次被修改的时间(从1970年1月1日凌晨12点到这个文件的修改时间之间所经历的毫秒数)

. String getPath()/getAbsolutePath():返回指定文件或者目录的路径和绝对路径

. String getCanonicalPath(): 获取该File对象所代表的文件或者目录的正规路径

. String getParent()/getName():返回指定文件或者目录的父目录(没有返回null)和名字

1. 当创建一个FileInputStream对象的时候,文件必须存在以及是可读的FileInputStream(File file)

FileInputStream(String name)

2. 当创建一个FileOutputStream对象的时候,可以创建一个新的文件,也可以覆盖一个已经存在的同名文件。

FileOutputStream(File file)

FileOutputStream(File file, boolean append)       

如果要创建的文件已经存在,可以选择向旧文件添加新的内容(append为true)或者新的内容覆盖旧文件的内容(append为false)。

FileReader and FileWriter  

1. 读写字符文件方便

2. 构造器

FileReader(File file)

FileReader(String name)

FileWriter(File file)

FileWriter(String filename)

PrintWriter

可以输出基本数据类型、对象、字符(字符数组)和字符串,但是不能输出字节流。

PrintWriter类可以代替桥和BufferedWriter

PrintStream类既可以输出字节流也可以输出字符流或者字符串

Reading and Writing with RandomAccessFile

1. 实现数据的输入和输出

2. 用它能够提供读写文件的功能

3. 提供通过一个文件指针从文件的某一个断点开始读写数据的功能

4. 构造器

RandomAccessFile(File file, String mode)

RandomAccessFile(String filename, Stringmode)

mode可以选择读、写和读写的方式

5. 方法

read()/write()    seek(long pointer) 定位到文件的断点

对象的序列化和反序列化

对象的序列化:  把对象写到一个输出流;

对象的反序列化:从一个输入流中读取一个对象;

1. 对象的持久化

2. 仅仅是一个对象的数据被序列化(将对象的数据序列化成字节流)

3. 标识为transit的数据不能被序列化 例如:transit 类名 表示该类不能被序列化 或者transit 字段

4. 要序列化的对象必须实现java.io.Serializable接口

对象的序列化主要用于:

1. 网络中传输的是字节流的数据,网络中对象的传输,是将对象的数据经过序列化后转换成字节流。

2. 将对象数据序列化到文件中,将对象数据转换成字节流存储到文件中。从文件中读取字节流数据并转换成对象叫做对象的反序列化。

 

 ObjectInputStream 和ObjectOutputStream(对象输入和输出流,可以读写基本数据类型和对象)

1. ObjectInputStream 和ObjectOutputStream为应用程序提供对象的持久化存储

2. 一个ObjectInputStream 可以反序列化通过ObjectOutputStream写入的基本数据类型和对象

3. 构造器

ObjectInputStream(InputStream in)

ObjectOutputStream(OutputStream out)

4. 方法

readObject()/writeObject()  将对象写入到输出流中或者从输入流中读取对象

网络

1、计算机网络

计算机网络是相互连接的独立自主的计算机的集合,

最简单的网络形式由两台计算机组成。

2、网络通信

IP地址:

1)IP网络中每台主机都必须有一个惟一的IP地址;

2)IP地址是一个逻辑地址;

3)因特网上的IP地址具有全球唯一性;

4)32位,4个字节,常用点分十进制的格式表示,例如:192.168.0.16。

协议:

1)为进行网络中的数据交换(通信)而建立的规则、标准或约定;(=语义+语法+规则) ;

2)不同层具有各自不同的协议。

端口号:

端口使用一个16位的数字来表示,它的范围是0--65535,1024以下的端口号保留给预定义的服务。例如:http使用80端口。

3、OSI(Open SystemInterconnection)参考模型

物理层:二进制传输,确定如何在通信信道上传递比特流;

数据链路层:加强物理层的传输功能,建立一条无差错的传输线路;

网络层:在网络中数据到达目的地有很多线路,网络层就是负责找出最佳的传输线路;

传输层:传输层为源端计算机到目的端计算机提供可靠的数据传输服务,隔离网络的上下层协议,使得上层网络应用的协议与下层无关;

会话层:在两个相互通信的应用进程之间建立、组织和协调其相互之间的通信;

表示层:处理被传送数据的表示问题,也就是信息的语法和语义,如有必要将使用一种通用的格式在多种格式中进行转换;

应用层:为用户的应用程序提供网络通信服务;

OSI(Open System Interconnection)参考模型并不是物理实体上存在这七层,这只是功能的划分,是一个抽象的参考模型。进行网络通信时,每层提供本层对应的功能;

1)通信实体的对等层之间不允许直接通信,它们之间是虚拟通信,实际通信在最底层完成;

2)各层之间是严格单向依赖;

3)上层使用下层提供的服务 — Service user;

4)下层向上层提供服务 — Service provider。

5)对等层实体之间虚拟通信;

6)下层向上层提供服务,实际通信在最底层完成。

6、OSI各层所使用的协议

1)应用层:远程登录协议Telnet、文件传输协议FTP(网上下载一个软件或者资料的时候就会使用该协议)、 超文本传输协议HTTP(使用较多,通过IE浏览一个网页的时候就使用该协议)、域名服务DNS(使用较多,通过网络访问一个计算机一般不使用该主机的IP地址,而是通过该主机的域名访问)、简单邮件传输协议SMTP(通过Foxmail发送邮件)、邮局协议POP3等(通过Foxmail收邮件);

2)传输层:传输控制协议TCP、用户数据报协议UDP;

TCP:面向连接的可靠的传输协议;在利用TCP协议进行通信的时候,首先要经过三步握手建立起通信双方的连接,一旦连接建立后就可以通信了。TCP协议提供数据确认和重传的机制,保证数据一定能够到达数据接收端。像打电话。

UDP:是无连接的,不可靠的传输协议;采用UDP协议进行通信时,不需要建立连接,可以直接向一个IP地址发送数据,至于是不是能够收到不能保证,发送过程中数据有可能丢失、IP地址可能不存在、再者IP地址代表的主机没有运行等原因都可能导致不能接收到数据。

3)网络层:网际协议IP、Internet互联网控制报文协议ICMP、Internet组管理协议IGMP。

基于TCP的Socket编程步骤:

1)服务器程序编写:

    ①调用ServerSocket(intport)创建一个服务器端套接字,并绑定到指定端口上;

    ②调用accept(),监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字;

    ③调用Socket类的getOutputStream()和getInputStream获取输出流和输入流,开始网络数据的发送和接收;

    ④最后关闭通信套接字。

2)客户端程序编写:

    ①调用Socket()创建一个流套接字,并连接到服务器端;

    ②调用Socket类的getOutputStream()和getInputStream获取输出流和输入流,开始网络数据的发送和接收;

    ③最后关闭通信套接字。

基于UDP的Socket编程步骤:

1)接收端程序编写:

    ①调用DatagramSocket(intport)创建一个数据报套接字,并绑定到指定端口上;

    ②调用DatagramPacket(byte[]buf, int length),建立一个字节数组以接收UDP包;

    ③调用DatagramSocket类的receive(),接收UDP包;

    ④最后关闭数据报套接字。

2)发送端程序编写:

    ①调用DatagramSocket()创建一个数据报套接字;

    ②调用DatagramPacket(byte[]buf, int offset, int length, InetAddress address, int port),建立要发送的UDP包;

    ③调用DatagramSocket类的send(),发送UDP包;

    ④最后关闭数据报套接字。


0 0