Java教程

Java多线程,反射

本文主要是介绍Java多线程,反射,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

多线程

什么是进程
进程是一个应用程序

什么是线程
线程是一个进程中的执行场景/进行单元
一个进程可以启动多个线程

对于java程序来首,当在DOS命令窗口中输入:
java HelloWorld回车之后。
会先启动JVM,而JVM就是一个进程
同时再启动一个垃圾回收线程负责看护,回收垃圾
最起码,现在java程序至少有两个线程并发,一个垃圾回收线程,一个是执行main方法的主线程

进程A和进程B的内存独立不共享

线程A和线程B,在java语言中:线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈
假设启动10个线程,会有10个栈空间,每个栈和栈之间,互不干扰,各自执行各自的,这就是多线程并发

java中的多线程机制,目的就是为了提高程序的处理效率

什么是真正的多线程并发
t1线程执行t1的
t2线程执行t2的
t1不会影响t2,t2也不会影响t1这叫做真正的多线程并发

java中实现多线程的方式:
1.编写一个类,直接继承java.lang.Thread,重写run方法
2.编写一个类实现java.lang.Runnable接口

第二种方式实现接口比较常用,因为一个类实现了接口,还可以继承其它类,更灵活

关于线程对象的生命周期
新建状态
就绪状态
运行状态
阻塞状态
死亡状态

常见的线程调度有哪些?
抢占式调度模型:
哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些
java采用的就是抢占式调度模型

均分式调度模型
平均分配CPU时间片,每个线程占有的CPU时间的时间长度一样
平均分配,一切平等

java中提供了哪些方法是和线程调度有关系的呢?
实例方法
void setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程的优先级
最低有限级是1
默认有限级是5
最高优先级是10
优先级比较高的获取CPU的时间片可能会多一些(但也不完全是,大概率是多的)

静态方法
static void yield() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其他线程使用。
yield()方法的执行会让当前线程从"运行状态"回到"就绪状态"实例方法
void join()
合并线程

class MyThrea1 extends Thread {
    public void doSome() {
        MyThrea2 t = new MyThrea2();
        t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以正常执行
    }
}

class MyThrea2 extends Thread {

}

重点
关于多线程并发环境下,数据安全的问题。

以后开发中,我们的项目都是运行在服务器东中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写

最重要的是:编写的程序需要放到一个多线程的环境下运行,关注这些数据在多线程并发的环境下是否是安全的

怎么解决线程安全问题?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时存在线程安全问题
线程排队执行。(不能并发)
用排队执行解决线程安全的问题。
这种机制被称为:线程同步机制

专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行

线程排队了就会牺牲一部分效率,数据安全第一位,只有数据安全了。我们才可以谈效率。数据不安全,没有效率的事情


异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1
谁也不需要等谁,这种编程模型叫做:异步编程模型
其实就是:多线程并发(效率较高)

同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束。或者说t2线程执行的时候,必须等待t1线程执行结束。
两个线程之间发生了等待关系,这就是同步编程模型(效率较低)

线程同步机制的语法是:
synchronized() {
    // 线程同步代码块
}

synchronized后面小括号中传的这个“数据”是相当关键的。
这个数据必须是多线程共享的数据才能达到多线程排队

synchronized()的()中写什么?
那要看你想让哪些线程同步
假设t1,t2,t3,t4,t5有5个线程
你只希望t1,t2,t3排队 t4,t5 不需要排队。怎么办?
一定要在()中写一个t1,t2,t3共享对象。而这个对象对于t4,t5来说不是共享的

java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁)
100个对象,100把锁。1个对象1把锁。

线程代码的执行原理
1.假设t1和t2线程并发,开始执行代码的时候,肯定有一个先一个后
2.假设t1线执行了,遇到synchronized,这个时候自动找"后面共享对象"的对象锁,找到之后并占有这把锁,然后执行同步代码块中的程序,在程序执行的过程中一致都是占有这把锁的。直到同步代码块结束,这把锁才会释放。
3.假设t1已经占有这把锁,此时t2页遇到synchronized关键字,也会去占有后面共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1结束,直到t1把同步代码执行结束了,t1会归还这把锁。之后t2会占有这把锁

这样就达到了线程排队执行。
这里需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行这些线程对象所共享的。
 

java中的三大变量
实例变量:在堆中
静态变量:在方法区
局部变量:在栈中

