十、反射机制
1.Java反射机制概述
1.1Java Reflection
●Reflection(反射)是被犯为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子到类的结构,所以,我们形象的称之为:反射。
1.2Java反射机制提供的功能
在运行时判断任意一个对象所属的资T>在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法>在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法>在运行时处理注解
生成动态代理
2.理解Class类并获取Class实例
哪些类型可以有class对象?
(1) class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2) interface:接口
(3)[]:数组
(4) enum:枚举
(5) annotation:注解@interface
(6) primitive type:基本数据类型
(7) void
3.类的加载与ClassLoader的理解
1.类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化a
学
1 |
2 |
3 |
类的加载 |
类的链 |
按类的初始化 |
(Load) |
(Link) |
(Initialize) |
将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成 |
将类的二进制数据合并到JRE中 |
JVM负责对类进行初始化 |
加载: 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时
数据结构,然后生成一个代表这个类的ava.lang.ulas通N这个Class对象。这个加载的入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的
过程需要类加载器参与。
链接: 将Java类的二进制代码合并到JVM的运行状态之中的过程。
验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
准备:正式为类变量( static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化:
1.执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
2.当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
3.虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
2.类加载器的作用:
类加载的作用: 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
类缓存: 标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
3.ClassLoader
了解:ClassLoader
类加载器作用是用来把类(class)装载进内存的。
4.创建运行时类的对象
5.获取运行时类的完整结构
6.调用运行时类的指定结构
7.反射的应用:动态代理
●代理设计模式的原理:
使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
之前为大家讲解过代理机制的操作,属于静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
最好可以通过一个代理类完成全部的代理功能。
/**
* 静态代理举例
*
* 特点:代理类和被代理类在编译期间,就确定下来了。
*/
interface ClothFactory{
void produceCloth();
}
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory;//用被代理类对象进行实例化
public ProxyClothFactory(ClothFactory factory){
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工厂做一些准备工作");
factory.produceCloth();
System.out.println("代理工厂做一些后续的收尾工作");
}
}
//被代理类
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("Nike工厂生产一批运动服");
}
}
public class StaticProxyTest {
public static void main(String[] args) {
//创建被代理类对象
NikeClothFactory nike = new NikeClothFactory();
//创建代理类对象
ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nike);
proxyClothFactory.produceCloth();
}
}
/**
* 动态代理举例
*
*/
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}
/*
要想实现动态代理,需要解决的问题?
门题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。
*/
class ProxyFactory{
//调用此方法返回一个代理类的对象,解决问题一
public static Object getProxyInstance(Object obj){//obj:被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;//赋值时需要使用被代理类对象赋值
public void bind(Object obj){
this.obj = obj;
}
//当我们通过代理类的对象调用方法a时就会自动的调用如下的方法:invoke()
//将被代理类要执行的方法a的功能声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method:即为代理类的抽象方法,此方法也就作为了背代理类调用的方法
//obj: 被代理类的对象
Object returnValue = method.invoke(obj, args);
//上述方法的返回值就作为当前类中的invoke()的返回值
return returnValue;
}
}
public class ProxyTEST {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("大猪蹄子");
}
}
十一、多线程
1.基本概念:程序、进程、线程
程序(progran) 是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process) 是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程(thread) 进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
一个进程中的多个线程共享相同的内存单元/内存地址空间→它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
使用多线程的优点
背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
多线程程序的优点:
1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。2.提高计算机系统CPU的利用率
3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
2.线程的创建和使用
Thread类的有关方法:
ovoid start():启动线程,并执行对象的run()方法
run():线程在被调度时执行的操作
String getName():返回线程的名称
void setName(String name):设置该线程名称
static Thread currentThread():返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
static void yield():线程让步
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
若队列中没有同优先级的线程,忽略此方法
join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止
低优先级的线程也可以获得执行
static void sleep(long millis):(指定时间:毫秒)
令当前活动线程在指定时间段内放奋对CPU控制,使其他线程有机会被执行,时间到后重排队。
抛出InterruptedException异常
stop():强制线程生命期结束,不推荐使用
boolean isAlive():返回boolean,判断线程是否还活着
线程的调度
调度策略:
1.时间片调度
2.抢占式:高优先级的线程抢占CPU
Java的调度方法
同优先级线程组成先进先出队列(先到先服务),使用时间片策略>对高优先级,使用优先调度的抢占式策略
线程的优先级
线程的优先级等级
MAX_PRIORITY: 10
MIN_PRIORITY:1
NORM_PRIORITY: 5
涉及的方法
getPriority():返回线程优先值工
setPriority(int newPriority):改变线程的优先级
说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
3.线程的生命周期线程的同步
JDK中用Thredd.State类定义了线程的儿种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU并临时中止自己的执行;进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
4.线程的同步
/**
* 例子:创建三个窗口买票,总票数为100张 使用继承Thread类的方式
*
* 存在线程安全问题:待解决
*
* 1.问题:卖票过程中,出现了重票、错票 -->出现了线程安全问题
* 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,
* 其他线程参与进来,也操作车票。
* 3.如何解决:当一个线程在操作ticket的时候其他线程不能参与进来。
* 直到线程a操作完ticket时,其他线程才可以操作ticket。这种情况
* 即使线程a出现了阻塞,也不能被改变
* 4,在Java中通过同步机制解决线程安全问题。
*
* 方式一:同步代码快
* synchronized(同步监视器){
* //需要被同步的代码
* }
* 说明:操作共享数据的代码时需要被同步的代码
* 共享数据:多个线程共同操作的变量
* 同步监视器:俗称:锁。任何一个类的对象都可以充当锁。
* 要求:多个线程必须共用同一把锁
* 方式额:同步方法
*
*/
class Windows extends Thread{
Object obj = new Object();
private static int ticket = 100;
@Override
public void run() {
while (true){
synchronized(obj){
if(ticket > 0){
System.out.println(getName() + ":买票,票号为" + ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class WindowsTest {
public static void main(String[] args) {
Windows w1 = new Windows();
Windows w2 = new Windows();
Windows w3 = new Windows();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
2.线程的死锁问题
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
3.Lock(锁)
从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock类实现了Lock ,它拥有与synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
解决线程安全方式三:Lock锁
/**
* 解决线程安全方式三:Lock锁 JDK5.0新增
*
* 1.面试题: synchronized 与Lock的异同?相同:二者都可以解决线程安全问题
* 不同: synchronized机制在执行完相应的同步代码以后,自动的释放同步监视
* Lock需要手动的启动同步(Lock()),同时结束同步也需要手动的实现(unlock())
*
*
*/
class Window implements Runnable{
private int ticket = 100;
//实例化一个Lock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//2.调用锁定的方法lock()
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally {
//3.调用解锁的方法unlock()
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread thread1 = new Thread(w);
Thread thread2 = new Thread(w);
Thread thread3 = new Thread(w);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
5.线程的通信
涉及到的三个方法:
- wait():一旦执行此方法,当前线程就进入阻塞状态并释放同步监视器
- notify():一旦执行此方法,就会先后被wait的线程,多个线程被wait,则唤醒优先级高的那个
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
- 1.wait()、notify()、notifyAll()只能出现在同步代码块和同步方法中,lock中不行
- 2.wait( )、notify()、notifyALL()三个方法的调用者必须是同步代码块或同步方法中的同步监视器
否则,会出现ILLegaLMonitorStateException异常
- 3.wait()、notify()、notifyALL()三个方法是定义在java.Lang.object类中。
面试题:sleep()和 wait()的异同?
- 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
- 2.不同点:
1)两个方法声明的位置不同: Thread类中声明sleep() , object类中声明wait()。
2)调用的要求不同: sleep()可以在任何需要的场景下调用。wait()必须使用在同步代块或同步方法中调用。
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
/**
* 线程通讯的例子:
* 使用两个线程交替打印1-100。线程1,线程2交替打印
*
* 涉及到的三个方法:
* wait():一旦执行此方法,当前线程就进入阻塞状态并释放同步监视器
* notify():一旦执行此方法,就会先后被wait的线程,多个线程被wait,则唤醒优先级高的那个
* notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
*
* 说明:
* 1.wait()、notify()、notifyAll()只能出现在同步代码块和同步方法中,lock中不行
* 2.wait( )、notify()、notifyALL()三个方法的调用者必须是同步代码块或同步方法中的同步监视器
* 否则,会出现ILLegaLMonitorStateException异常
* 3.wait()、notify()、notifyALL()三个方法是定义在java.Lang.object类中。
*
* 面试题:sleep()和 wait()的异同?
* 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
* 2.不同点:1)两个方法声明的位置不同: Thread类中声明sleep() , object类中声明wait()。
* 2)调用的要求不同: sleep()可以在任何需要的场景下调用。wait()必须使用在同步代块或同步方法中调用。
* 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
*/
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while(true){
synchronized (this) {
notify();
if(number <= 100){
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使得调用如下wait()的线程进入阻塞状态
//会释放同步监视器
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
6.JDK5.0新增线程创建方式
新增方式一:实现Callable接口
与使用Runnable相比,Callable功能更强大些相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果
新增方式二:使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程池相关API
JDK 5.0起提供了线程池相关APl:ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行
Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callablq
void shutdown():关闭连接池
Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n);创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
创建线程的方式三:实现Callable接口
/**
* 创建线程的方式三:实现Callable接口。 JDK 5.0
*
*
*
*
*/
//1.创建一个实现Callable的实现类
class numberThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum = sum + i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建callable实现类的对象
numberThread numberThread = new numberThread();
//4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numberThread);
//5将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start();
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的caLL()的返回值。
Object o = futureTask.get();
Integer integer = (Integer) o;
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
创建线程的方式四:线程池
/**
* 创建线程的方式四:
* 好处:
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建>)3.便于线程管理
* corePoolsize:核心池的大小
* maximumPoolsize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止
*
*/
class NumberThread5 implements Runnable{
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2,执行指定线程的操作。需要实现提供Runnable接口或者Callable接口的实现类对象
service.execute(new NumberThread5());//适合用于Runnable
// executorService.submit(Callable callable)//适合用于Callable
//3.关闭线程池
service.shutdown();
}
}