JavaSE基础巩固
线程
线程与进程的关系
-
进程是一个应用程序;线程是一个进程中的执行单元,一个进程可以启动多个线程
-
对于Java程序来说,运行文件时,会先启动JVM虚拟机,JVM虚拟机再启动一个垃圾回收线程负责看护,然后执行main方法的主线程
-
两个进程之间是独立的,不共享资源;两个线程,堆内存和方法区内存资源共享,栈内存独立,一个线程一个栈
多线程机制
-
Java使用多线程机制,就是为了提高程序的处理效率
-
使用了多线程机制后,main主方法结束了,主栈空了,其他的线程(栈)还是有可能在继续执行
-
单核CPU不能做到真正意义上的多线程并发,但是可以给人一个“多线程并发”的错觉
实现线程的方式
-
第一种方式:编写一个类,继承java.lang.Thread,重写run方法
class ThreadTest{ public static void main(String[] args){ //创建线程对象 MyThread t1 = new MyThread(); //启动线程 t1.start(); } } class MyThread extends Thread { //run方法当中的异常不能throws,只能try...catch @Override public void run(){ super.run(); } }
-
启动线程方法:start(),作用是在JVM中新开辟一个栈内存空间,供t1线程使用,开辟完即方法结束,启动成功的线程会由JVM虚拟机自动调用run方法
-
-
第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法
class ThreadTest{ public static void main(String[] args){ //创建可运行对象 MyRunnable r = new MyRunnable(); //将可运行对象封装成一个线程对象 Thread t1 = new Thread(r); //启动线程 t1.start(); } } class MyRunnable implements Runnable { //run方法当中的异常不能throws,只能try...catch @Override public void run(){ super.run(); } }
-
使用匿名内部类改写第二种方式
class ThreadTest{ public static void main(String[] args){ //创建可运行对象 MyRunnable r = new MyRunnable(); //将可运行对象封装成一个线程对象 Thread t1 = new Thread(new Runnable(){ //run方法当中的异常不能throws,只能try...catch @Override public void run(){ super.run(); } }); //启动线程 t1.start(); } }
-
第三种方式:新建一个“未来任务类”对象,实现Callable接口
public class ThreadTest01 { public static void main(String[] args) throws ParseException, ExecutionException, InterruptedException { //创建一个给Callable接口实现类的对象 FutureTask task = new FutureTask(new Callable() { @Override public Object call() throws Exception {//call方法相当于有返回值的run方法 return new Object(); } }); Thread t = new Thread(task); t.start(); //获取线程t的返回值 //这里获取返回值的操作可能会造成当前线程阻塞,因为需要先执行完call方法才能得到返回值 Object o = task.get(); System.out.println("线程t返回的是:" + o); } }
线程的生命周期
线程的常用方法
-
设置线程名称:void setName(String name),通过构造方法也可以设置线程名称
-
获取线程名称:String getName()
-
获取当前线程:static Thread currentThread()
-
使当前线程暂停执行一定时间(进入阻塞状态):static void sleep(long millis)
-
改变中断状态:void interrupt(),不会中断线程的运行,通过抛出InterruptedException中断异常来改变wait、sleep、join方法的中断状态,使之中断,依靠了java的异常处理机制
-
终止一个线程的执行
class ThreadTest{ public static void main(String[] args){ //创建可运行对象 MyRunnable r = new MyRunnable(); //将可运行对象封装成一个线程对象 Thread t1 = new Thread(r); //启动线程 t1.start(); //模拟5秒 try{ Thread.sleep(1000*5); }catch(InterruptedException e){ e.printStackTrace(); } //终止线程 t1.run = false; } } class MyRunnable implements Runnable { //布尔标记 boolean run = true; //run方法当中的异常不能throws,只能try...catch @Override public void run(){ for(int i = 0; i < 100; i++){ if(run){ System.out.print(Thread.currentThread().getName() + "--->" + i); try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } }else{ //终止线程前进行其他工作 //save()... return; } } } }
线程调度概述
-
线程的两种调度模型
-
抢占式调度模型:优先级高的线程抢占时间片的概率要高些,如果线程的优先级相同,则随机抢占时间片,Java使用的是抢占式调度模型
-
分时调度模型(均分式调度模型):所有线程轮流使用CPU的使用权,平均分配每个线程相同的CPU时间片
-
-
与线程调度有关的方法
-
设置线程优先级:final void setPriority(int newPriority)
-
获取线程优先级:final int getPriority()
-
默认线程优先级为5,范围为1-10
-
-
线程让位方法:static void yield(),暂停当前正在执行的线程对象,并执行其他线程
-
运行状态--->就绪状态,回到就绪状态会继续开始抢CPU时间片
-
-
等待该线程死亡:void join(),调用方法线程与当前线程合并,当前线程受阻塞,待调用方法线程死亡后,当前线程继续执行
-
当前线程:运行状态--->阻塞状态,待调用方法线程:运行状态--->死亡状态后,当前线程:阻塞状态--->就绪状态
-
-
线程安全
-
何时出现线程安全问题
-
是否是多线程环境
-
是否有共享数据
-
是否有多条语句操作共享数据
-
-
如何解决线程安全问题
-
使用线程同步机制:使之线程排队执行,不能并发
-
-
同步、异步编程模型
-
同步编程模型:线程t1与线程t2,等待对方执行完后才会开始执行,存在等待关系,同步就是排队,效率较低
-
异步编程模型:线程t1与线程t2互不影响,各自执行各自的,不存在任何关系,异步就是并发,效率较高
-
-
线程同步机制的语法
synchronized(){ //线程同步代码块 }
-
synchronized传的形参必须是多线程共享的数据
-
-
线程同步机制代码执行原理
-
Java语言中,每一个对象都有“一把锁”,线程t1和线程t2并发,当t1先执行并遇到synchronized时,会释放掉之前占有的CPU时间片,然后自动去锁池(相当于一种阻塞状态)中找“共享对象”的对象锁,找到后并占有这把锁,执行同步代码块中的程序。在t1占有这把锁时,线程t2遇到了synchronized,t2也会释放之前占有的时间片,并去锁池中找这把锁,而这把锁此时被t1占有,所以t2线程对象只能在锁池中等待t1归还这把锁。直到t1执行完同步代码块代码,这把锁才会被释放回锁池中,而t1也会返回到就绪状态去抢夺CPU时间片,t2会占有这把锁执行同步代码块中的程序,这样就达到了线程排队执行
-
-
Java中三大变量,只有局部变量永远不存在线程安全问题,因为局部变量存在于栈内存中,数据不共享,而实例变量存储在堆内存中,堆内存只有一个,多线程数据共享,静态变量存储在方法区中,方法区只有一个,多线程数据共享
-
成员变量存储于堆内存,存在线程安全问题
-
常量不可修改,不存在线程安全问题
-
-
同步实例方法
-
格式:修饰符 synchronized 返回值类型 方法名(方法参数){ }
-
synchronized出现在实例方法上,对象锁一定是this
-
缺点:1.对象锁只能是this,不灵活 2.整个方法体都需要同步,扩大同步范围,降低效率
-
-
同步静态方法
-
格式:static synchronized 返回值类型 方法名(方法参数){ }
-
synchronized出现在静态方法上,表示找类锁,类锁永远只有一个
-
-
线程安全的类
-
StringBuffer
-
线程安全,可变的字符序列
-
从JDK5开始,被StringBuilder替代,StringBuilder线程不安全,效率高
-
-
Vector
-
线程安全,当Vector的容量需要增加时,Vector会将容量增加100%,而ArrayList只会增加50%
-
Vector方法是同步的,所以效率低,ArrayList是异步的,效率要高
-
由Collections类下的synchronizedList方法来代替Vector
//static <T> List<T> synchronizedList(List<T> list) 返回由指定列表支持的同步(线程安全)列表 List<String> list = Collections.synchronizedList(new ArrayList<String>());
-
-
Hashtable
-
该类实现了一个哈希表,它将键映射到值,任何非null对象都可以用作键和值
-
Hashtable方法是同步的,所以效率低,HashMap是异步的,效率高
-
-
-
死锁
-
当线程t1锁对象o1时,线程t2锁对象o2,如果t1还要去锁对象o2,而t2要去锁对象o1,则两个线程都会进入死锁状态,不出现异常,不出现错误,僵持在那里
public class ThreadTest01 { public static void main(String[] args) { Object o1 = new Object(); Object o2 = new Object(); MyThread1 t1 = new MyThread1(o1,o2); MyThread2 t2 = new MyThread2(o1,o2); t1.start(); t2.start(); } } class MyThread1 extends Thread{ Object o1; Object o2; public MyThread1(Object o1, Object o2) { this.o1 = o1; this.o2 = o2; } @Override public void run() { synchronized (o1){ try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (o2){ System.out.println("我已经找到o2锁了"); } } } } class MyThread2 extends Thread{ Object o1; Object o2; public MyThread2(Object o1, Object o2) { this.o1 = o1; this.o2 = o2; } @Override public void run() { synchronized (o2){ try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (o1){ System.out.println("我已经找到o1锁了"); } } } }
-
守护线程
-
Java语言中线程分为两大类:用户线程和守护线程(最具有代表性的例子:垃圾回收线程)
-
守护线程的特点:一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束(主线程main方法是一个用户线程)
-
启动线程之前,将线程设置为守护线程
Thread t = new MyThread(); //设置线程为守护线程 t.setDaemon(true); t.start();
定时器
-
作用:间隔特定的时间,执行特定的程序
-
多种方式实现
-
可以使用sleep方法睡眠,设置睡眠时间,执行任务(最原始的定时器)
-
java的类库中存在一个定时器类:java.util.Timer,很多高级框架的底层实现原理就是这个类
void schedule(TimerTask task, Data firstTime, long period); //安排指定的任务(task)在指定的时间(firstTime)开始进行重复的固定延迟(period)执行
-
开发中,使用最多的是Spring框架提供的SpringTask框架,实现定时器的任务
-
public class TimerTest { public static void main(String[] args) throws ParseException { //创建定时器对象 Timer timer = new Timer(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date firstTime = sdf.parse("2022-10-11 13:27:00"); timer.schedule(new TimerTask(){ @Override public void run() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String strTime = sdf.format(new Date()); System.out.println(strTime + ":成功完成了一次数据备份"); }, firstTime, 1000 * 10); } }
生产者和消费者模式
-
wait方法和notify方法是Object类的方法,不是线程对象调用,而是线程作用于的对象调用
-
wait()方法
Object o = new Object(); o.wait(); //表示让正在o对象上活动的线程进入等待状态,直到被唤醒为止
-
notify()方法
Object o = new Object(); o.notify(); //表示唤醒正在o对象上等待的线程,继续活动
-
notifyAll()方法
Object o = new Object(); o.notifyAll(); //表示唤醒正在o对象上等待的所有线程,继续活动
-
wait方法和notify方法建立在synchronized线程同步的基础上
-
o.wait()会让正在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁
-
o.notify()会让在o对象上等待的线程唤醒,只会通知,不会释放之前占有的o对象的锁
反射机制
反射概述
-
作用:通过反射机制可以操作字节码文件(读/写),通过反射机制可以操作代码片段(class文件)
-
反射机制相关类:
-
代表整个字节码,代表整个类:java.lang.Class
-
代表字节码中的方法字节码,代表类中的方法:java.lang.reflect.Method
-
代表字节码中的构造方法字节码,代表类中的构造方法:java.lang.reflect.Constructor
-
代表字节码中的属性字节码,代表类中的成员变量(静态变量/实例变量):java.lang.reflect.Field
-
反射机制实例化对象
-
操作一个类的字节码,需要先获取到这个类的字节码,获取java.lang.Class实例的方法:
-
返回与带有给定字符串名的类货接口相关联的Class对象:static Class<?> forName(String className)
-
这个方法的执行会导致类加载,类加载(类初始化阶段)时,静态代码块执行
-
-
java.lang.Object类中方法返回此Object的运行时类:Class<?> getClass()
-
java语言中任何一种类型,都有.class属性
-
-
通过反射机制,获取Class来实例化对象
-
Class c = Class.forName("java.lang.String"); //newInstance()方法会调用String这个类的无参构造方法,完成对象的创建,JDK9后已过时 Object obj = c.newInstance();
-
-
利用properties文件来创建类对象,在不改变源代码的基础上,可以做到不同对象的实例化,更加灵活(符合OCP开闭原则:对扩展开放,对修改关闭)
//通过IO流读取classinfo.properties文件 FileReader reader = new FileReader("src/java/classinfo.properties"); //创建属性类对象Map,key和value都是String Properties prop = new Properties(); //加载配置文件 prop.load(reader); //关闭流 reader.close(); //通过key获取value String className = prop.getProperty("className"); //通过反射机制实例化对象 Class c = Class.forName(classname); Object obj = c.newInstance();
-
当.properties文件放在类路径下(src目录下)时,有一个通用文件路径方式替代原文件路径
/* 以下代码可以拿到一个文件的绝对路径 Thread.currentThread() 当前线程对象 getContextClassLoader() 线程对象的方法,可以获取到当前线程的类加载器对象 getResource() 类加载器对象的方法,获取资源,当前线程的类加载器默认从类的跟路径下加载资源 */ String path = Thread.currentThread().getContextClassLoader().getResource("java/classinfo.properties").getPath(); FileReader reader = new FileReader(path);
//以下代码等同于上方代码,可替代,直接以流的形式直接返回 InputStream reader = Thread.currentTHread().getContextClassLoader().getResourceAsStream("java/classinfo.properties");
-
-
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容
ResourceBundle bundle = ResourceBundle.getBundle("java/classinfo.properties"); String className = bundle.getString("className");
-
使用资源绑定器这种方式时,属性配置文件必须放在类路径下,只能绑定.properties文件
-
写路径时,路径后面的扩展名不能写
-
反射属性(类)
Field
-
获取类中所有public修饰的Field对象
//获取整个类(自定义Student类) Class c = Class.forName("com.java.Student"); //获取类中所有public修饰的Field对象 Field[] fields = c.getFields();
-
获取类中所有的Field对象
//获取类中所有public修饰的Field对象 Field[] fields = c.getDeclareFields();
-
获取所有Field对象的名字
//获取所有Field对象的名字 for(Field field : fields){ String fieldName = field.getName(); }
-
获取所有Field对象的类型
//获取所有Field对象的类型 for(Field field : fields){ //获取Field对象的类型列表 Class fieldType = field.getType(); //Class类下的方法:getName(),获取类的全类名 String fieldName = fieldType.getName(); //Class类下的方法:getSimpleName(),获取类的简类名 String fieldSimpleName = fieldType.getSimpleName(); }
-
获取所有Field对象的修饰符
//获取所有Field对象的修饰符 for(Field field : fields){ //获取属性的修饰符列表 int i = field.getModifiers(); //将i这个“代号”转换成字符串格式,Modifier类下的toString()方法 String modifierStr = Modifier.toString(i); }
-
获取一个指定的属性
//获取类中一个指定的属性(根据属性名指定,自定义Student类中的name属性) Field nameField = c.getDeclareFields("name");
-
给一个实例对象的指定属性赋值(private修饰属性除外)
//实例化Student类对象 Object stu = c.newInstance(); //给stu对象的name属性赋值 nameField.set(stu,"大帅哥");
-
给一个实例对象的指定private修饰属性赋值
//获取类中一个指定的属性(根据属性名指定,自定义Student类中的userID属性,userID属性被private修饰) Field userIDField = c.getDeclareFields("userID"); //打破封装(反射机制缺点:打破封装,不安全) userIDField.setAccessible(true); //给stu对象的userID属性赋值 userIDField.set(stu,66);
-
获取一个实例对象的指定属性值
//获取stu对象的name属性值 String nameStu = nameField.get(stu);
反编译Field
StringBuilder sb = new StringBuilder(); Student stu = new Student(); Class studentClass = Class.forName("com.java.Student"); sb.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + "{\t"); Field[] fields = studentClass.getDeclaredFields(); for (Field field : fields) { sb.append(Modifier.toString(field.getModifiers())); sb.append(" "); sb.append(field.getType().getSimpleName()); sb.append(" "); sb.append(field.getName()); sb.append(";\n"); } sb.append("}"); System.out.println(sb);
Method
-
可变长度参数
public static void method(int... args){ }
-
可变长度参数只能出现在最后且只能有一个
-
可变长度参数是一个数组,长度为0—n
-
-
获取所有的Method对象
//获取整个类(自定义Student类) Class c = Class.forName("com.java.Student"); //获取类中所有的Method对象 Method[] methods = c.getDeclareMethods();
-
获取所有Method对象的名字
//获取所有Method对象的名字 for(Method method : methods){ String methodName = method.getName(); }
-
获取所有Method对象的返回值类型
//获取所有Method对象的返回值类型 for(Method method : methods){ //获取Field对象的类型列表 Class methodType = method.getReturnType(); //Class类下的方法:getName(),获取类的简类名 String methodSimpleName = methodType.getSimpleName(); }
-
获取所有Method对象的修饰符
//获取所有Method对象的修饰符 for(Method method : methods){ //获取方法的修饰符列表 int i = method.getModifiers(); //将i这个“代号”转换成字符串格式,Modifier类下的toString()方法 String modifierStr = Modifier.toString(i); }
-
获取所有Method对象的参数
//获取所有Method对象的参数 for(Method method : methods){ //获取方法参数的类型列表 Class[] parameterTypes = method.getParameterTypes(); //获取方法参数的简类名 String parameterTypeSimpleName = parameterTypes.getSimpleName(); }
反射机制调用方法
-
使用Method类下的Object invoke(Object obj, Object... args)方法
-
Object:返回值类型
-
Object obj:调用对象
-
Object... args:方法参数列表
-
//使用反射机制来调用一个对象的方法 //获取class对象 Class c = Class.forName("com.java.Student"); //创建stu对象 Object stu = c.newInstance(); //获取指定login方法的Method对象 Method loginMethod = c.getDeclareMethod("login",String.class,String.class); //调用invoke方法来调用指定login方法 Object retValue = loginMethod.invoke(stu,"大帅哥","123456")
Constructor:同上
反射机制调用构造方法来实例化对象
//使用反射机制调用构造方法来实例化对象 //获取class对象 Class c = Class.forName("com.java.Student"); //调用无参构造方法来创建stu1对象(JDK9后已过时) Object stu1 = c.newInstance(); //获取无参构造方法 Constructor constructor1 = c.getDeclareConstructor(); //调用无参构造方法来创建stu2对象 Object stu2 = constructor1.newInstance(); //获取有参构造方法 Constructor constructor2 = c.getDeclareConstructor(int.class,String.class,String.class,boolean.class); //调用有参构造方法来创建stu3对象 Object stu3 = constructor2.newInstance(1,"大帅哥","2000-1-1",true);
父类与接口
-
获取Class类的父类
//获取Class对象 Class c = Class.forName("java.lang.String"); //获取CLass对象的父类Class对象 Class superClass = c.getSuperclass();
-
获取Class类的接口
//获取Class对象 Class c = Class.forName("java.lang.String"); //获取CLass对象的接口Class对象列表 Class[] interfaces = c.getInterfaces();
类加载器
-
ClassLoader(类加载器):专门负责加载类的命令/工具
-
JDK中自带了三个类加载器
-
Bootstrap classLoader 启动类加载器:rt.jar(父加载器)
-
ExtClassLoader 扩展类加载器:ext / *.jar(母加载器)
-
AppClassLoader 应用类加载器:classpath
-
-
双亲委派机制
-
有一段代码 String s = "abc"; ,代码开始执行前,会将所需要类全部加载到JVM中,通过类加载器加载,会去寻找与代码对应的class文件,以上代码会去找String.class文件
-
首先通过“启动类加载器”加载(启动类加载器中都是JDK最核心的类库)
-
其次如果通过“启动类加载器”加载不到时,会通过“扩展类加载器”加载
-
最后如果通过“扩展类加载器”加载不到时,会通过“扩展类加载器”加载(应用类加载器专门加载classpath中的类(src目录下的类))
-
-
类加载机制
注解
注解概述
-
注解 Annotation,又叫做注释
-
注解是一种引用数据类型,编译之后生产.class文件
-
注解的作用:一种提示标识符
-
注解使用语法格式:@注解类型名
-
默认情况下,注解可以出现在任意位置
JDK内置注解
元注解
-
用来标注“注解类型”的“注解”,称为元注解
-
常见的元注解
-
Target注解
-
源代码:
//元注解 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation interface * can be applied to. * @return an array of the kinds of elements an annotation interface * can be applied to */ //枚举类型属性 ElementType[] value(); }
-
ElementType[] value()源代码:
//枚举类型 public enum ElementType { TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE, MODULE, RECORD_COMPONENT; }
-
-
这个注解用来标注“被标注的注解”可以出现在哪些位置上
-
@Target(ElementType.METHOD):表示“被标注的注解”只能出现在方法上
-
-
Retention注解
-
源代码:
//元注解 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ //枚举类型属性 RetentionPolicy value(); }
-
RetentionPolicy value()源代码:
//枚举类型 public enum RetentionPolicy { SOURCE, CLASS, RUNTIME }
-
-
这个注解用来标注“被标注的注解”最终保存在哪里
-
@Retention(RetentionPolicy.SOURCE):表示“被标注的注解”只能保留在java源文件中
-
@Retention(RetentionPolicy.CLASS):表示“被标注的注解”被保存在class文件中
-
@Retention(RetentionPolicy.RUNTIME):表示“被标注的注解”被保存在class文件中,并且可以被反射机制所读取
-
-
Override注解
-
表示一个方法声明打算重写超类中的另一个方法声明,只能注解方法
-
这个注解是标识性注解,给编译器参考的,跟运行阶段无关
-
凡是java中的带有这个注解的方法,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器报错
-
-
源代码:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override{ }
Deprecated注解
-
表示被标注的元素已过时
-
主要向程序员传达一个信息,告知已过时,存在更好的解决方案
自定义注解
-
语法格式:修饰符 @interface 注解类型名{ }
-
注解属性:修饰符 数据类型 属性名() default 默认值;
-
注解属性,如果没有设置默认值,则在引用注解时,必须为其指定值
-
如果注解中有且只有一个属性,属性名为value时,引用注解可以省略“value=”
-
属性的类型可以是:byte、short、int、long、float、double、boolean、char、String、Class、枚举类型,以及以上每一种类型的数组形式
-
如果属性是一个数组,且数组中只有一个元素,引用注解时,可以省略大括号
-
反射注解
-
判断类上面是否有指定的注解
//获取Class对象 Class c = Class.forName("java.lang.String"); //判断类上面是否有指定的注解(方法/属性等其他元素同样的操作) if(c.isAnnotationPresent(Target.class)){ //获取该注解对象 Target target = (Target) c.getAnnotation(Target.class); //获取注解对象的属性 ElementType[] value = Target.value(); }