以上三大变量中:
局部把变量永远都不会存在线程安全问题,因为局部变量不共享。(一个线程一个栈)
局部变量在栈中。所以局部变量永远都不会共享

实例变量在堆中,堆只有1个。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的。所以可能存在线程安全问题

局部变量 + 常量:不会有线程安全问题
成员变量:可能会有线程安全问题

synchronized出现在实例方法上锁的只能是this
表示整个方法体都需要同步,导致程序执行效率较低,这种方式不常用
优点:只是节俭了代码
如果共享对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式

如果使用局部变量的话
建议使用:StringBuilder
因为局部变量不存在线程安全问题。
StringBuffer是非线程安全的。
 

总结:
synchronized有两种写法:
第一种:同步代码块 (灵活)
synchronized(线程共享对象) {
    同步代码块;
}

第二种:在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体

第三中:在静态方法上使用synchronized
表示找类锁。
类锁只有一个把。
就算创建了100个对象,那类锁也只有一把

以后开发中应该怎么解决线程安全问题?

synchronized会让线程的执行效率降低,用户体验不好。系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制

第一种方案:尽量使用局部变量代替"实例变量和静态变量"
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了(一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题)
第三种方案:如果不能使用局部变量,对象也不能创建多个,就只能选择synchronized了,线程同步机制

java语言中线程分为两大类
一类是:用户线程
一类是:守护线程(后台线程)
最具有代表性的就是:垃圾回收线程(守护线程)
main线程是用户线程

守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束守护线程自动结束。

守护线程用在什么地方呢?
每天00:00的时候系统数据自动备份。
这个需要用到定时器,并且我们可以将定时器设置为守护线程。一致在哪里看着,没到00:00的时候备份一次。所有用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。

定时器

定时器的作用
间隔特定的时间,执行特定的程序

在实际开发中,没隔多久执行一段特定的程序,这种需求是很常见的。
在java中其实有多种方式

可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。

在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。
不过这总方式在目前的开发中很少用,因为现在有很多高级框架都是支持定时任务的。

在实际开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

TimerTask实现了Runner接口

实现线程的第三种方式
FutureTask方式,实现Callable接口。(JDK8新特性)
这种方式实现的线程可以获取线程的返回值。
之前的两种方式是无法获取线程返回值,因为run方法返回void

思考:
系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,怎么能拿到这个执行结果呢
使用第三种方式:实现Callable接口方式

优点:获取到线程的执行结果
缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低
 

关于Object类中的wait和notify方法。(生产者和消费者模式)

a.wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为两个都是是Object类自带的
b.wait()方法作用
--让正在o对象上活动的线程进入等待状态直到被唤醒为止
--释放掉之前线程占有的o对象锁
 Object o = new Object();
 o.wait();
c.notify()方法作用
--唤醒o对象上等待的线程
--只是通知,不会释放o对象上之前占有的锁
 o.notify();
 

 生产者和消费者模式
 生产线程负责生产,消费线程负责消费
 生产线程和消费线程,需要达到均衡

这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法
wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题
 

/*
怎么创建线程对象?new就行了
怎么启动线程呢?调用线程对象的start()方法
 */
public class ThreadTest01 extends Thread{
    public static void main(String[] args) {
        // 创建一个分支线程对象
        MyThread myThread = new MyThread();

        // 启动线程
        // start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,折断代码任务完成之后,瞬间就结束了
        // 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了
        // 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)
        // run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的
        myThread.start();
        // 这里代码还是运行在主线程中。
        for(int i = 0; i < 1000; i++) {
            System.out.println("主线程-->" + i);
        }


    }
}

class MyThread extends Thread {
    // 编写程序,这段程勋运行在分支线程中(分支栈)
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++){
            System.out.println("分支线程-->" + i);
        }
    }
}
public class ThreadTest02 {
    public static void main(String[] args) {
        // 创建一个可运行的对象
        MyRunnable myRunnable = new MyRunnable();

        // 将可运行的对象封装成一个线程对象
        Thread thread = new Thread(myRunnable);
        thread.start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程-->" + i);
        }
    }
}

// java只是单继承 采用第一种无法继承别的类
class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程-->" + i);
        }
    }
}
/*
static Thread currentThread() 获取当前线程对象
    Thread.currentThread()
线程对象.getName();获取线程的名字
线程对象.setName();修改线程的名字

当线程没有设置名字的时候,默认的名字有什么规律
Thread-0
Thread-1
Thread-2
....
 */
