Java——多线程,反射
3.多线程
(1).概述:
【进程】:一个应用程序,一个进程可启动多个线程;
【线程】:一个进程中的执行场景/单元。
多线程的作用和特点:
不同线程的栈内存独立,堆和方法区共享
,多线程并发可提高程序处理效率。
(2).线程的生命周期:
(3).多线程的实现方法:
<1>.Thread类详解:
所属包名:java.lang.Thread
常用方法:
setName()方法:
/*
*修改线程的名字
*@param name:线程的新名字
*/
public final synchronized void setName(String name);
getName()方法:
/*
*获取线程的名字
*@return:线程的名字
*/
public final String getName();
interrupt()方法:
/*
*利用异常处理机制终止睡眠
*/
public void interrupt();
currentThread()方法:
/*
*获取当前线程
*return:当前线程
*/
public static native Thread currentThread();
sleep()方法:
/*
*使当前线程进入休眠,出现在哪个线程,使哪个线程进入休眠状态
*@param millis:休眠的毫秒数
*/
public static native void sleep(long millis);
<2>.继承实现多线程:
①编写一个类,使其继承java.lang.Thread
类;
②重写run()方法;
③.启动线程:调用对象的【start()方法】(本质是在JVM中开辟新的栈空间)。
<3>.接口实现多线程:
①编写一个类,使其实现java.lang.Runnable
接口;
②实现run()方法;
③创建线程对象,传入接口实现类;
④调用线程对象的【start()方法】。
补充对run()方法的理解:run()方法在分支线程中等同于主线程的main()
(4).账户存取款实战:
业务需求:写一个程序,要求使用多线程操控一个账户,同时进行取钱操作。寻找安全隐患问题。
源码展示:
Account账户类:
package Thread222;
public class Account {
private String name;
private double balance;
public Account(){}
public Account(String name,double balance){
this.name = name;
this.balance = balance;
}
//存钱方法
public void deposit(double money){
setBalance(balance+money);
System.out.println("存钱成功,当前余额:$"+balance);
}
//取钱方法
public void withdraw(double money){
if (balance < money){
System.out.println("取钱失败,余额仅剩"+balance);
return;
}
setBalance(balance-money);
System.out.println("取钱成功,当前余额:$"+balance);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"name='" + name + '\'' +
", balance=" + balance +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Account account = (Account) o;
return Double.compare(account.balance, balance) == 0 &&
name.equals(account.name);
}
}
主类以及线程类:
package Thread222;
public class Main {
public static void main(String[] args) {
Account act = new Account("QAQ",50000);
Thread t1 = new Thread(new Thread111(act));
Thread t2 = new Thread(new Thread222(act));
Thread t3 = new Thread(new Thread333(act));
//启动线程
t1.start();
t2.start();
t3.start();
}
}
class Thread111 implements Runnable{
Account act;
public Thread111(Account act){
this.act = act;
}
@Override
public void run() {
//同时取钱:
act.withdraw(5000);
}
}
class Thread222 implements Runnable{
Account act;
public Thread222(Account act){
this.act = act;
}
@Override
public void run() {
//同时取钱:
act.withdraw(5000);
}
}
class Thread333 implements Runnable{
Account act;
public Thread333(Account act){
this.act = act;
}
@Override
public void run() {
//同时取钱:
act.withdraw(5000);
}
}
(5).线程安全:
<1>产生线程安全问题的情况:
通过上述程序可知,每个线程内部的执行顺序不确定,由此会产生线程安全问题。
总而言之,以下情况会产生线程安全问题:
Ⅰ.多线程并发程序;
Ⅱ.存在共享数据,比如上述的余额;
Ⅲ.共享数据有修改的行为,比如上述案例的取钱操作。
<2>.解决方案(重点):线程同步机制
synchronized关键字:
用法①:
synchronized(同步共享对象){同步代码块}
同步共享对象处可填:this,Object类,字符串
。
案例:
public void withdraw(double withdrawMoney){
synchronized (this){
if (withdrawMoney>=0){
if (getBalance()<withdrawMoney){
System.out.println("取钱失败,余额仅剩"+getBalance()+"。");
}else{
setBalance(getBalance()-withdrawMoney);
System.out.println("取钱成功,余额"+selectBalance());
}
}else{
System.out.println("请输入有效的数字!");
}
}
}
用法②:
使用在实例方法上,但这种方法使其共享对象仅能是【this】,且可能无故扩大作用域,效率较低,这种方式叫作添加【对象锁】。例如:
public synchronized void withdraw(double withdrawMoney){
if (withdrawMoney>=0){
if (getBalance()<withdrawMoney){
System.out.println("取钱失败,余额仅剩"+getBalance()+"。");
}else{
setBalance(getBalance()-withdrawMoney);
System.out.println("取钱成功,余额"+selectBalance());
}
}else{
System.out.println("请输入有效的数字!");
}
}
}
用法③:
修饰静态方法时叫作【类锁】。
【类锁】:一百个同类型对象一把类锁。
【对象锁】:一个对象一把对象锁。
【死锁】:无法解开的锁叫作死锁,比如两个锁方法互相或者嵌套调用。
高效解决线程安全问题的方法:
Ⅰ.使用局部变量(存在栈中)替代成员变量;(推荐)
Ⅱ.创建多个对象,使线程和对象一一对应。
Ⅲ.使用【synchronized】关键字。
(6).守护线程(后台线程):
实例:垃圾回收机制;
特点:.
①.守护线程是一个死循环;
②.所有用户线程结束后,守护线程自动结束;
③.将用户线程转换为守护线程的方法(Thread下的方法):
public final void setDaemon(boolean on);
(7).生产者模式与消费者模式:
<1>.Object类下的两个方法介绍:
①wait():
/*
* 让正在对象上活动的线程无限期等待
* 【释放之前占用的对象锁】
*/
public final void wait();
②notify()与notifyAll:
/*
* 唤醒正在对象上等待的线程
* 该方法不会释放锁
*/
public final native void notify();
<2>.业务实战:
业务需求: 巧妙地利用wait()方法与notify()方法对线程进行生产者与消费者的平衡。
源码展示:
package 多线程;
import java.util.ArrayList;
import java.util.List;
/*
* 测试生产者模式与消费者模式:
* 巧妙地利用wait()方法与notify()方法对线程进行
* 生产者与消费者的平衡。
* wait()方法释放对象锁,让线程进行等待;
* notify()方法不会释放锁,仅仅告知唤醒被wait的对象。
* */
public class Producer_and_Consumer {
public static void main(String[] args) {
//创建仓库对象:
List list = new ArrayList();
//创建线程对象:
Thread t1 = new Thread(new Producer(list));
Thread t2 = new Thread(new Consumer(list));
//设置线程名字:
t1.setName("生产者线程");
t2.setName("消费者线程");
//启动线程:
t1.start();
t2.start();;
}
}
//生产者线程:
class Producer implements Runnable{
// 传入共享对象仓库List集合:
List list;
public Producer(List list){
this.list = list;
}
@Override
public void run() {
while(true){
synchronized(list) {
if (list.size() > 0) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序走到这里代表集合中无元素,需要“生产”:
Object o = new Object();
list.add(o);
System.out.println(Thread.currentThread().getName() + "--->" + o);
list.notify();
}
}
}
}
//消费者线程:
class Consumer implements Runnable{
//传入共享对象仓库List集合:
List list;
//构造方法:
public Consumer(List list){
this.list = list;
}
@Override
public void run() {
while(true){
synchronized(list){
if (list.size() == 0){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果程序能走到这里,说明集合需要消费:
Object o = list.remove(0);
System.out.println(Thread.currentThread().getName()+"--->"+o);
list.notify();
}
}
}
}
4.反射(java.lang.reflect.*)
(1).定义与作用:
定义:
反射就是把java类中的各种成分映射成一个个的Java对象。
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
作用:
通过反射可以操作字节码文件(class文件)。
(2).反射的常用功能:
<1>.获取某类的字节码:
方法①:
※ 该方法会导致类加载,静态代码块会执行。
//该方法会导致类加载,静态代码块会执行。
Class c = Class.forName("(完整类名带包名)");
方法②:
Class c = (引用).getClass();
方法③:
Class c = (任何类型).class;
上述三种方式中:
①,③两种方式使用类名反射;
②方式使用对象反射;
<2>.通过反射机制实例化对象:
注:这种方式创建对象仅能调用其无参构造。
//注:这种方式创建对象仅能调用其无参构造。
Class c = Class.forName("Student");
Student s = (Student)c.newInstance();
<3>.获取文件的绝对路径:
String path =
Thread.currentThread().getContextClassLoader().
getResource("全限定名称路径").getPath();
<4>.资源绑定器:
所在包:java.util.ResourceBundle
public static final ResourceBundle getBundle(String baseName)
实例:
存在属性配置文件db.properties(src下):
id=001
name=guan
age=20
ResourceBundle bundle = ResourceBundle.getBundle("db");
String id = bundle.getString("id");
String name = bundle.getString("name");
String age = bundle.getString("age");
(3).反射应用(java.lang.reflect.*):
<1>.反射属性(~.Field):
常用方法:
Class类下:
getFields()方法:
只能拿到public修饰的属性
//拿到反射类对象的以public修饰的属性
public Field[] getFields();
getDeclaredFields():
//拿到反射类对象的所有属性
public Field[] getDeclaredFields();
Field类下:
getName():
//拿到属性的名字
public String getName();
getType():
//拿到属性的类型
public String getType();
set():
//给Object的对象赋值
public void set(Object o,Object value);
get():
//获取该属性的值
public Object get(Object obj);
<2>.反射方法(~.Method):
常用方法:
getReturnType():
//获取返回值类型
public Class<> getReturnType();
getModifiers():
//获取修饰符列表
Modifier.toString(method.getModifiers);
//获取返回值类型字符串
String returnType = method.getReturnType().getSimpleName();
getName():
//获取方法名
String name = method.getName();
getParameterTypes():
//获取方法的参数列表
public Class[] getParameterTypes();
//获取方法
getDeclaredMethod(…);
//调用方法:
public Object invoke(Object obj,参数列表);
<3>.反射类的父类和接口
//获取父类
public Class getSuperClass();
//获取父接口
public Class[] getInterfaces();
5.注解(annotation)
(1).概述:
①.注解是一种引用数据类型,编译后也生产class文件。
②.定义注解的方法:[修饰符列表]@interface 注解类型名{ ... }
。
③.注解可修饰属性,方法,变量,枚举,注解等…
④.注解使用时的语法格式:@注解类型名
(@override)
(2).两种常见注解:
@override
:
作用:编译器检查编译,自动检查方法是否重写父类方法,若没有重写,则报错。
用来注解的注解叫作【元注解】。
@Deprecated
:
作用:所标注的元素已过时。
(3).注解的其他用法:
①.注解自定义可定义属性,且当有属性时,必须给属性赋值;
例如:@......(属性名=属性值)
。
属性声明时格式为:数据类型+属性名();
例如:String name()
②.如果注解中属性名为value时并且只有这一个属性时,value可省略。