public class ThreadTest03 {
    public static void main(String[] args) {
        MyThread2 t1 = new MyThread2();

        // 获取当前线程
        // 因为这个带吗出现在main方法,所有当前线程就是主线程
        Thread currentThread = Thread.currentThread();
        System.out.println(currentThread.getName());

        // 修改线程名字
        t1.setName("t1");
        // 获取线程的名字
        String name = t1.getName();
        System.out.println(name);
        // 启动线程
        t1.start();

        MyThread2 t2 = new MyThread2();
        t2.start();
    }
}

class MyThread2 extends Thread {
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        // 当前是哪个线程执行run方法 输出的就是哪个线程
        System.out.println(thread.getName());

        for (int i = 0; i < 100; i++) {
            System.out.println("分支线程" + i);
        }
    }
}
/*
关于线程的sleep方法:
static void sleep(long millis)
1.静态方法:Thread.sleep(1000);
2.参数是毫秒
3.作用:让当前线程进入休眠,进入"阻塞状态",放弃占有CPU时间片,让给其它线程使用。

*/
public class ThreadTest04 {
    public static void main(String[] args) {
        // 让当前线程进入睡眠,睡眠5s
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 5秒后执行这里的代码
        System.out.println("hello world!");

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);

            // 睡眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 关于Thread.sleep()方法的面试题
        // 创建线程对象
        MyThread3 t = new MyThread3();
        t.setName("t3");
        t.start();

        // 调用sleep方法
        try {
            // 这个代码会让线程t进入休眠状态吗
            // sleep是静态方法,在执行的时候还是会转换成:Thread.sleep(1000*5)
            // 也就是说会让main线程进入休眠
            t.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class MyThread3 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}
/*
结束睡眠
线程对象.interrupt();
*/
public class ThreadTest05 {
    public static void main(String[] args) {
        MyRunnable2 mr = new MyRunnable2();
        Thread t = new Thread(mr);

        t.setName("t1");

        // 希望5秒之后,t线程醒来
        try {
            Thread.sleep(1000 *5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 终断t线程的睡眠
        // 这种方式依靠了java的异常处理机制(让sleep抛异常,结束睡眠)
        t.interrupt(); // 干扰
    }
}

class MyRunnable2 implements Runnable {

    // 重点:run()当中的异常不能throws,只能try catch
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "-->" + "begin");
        // 此时的异常不能throws,因为throws不能比父类更大的异常
        // Runnable接口里面并没有throws异常。所以此时不能throws异常
        try {
            Thread.sleep(1000 * 60 * 60 * 60);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "-->" + "end");
    }
}
/*
线程对象.stop() 强行终止t线程
缺点:
这种方式直接将线程杀死了,容易丢数据

下面方法更安全
 */
public class ThreadTest06 {
    public static void main(String[] args) {
        MyRunnable3 r = new MyRunnable3();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();

        // 模拟5s
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 5s之后强行终止t线程
        // t.stop(); // 已经过时不建议使用

        // 终止线程
        // 你想要的睡眠时候终止t的执行,那么把标记改为false就行
        r.run = false;
    }
}

class MyRunnable3 implements Runnable {
    // 打一个布尔标记
    boolean run = true;

    @Override
    public void run() {
        if (run) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "-->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } else {
            // return 就结束了,结束之前还有什么没有保存的
            // 可以在这里离面保存
            // 终止当前线程
            return;
        }
    }
}
// 优先级
public class ThreadTest07 {
    public static void main(String[] args) {
        System.out.println("最高优先级" + Thread.MAX_PRIORITY);
        System.out.println("最低优先级" + Thread.MIN_PRIORITY);
        System.out.println("默认优先级" + Thread.NORM_PRIORITY);

        // 获取当前线程对象,获取当前线程的优先级
        Thread currentThread = Thread.currentThread();
        System.out.println(currentThread.getName() + "线程的默认优先级是:" + currentThread.getPriority());

        Thread t = new Thread(new MyRunnable4());
        t.setPriority(10);
        t.setName("t");
        t.start();

        // 优先级比较高的,只是抢到的CPU时间片相对多一些
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

class MyRunnable4 implements Runnable {

    @Override
    public void run() {
        // 获取线程有限级
        // System.out.println(Thread.currentThread().getName() + "线程的默认优先级:" + Thread.currentThread().getPriority());
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}
/*
线程让位
当前线程暂停,回到就绪状态,让给其他线程
静态方法
Thread.yield();
 */
public class ThreadTest08 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable5());
        t.setName("t");
        t.start();

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

class MyRunnable5 implements Runnable {

    @Override
    public void run() {
        // 获取线程有限级
        // System.out.println(Thread.currentThread().getName() + "线程的默认优先级:" + Thread.currentThread().getPriority());
        // 每100个让位一次
        for (int i = 0; i < 1000; i++) {
            if (i % 100 == 0) {
                Thread.yield(); //  当前线程结束让给主线程
            }
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}
/*
线程合并
 */
public class ThreadTest09 {
    public static void main(String[] args) {
        System.out.println("main begin");
        Thread t = new Thread(new MyRunnable6());
        t.setName("t");
        t.start();

        // 合并线程
        try {
            t.join(); // t合并到当前线程中,当前线受阻塞,t线程执行直到结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main over");
    }
}

class MyRunnable6 implements Runnable {

    @Override
    public void run() {
        // 获取线程有限级
        // System.out.println(Thread.currentThread().getName() + "线程的默认优先级:" + Thread.currentThread().getPriority());
        // 每100个让位一次
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

// 银行账户
// 多线程对同一个账户取款出现线程安全问题
// 可以使用线程同步机制结局线程安全问题
public class Account {
    // 账号
    private String actno;
    // 余额
    private double balance;

    Object obj = new Object();

    public Account() {
    }

    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    // 取款的方法
    // public synchronized void withdraw(double money) { // 此时锁的是this共享变量
    public void withdraw(double money) {
        
        Object obj2 = new Object();

        // 这里的共享对象是:账户对象。
        // 账户对象是共享的,那么那么this就是账户对象
        // 不一定是this,这里只要是多线程共享的那个对象就行
        // synchronized (obj) { // 此时obj是实例变量,属于共享变量。可以
        // synchronized (obj2) { // 此时obj2是局部变量,每次运行都会new对象,不属于共享变量。不可以
        // synchronized ("abc") { // "abc"在字符串常量池当中。可以。这样所有线程都会同步
        synchronized (this) {
            // 取款之前的余额
            double before = this.balance;
            // 取款之后的余额
            double after = before - money;
            // 模拟一下网络延迟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 更新余额
            // 没有加synchronized t1执行到这里了,还没有来得及执行这行代码。t2线程进来withdraw方法了。此时一定出问题
            this.setBalance(after);
        }
    }
}

///

public class AccountThread extends Thread {
    // 两个线程必须共享同一个账户对象
    private Account act = null;

    // 通过构造方法传递过来账户对象
    public AccountThread(Account act) {
        this.act = act;
    }

    @Override
    public void run() {
        // run方法的执行表示取款操作
        // 假如取款5000
        double money = 5000;
        // 取款
        act.withdraw(money);

        /* 也可以写在这里 只不过范围更大 效率会低
        synchronized (act) {
            act.withdraw(money);
        }
        */
        System.out.println("账户" + act.getActno() + "余额:" + act.getBalance());
    }
}

///

public class Test01 {
    public static void main(String[] args) {
        // 创建账户对象(只创建一个)
        Account act = new Account("act-001", 10000);
        // 创建两个线程
        AccountThread t1 = new AccountThread(act);
        AccountThread t2 = new AccountThread(act);
        // 设置name
        t1.setName("t1");
        t2.setName("t2");
        // 启动线程
        t1.start();
        t2.start();

    }
}
// 面试题
/*
doOther方法的执行是否需要等待doSome方法的结束
不需要 因为doOther方法没有synchronized t2线程不需要这把锁
没有排队
 */
public class Exam01 {
    public static void main(String[] args) {
        MyClass mc = new MyClass();

        Thread t1 = new MyThread(mc);
        Thread t2 = new MyThread(mc);

        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        // 这个睡眠是为了保证t1先执行
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

class MyThread extends Thread {
    private MyClass mc;
    public MyThread(MyClass mc) {
        this.mc = mc;
    }

    @Override
    public void run() {
        if(Thread.currentThread().getName() == "t1") {
            mc.doSome();
        }

        if(Thread.currentThread().getName() == "t2") {
            mc.doOther();
        }
    }
}

class MyClass {
    public synchronized void doSome() {
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void doOther() {
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}
// 死锁
public class DeadLock {
    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) {
                e.printStackTrace();
            }
            synchronized (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) {
                e.printStackTrace();
            }
            synchronized (o1) {

            }
        }
    }
}

// 守护线程
public class ThreadTest10 {
    public static void main(String[] args) {
        BakDataThread t = new BakDataThread();
        t.setName("备份数据的线程");
        // 启动之前,将线程设置为守护线程
        t.setDaemon(true);
        t.start();

        // 主线程是用户线程
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 备份数据
class BakDataThread extends Thread {
    @Override
    public void run() {
        int i = 10;
        while (true) {
            System.out.println(Thread.currentThread().getName() + "-->" + ++i);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
// JUC包下的,属于java的并发包,老JDK中没有这个包
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/*
实现线程的第三种方式
实现Callable
优点获取到线程的执行结果
缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低
 */
public class ThreadTest11 {
    public static void main(String[] args) throws Exception{
        // 第一步:创建一个"未来任务类"对象
        // 参数非常重要,需要给一个Callable接口实现类对象
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
                // 线程执行一个任务,执行之后可能会有一个执行结果
                // 模拟执行
                System.out.println("call method begin");
                Thread.sleep(1000 * 10);
                System.out.println("call method end!");
                int a = 100;
                int b = 200;
                return a + b; // 自动装箱(300编程Integer)
            }
        });

        // 创建线程对象
        Thread t = new Thread(task);

        t.start();

        // 这里是main方法,这是在主线程中
        // 在主线程中,怎么获取t线程的返回结果
        // get方法的执行会导致的当前线程阻塞
        Object obj = task.get();

        // main方法在这里的程序要想执行必须等待get()方法的结束
        // 而get()方法可能需要很久。因为get()方法时为了拿另一个线程的执行结果
        // 另一个线程执行是需要时间的
    }
}
import java.util.ArrayList;
import java.util.List;

/*
使用wait方法和notify方法实现"生产者和消费者模式"

模拟需求
仓库采用List集合
List集合中假设只能存储一个元素
一个元素就表示仓库满了
如果list集合中元素个数是0,就表示仓库空了
保证list集合中永远都是最多存储一个元素

必须做到这种效果:生产一个消费一个
 */
public class ThreadTest12 {
    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("消费者线程");


    }
}

// 生产线程
class Producer implements Runnable {
    // 仓库
    private List list;

    public Producer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        // 一直生产(使用死循环来模拟一直生产)
        while (true) {
            // 给仓库对象加锁
            synchronized (list) {
                if (list.size() > 0) {
                    // 当前线程进入等待状态,释放list集合的锁
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 能够执行到这里说明仓库是空的
                Object o = new Object();
                list.add(o);
                System.out.println(Thread.currentThread().getName() + "-->" + o);
                // 唤醒消费者消费
                list.notifyAll();
            }
        }
    }
}

// 消费线程
class Consumer implements Runnable {
    // 仓库
    private 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();
                System.out.println(Thread.currentThread().getName() + "-->" + o);
                // 唤醒生产者生产
                list.notifyAll();
            }
        }
    }
}

反射

反射机制有什么作用?
通过java语言的反射机制可以操作字节码文件。
优点类似于黑客。(可以读和修改字节码文件)

反射机制在哪个包下
java.lang.reflect.*;

反射机制相关重要的类有哪些?
java.lang.Class
代表字节文件,代表一个类型,代表整个类

java.lang.reflect.Method
代表字节码中的方法字节码。代表类中的方法

java.lang.reflect.Constructor : 
代表字节码中的构造方法字节码,代表类中的构造方法

java.lang.reflect.Field
代表字节码中的属性字节码。代表类中的成员变量(静态变量 + 实例变量)

获取一个类的字节码的三种方式
1.Class c = Class.forName("完整类名带包名");
2.Class c = 引用.getClass();
3.Class c = 任何类型.class

通过Class的newInstance()方法来实例化对象。
注意:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参数构造存在才可以。

指向方法区当中的字节码 

import java.util.Date;

/*
Class.forName()
静态方法
方法的参数是一个字符串
字符参数是一个完整类名
完整类名必须带有包名。java.lang包也不能省略
 */
public class ReflectTest01 {
    public static void main(String[] args) {
        // 第一种方式
        Class c1 = null;
        try {
            c1 = Class.forName("java.lang.String");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // 第二种方式
        // java中任何一个对象都有一个方法:getClass()
        String s = "abc";
        // x代表String.class字节码文件,x代表String类型。
        Class x = s.getClass();
        // c1和x中保存的内存你地址相同
        System.out.println(c1 == x); // true

        // 第三种:java语言中任何一种类型,包括基本数据类型,他都有.class属性
        Class z = String.class;
        Class k = Date.class;
        Class f = int.class;
        Class e = double.class;

        System.out.println(x == z); // true
    }
}
public class ReflectTest03 {
    public static void main(String[] args) {
        try {
            // Class.forName()这个方法执行会导致:类加载(类加载时,静态代码块会执行)
            Class.forName("com.thintime.demo03.MyClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class MyClass {
    // 静态代码块在啊类加载时执行,并且只执行一次
    static {
        System.out.println("MyClass类的静态代码块执行了!");
    }
}
public class Student {
    public int no;
    private String name;
    protected int age;
    boolean sex;
    private static final double MATH_PI = 3.14159267;
}
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

// 反射访问类中属性
public class ReflectTest04 {
    public static void main(String[] args) throws Exception {
        Class stuClass = Class.forName("com.thintime.demo03.Student");
        String className = stuClass.getName();
        System.out.println("完整类名:" + className);

        String simpleName = stuClass.getSimpleName();
        System.out.println("简类名:" + simpleName);

        // 获取类中属性
        Field[] fields = stuClass.getFields();
        System.out.println(fields.length); // 1 只有一个元素
        // 取出这个Field的名字
        Field f = fields[0];
        String fieldName = f.getName();
        System.out.println(fieldName); // no

        // 获取类中所有属性
        Field[] dFields = stuClass.getDeclaredFields();
        System.out.println(dFields.length); // 5

        for (Field field : dFields) {
            // 获取属性的类型
            Class type = field.getType();
            String typeName = type.getSimpleName();
            System.out.println(typeName);

            // 获取属性的名字
            String fieldName2 = field.getName();
            System.out.println(fieldName2);

            // 获取属性的修饰符列表
            // 返回的修饰符是一个数字,每个数字是修饰符的代号!
            int modifiers = field.getModifiers();
            System.out.println(modifiers);
            // 将修饰符的代号转换成字符串
            String modifierString = Modifier.toString(modifiers);
            System.out.println(modifierString);
        }
    }
}
public class ReflectTest05 {
    public static void main(String[] args) throws Exception{
        Class stuClass = Class.forName("com.thintime.demo03.Student");
        Object obj = stuClass.newInstance();

        // 使用反射机制访问对象的一个属性
        // 根据属性名称来获取Field
        Field noField = stuClass.getDeclaredField("no");

        // 给obj对象(Student对象)的no属性赋值
        noField.set(obj, 2222); // 给obj对象no属性赋值

        // 读取属性的值
        Object o = noField.get(obj);
        System.out.println(o);

        // 访问私有属性
        Field nameField = stuClass.getDeclaredField("name");
        // 打破封装
        nameField.setAccessible(true);
        // 给name属性赋值
        nameField.set(obj, "Tom");
        // 获取name的值
        System.out.println(nameField.get(obj)); // Tom
    }
}
import java.lang.reflect.Method;
/*
反射机制调用一个对象的方法

反射机制,让代码很具有通用性,可变化的内容都是写到配置文件中
将来修改配置之后,创建的对象不一样了,调用的方法也不同
 */
public class ReflectTest06 {
    public static void main(String[] args) throws Exception{
        // 获取类
        Class userServiceClass = Class.forName("com.thintime.demo03.UserService");

        // 获取所有的Method(包括私有的)
        Method[] methods = userServiceClass.getDeclaredMethods();

        // 遍历Method
        for (Method method : methods) {
            // 获取方法的返回值类型
            Class returnType = method.getReturnType();
            System.out.println(returnType.getSimpleName());

            // 获取方法名
            System.out.println(method.getName());
            // 获取方法的参数类型列表
            Class[] parameterTypes = method.getParameterTypes();
            for (Class type : parameterTypes) {
                System.out.println(type.getSimpleName());
            }

            // 获取方法
            Method login = userServiceClass.getDeclaredMethod("login", String.class, String.class);

            // 调用方法
            Object obj = userServiceClass.newInstance();

            Object returnValue = login.invoke(obj, "admin", "123");
            System.out.println(returnValue);
        }
    }
}

class UserService {

    /**
     * 登录方法
     * @param name 用户名
     * @param password 密码
     * @return true表示登录成功,false表示的登录失败
     */
    public boolean login(String name, String password) {
        if ("admin".equals(name) && "123".equals(password)) {
            return true;
        }
        return false;
    }

    // 退出系统的方法
    public void logout() {
        System.out.println("系统已经安全退出!");
    }
}
import java.lang.reflect.Constructor;
// 使用反射机制获取构造方法
public class ReflectTest07 {
    public static void main(String[] args) throws Exception{
        // 使用反射机制创建对象
        Class vipClass = Class.forName("com.thintime.demo03.Vip");
        // 调用无参构造方法
        Object obj = vipClass.newInstance();
        // 调用有参构造方法
        // 1.先获取到有参构造方法
        Constructor con = vipClass.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);
        // 2.调用构造方法创建对象
        Object tom = con.newInstance(100, "Tom", "2022-01-01", true);

        // 获取无参数构造方法
        Constructor con2 = vipClass.getDeclaredConstructor();
        Object newObj2 = con2.newInstance();

    }
}

class Vip {
    int no;
    String name;
    String birth;
    boolean sex;

    public Vip() {
    }

    public Vip(int no, String name, String birth, boolean sex) {
        this.no = no;
        this.name = name;
        this.birth = birth;
        this.sex = sex;
    }
}
/*
通过反射获取类的父类和接口
 */
public class ReflectTest08 {
    public static void main(String[] args) throws Exception{
        // String 类型
        Class strClass = Class.forName("java.lang.String");

        // 获取String的父类
        Class superclass = strClass.getSuperclass();
        System.out.println(superclass.getSimpleName());

        // 获取String类实现的所有接口
        Class[] strClassInterfaces = strClass.getInterfaces();
        for (Class in : strClassInterfaces) {
            System.out.println(in.getSimpleName());
        }
    }
}

关于JDK中自带的类加载器

什么是类加载器?
专门负责加载类的命令/工具 
ClassLoader

JDK中自带了3个类加载器
启动类加载器
扩展类加载器
应用类加载器

假如有代码:
String s = "abc";
代码开始执行之前,会将所需要类全部加载到JVM当中
通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载
首先通过"启动类加载器"加载。
注意:启动类加载器专门加载:jdk1.8.0_101\jre\lib\rt.jar
rt.jar中都是JDK最核心的类库。

如果通过"启动类加载器"加载不到的时候,会通过"扩展类加载器"加载
注意:"扩展类加载器"专门加载:jdk1.8.0_101\jre\lib\ext\*.jar

如果通过"扩展类加载器"加载不到的时候,会通过"应用加载器"加载
注意:"应用加载器"专门加载:classpath中的jar包(配置环境变量的路径)

java中为了保证类的加载安全,使用了双亲委派机制。
优先从启动类加载器中加载,这个称为"父"
"父"无法加载到,在从扩展类加载器中加载,这个称谓"母"
双亲委派。如果都加载不到,才会考虑从应用加载器中加载

import java.io.FileReader;

public class AboutPath {
    public static void main(String[] args) throws Exception {
        // 这种方式的路径缺点:移值性差,在IDEA中默认的当前路径是project的根路径
        // 这种代码假设离开了IDEA,换到其他位置,可能当前路径就不是project的根路径了
        FileReader fr = new FileReader("day04/testFile.properties");

        // 下面方式即使代码变化位置了,这样编写仍然是通用的
        // 注意:使用一下通用方式的前提是:这个文件必须在类路径下
        // 什么是类路径下?src下的都是类路径下
        // src是类的根路径
        /*
        Thread.currentThread() 当前线程对象
        getContextClassLoader() 线程对象的方法,可以获取到当前线程的类加载器对象。
        getResource() 类加载器对象的方法,当前线程类加载器默认从类的根路径下加载资源

         */
        String path = Thread.currentThread().getContextClassLoader().getResource("testFile2.properties").getPath();
        System.out.println(path); // /D:/workspace/javabase/out/production/day04/testFile2.properties

        // 从类的更路径下作为起点开始
        String path2 = Thread.currentThread().getContextClassLoader().getResource("com\\thintime\\demo03\\test.properties").getPath();
        System.out.println(path2); // /D:/workspace/javabase/out/production/day04/com%5cthintime%5cdemo03%5ctest.properties
    }
}
import java.util.ResourceBundle;

/*
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容
使用一下这种方式的时候,属性配置文件必须放到类路径下。
并且在写路径的时候,路径后面的扩展名不能写
 */
public class ResourceBundleTest {
    public static void main(String[] args) {
        ResourceBundle bundle = ResourceBundle.getBundle("testFile2");

        // className=com.thintime.demo03.User
        String className = bundle.getString("className");
        System.out.println(className); // com.thintime.demo03.User

        ResourceBundle bundle2 = ResourceBundle.getBundle("com\\thintime\\demo03\\test.properties");

    }
}

可变长度参数
int... args 这就是可变长度参数
语法是:类型... (注意:一定是3个点)

a.可变长度参数要求的参数个数是:0~N个
b.可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有一个
c.可变长度参数可以当做一个数组来看

/*
可变长度参数
 */
public class ArgsTest {
    public static void main(String[] args) {
        method1();
        method1(1);
        method1(1,2,3);
        method1(new int[]{1,2,3,5});

        method2("abc");
        method2("ccc", 2, 4); // 2 4
    }

    public static void method1(int... args) {
        System.out.println("m方法执行了");
    }

    public static void method2(String s, int... args) {
        System.out.println("m方法执行了");

        for (int arg : args) {
            System.out.print(arg);
        }
    }
}


注解

或者叫注释,英文单词是:Annotation
注解Annotation是一种引用数据类型,编译后也是生成xxx.class  

注解的属性类型可以是:
byte short int long float double boolean char String Class 枚举类型
以及以上每一种的数据形式

怎么自定义注解呢?语法格式?
[修饰符列表] @interface 注解类型名 {

}

注解怎么使用,用在什么地方?
1.注解使用时的语法格式是:
    @注解类型名
2.注解可以出现在类上、属性上、方法上、变量上等... 
    注解还可以出现在注解类型上

JDK内置的注解
java.lang包下面的注释类型:
a.Deprecated用 @Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它危险或存在更好的选择
b.Overrite 表示一个方法声明打算重写超类的另一个方法声明
c.SuppressWarings 只是应该在注释元素(已经包含在该注释元素中的所有程序元素)中却小现实指定的编译器警告

@Overrite注解只能注解方法

元注解
用来标注“注解类型”的“注解”

常见的元注解
Target
Retention

关于Target注解:
用来标注“被标注的注解”可以出现在哪些位置上

关于Retention注解:
用来标注“被标注的注解”最终保存在哪里
@Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中
@Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中
@Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取

如果一个注解的属性名字是value的话,并且只有个一个属性。在使用的时候,该属性名可以省略
 

/*
 自定义注解:MyAnnotation
 */

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 只允许该注解只可以标注类、方法
@Target({ElementType.METHOD,ElementType.TYPE})
// 希望这个注解可以被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    /*
    通常在注解当中可以定义属性,以下这个是MyAnnotation的name属性
    看着像一个方法,但实际还是那个称之为属性name
     */
    String name();

    String color();

    int age() default 25; // 属性指定默认值

}
///

public @interface MyAnnotation2 {
    // 当属性为value时候,注解的属性名可以不加
    String value();
}

///
import java.lang.reflect.Method;

public class AnnotationTest01 {
    // 如果一个注解当中有属性,那么必须给属性赋值
    // 有默认值的可以不加
    @MyAnnotation(name = "Tom", color = "Green")
    public void doSome() {
        System.out.println("doSome...");
    }

    // // 当属性为value时候,注解的属性名可以不加
    @MyAnnotation2("abc")
    public void doOther() {
        System.out.println("doOther...");
    }

    public static void main(String[] args) throws Exception{
        Class myAnn = Class.forName("com.thintime.demo01.MyAnnotation");
        // 判断类上面是否有@MyAnnotation
        System.out.println(myAnn.isAnnotationPresent(MyAnnotation.class));

        Class annTest = Class.forName("com.thintime.demo01.AnnotationTest01");
        Method doSome = annTest.getDeclaredMethod("doSome");
        // 判断该方法上是否存在这个注解
         if (doSome.isAnnotationPresent(MyAnnotation.class)) {
             MyAnnotation ann = doSome.getDeclaredAnnotation(MyAnnotation.class);
             System.out.println(ann.name());
             System.out.println(ann.color());
         }
    }
}

// 表示这个类已经过时
@Deprecated
public class AnnotationTest03 {
    public static void main(String[] args) {
        AnnotationTest03.doSome();
    }

    // 表示这个方法已经过时
    @Deprecated
    public static void doSome() {
        System.out.println("doSome...");
    }

    public static void doOther() {
        System.out.println("doOther...");
    }
}

这篇关于Java多线程,反射的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!