首页 > 技术文章 > JavaSE学习笔记24:多线程(二)

Sona-36D 2020-11-25 16:24 原文

多线程(二)

线程的生命周期

线程的生命周期有:新建状态、就绪状态、运行状态、阻塞状态、死亡状态

获取线程对象、名字、修改线程名

package se5.thread;
/*
1.怎么获取当前线程对象
    Thread t = Thread.currentThread();
    返回值t就是当前线程
2.获取线程对象的名字:String name = 线程对象.getName();
3.修改线程对象的名字:线程对象.setName("线程名字");
4.当线程没有设置名字的时候,默认的名字的规律如下:
    Thread-0
    Thread-1
    Thread-2
    ...
 */
public class ThreadTest05 {
    public static void main(String[] args) {

        //currentThread就是当前线程对象
        //这个代码出现在main方法中,所以当前线程就是主线程
        Thread currentThread = Thread.currentThread();
        System.out.println(currentThread.getName());//main
        //创建线程对象
        MyThread2 t = new MyThread2();
        //设置(修改)线程的名字,若不设置线程的名字是:Thread-0
        t.setName("t1");
        //获取线程的名字
        String tName = t.getName();
        System.out.println(tName);//t1

        MyThread2 t2 = new MyThread2();
        t2.setName("t2");
        System.out.println(t2.getName());//t2
        //启动线程
        t2.start();
        t.start();
        /**
         * 部分执行结果:
         * t1--->0
         * t2--->19
         * t1--->1
         * t2--->20
         */
    }
}
class MyThread2 extends Thread{
    public void run(){
        for (int i = 0; i < 100; i++) {
            //currentThread就是当前线程对象
            //当t1线程执行run方法,当前线程就是t1
            //当t2线程执行run方法,当前线程就是t2
            Thread currentThread = Thread.currentThread();
            System.out.println(currentThread.getName() + "--->" + i);
            //以下方式也可以,不过有局限性
            //System.out.println(super.getName() + "--->" + i);
            //System.out.println(this.getName() + "--->" + i);
            /**
             * 这样不行:
             *  this.getName();
             *  super.getName();
             * 这样可以:
             *  Thread.currentThread().getName();
             */
        }
    }
}

线程的sleep方法

  1. sleep(时间):指定当前线程阻塞的毫秒数
  2. sleep存在异常InterruptedException
  3. sleep时间达到后线程进入就绪状态
  4. sleep可以模拟网络延时,倒计时等
  5. 每一个对象都有一个锁,sleep不会释放锁
package se5.thread;
/*
关于线程的sleep方法
    static void sleep(long millis)
    1.静态方法
    2.参数是毫秒
    3.作用:让当前线程进入休眠,进入阻塞状态,放弃占有CPU时间片,让给其他线程使用
        这行代码出现在A线程中,A线程就会进入休眠
        这行代码出现在B线程中,B线程就会进入休眠
    4.Thread.sleep()方法,可以做到这种效果:
        间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
 */
public class ThreadTest06 {
    public static void main(String[] args) {

        //让当前线程进入休眠,睡眠5秒钟
        //当前线程是主线程!!!
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5秒之后执行这里的代码
        //System.out.println("Hello World!");

        for (int i = 1; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
            //睡眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

面试题

package se5.thread;
/*
关于Thread.sleep方法的面试题
 */
public class ThreadTest07 {
    public static void main(String[] args) {
        //创建线程对象
        Thread t = new MyThread3();
        t.setName("t");
        t.start();

        //调用sleep方法
        try {
            //问题:这行代码会让线程t进入休眠状态吗?不会!!!
            t.sleep(1000 * 5);//在执行的时候还是转换成Thread.sleep(1000 * 5);
                                    //这行代码的作用:让当前线程进入休眠,也就是说main线程进入休眠
                                    //这行代码出现在main方法中,main线程睡眠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Hello World!");
    }
}
class MyThread3 extends Thread{
    public void run(){
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName()+ "--->" +i);
        }
    }
}

案例:模拟倒计时

package com.thread2;

//模拟倒计时
public class TestSleep2 {

    public static void main(String[] args) {
        try {
            tenDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void tenDown() throws InterruptedException {

        int num = 10;
        while (true){
            Thread.sleep(1000);
            System.out.println(num--);
            if (num <= 0){
                break;
            }//从10开始倒计时
        }
    }
}

案例:龟兔赛跑

​ 注意:故事中乌龟是赢家,兔子需要睡觉,所有我们需要模拟兔子睡觉(兔子:MMP)

package com.thread;
//模拟龟兔赛跑
public class Race implements Runnable{

    //胜利者
    private static String winner;

    @Override
    public void run() {

        for (int i = 1; i <= 100; i++) {
            //模拟兔子休息
            if (Thread.currentThread().getName().equals("兔子") && i % 10 ==0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean flag = gameOver(i);
            //如果比赛结束了,就停止程序
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName() + "--->跑了" + i + "步");
        }
    }
    //判断是否完成比赛
    private boolean gameOver(int steps) {
        if (winner != null) {
            return true;
        }
        {
            if (steps == 100) {
                winner = Thread.currentThread().getName();
                System.out.println("Winnner is" + winner);
                return true;

            }
        }
        return false;
    }
    public static void main(String[] args) {

        Race race = new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
        /**
         * 本次运行结果:
         * ...
         * 乌龟--->跑了9步
         * 兔子--->跑了8步
         * 乌龟--->跑了10步
         * 兔子--->跑了9步
         * 乌龟--->跑了11步
         * ...
         *乌龟--->跑了98步
         *乌龟--->跑了99步
         * Winnner is乌龟
         */
    }
}

静态代理

package com.thread2;

public class StaticProxy {
    public static void main(String[] args) {
        WeddingCompany weddingCompany = new WeddingCompany(new You());
        weddingCompany.HappyMarry();
        /**
         * 输出:
         * 结婚之前,布置现场
         * 阿波要结婚了,超开心!
         * 结婚之后,收尾款
         */
    }
}
interface Marry{

    void HappyMarry();
}
//真实角色,你去结婚
class You implements Marry{

    @Override
    public void HappyMarry() {
        System.out.println("阿波要结婚了,超开心!");
    }
}
//代理角色,帮助你结婚
class WeddingCompany implements Marry{
    //代理谁--->真实目标角色
    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();//这就是真实对象
        after();
    }
    private void after() {
        System.out.println("结婚之后,收尾款");
    }
    private void before() {
        System.out.println("结婚之前,布置现场");
    }
}

静态代理总结

  1. 真实对象和代理对象都要实现同一个接口
  2. 代理对象要代理真实角色

好处:

  1. 代理对象可以做很多真实对象做不了的事情

  2. 真实对象专注做自己的事情

线程停止

终止线程的睡眠

package se5.thread;
/*
sleep睡眠太久了,如果希望半道上醒来,怎么办?如何叫醒一个正在睡眠的线程?
    注意:这个不是终断线程的执行,是终止线程的睡眠
 */
public class ThreadTest08 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable2());
        t.setName("t");
        t.start();
        //希望5秒之后,t线程醒来
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制)
        t.interrupt();//干扰
        /**
         * 运行结果:
         * t--->begin
         * java.lang.InterruptedException: sleep interrupted
         *     at java.lang.Thread.sleep(Native Method)
         *     at se5.thread.MyRunnable2.run(ThreadTest08.java:29)
         *     at java.lang.Thread.run(Thread.java:748)
         * t--->end
         * 注意:若不想看的异常信息可以去掉下面的e.printStackTrace();
         */
    }
}
class MyRunnable2 implements Runnable{
    //重点:run方法当中的异常不能throws,只能try catch
    //因为run方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--->begin");
        //睡眠1年
        try {
            Thread.sleep(1000 * 60 *24 * 365);
        } catch (InterruptedException e) {
            //打印异常信息,若不想看到异常信息,可以去掉
            e.printStackTrace();
        }
        //1年之后才会执行这里
        System.out.println(Thread.currentThread().getName() + "--->end");
    }
}

强制终止线程的执行

package se5.thread;
/*
在java中如何强行终止一个线程
stop:这种方式有很大的缺点,容易丢失数据,因为这种方式直接将线程杀死了,线程没有保存的数据会丢失,不建议使用。
仅作为了解
 */
public class ThreadTest09 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable3());
        t.setName("t");
        t.start();
        //模拟5秒
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5秒之后,强行终止t线程
        t.stop();//已过时,不建议使用!!!
    }
}
class MyRunnable3 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

合理的终止线程

package se5.thread;
/*
合理的终止一个线程,这种方式是很常用的
 */
public class ThreadTest10 {
    public static void main(String[] args) {
        MyRunnable4 r = new MyRunnable4();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();
        //模拟5秒
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //终止线程
        //想什么时候终止t的执行,把标记修改为false,就结束了
        r.run = false;
        /*
         ...
         t--->3
		t--->4
		t--->5		
		Process finished with exit code 0
		五秒后线程停止~
        */
    }
}
class MyRunnable4 implements Runnable{

    //打一个布尔标记
    boolean run = true;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (run){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                //return 就结束了,在结束之前还有什么没保存的,在这里可以保存
                //save...
                //终止当前线程
                return;
            }
        }
    }
}

线程礼让

  1. 礼让线程,让当前正在执行的线程暂停,但不阻塞
  2. 将线程从运行状态转为就绪状态
  3. 让CPU重新调度,礼让不一定成功,看CPU心情

注意:在回到就绪之后,有可能会再次抢到

package com.thread2;
/*
测试礼让线程
 */
public class TestYield {
    public static void main(String[] args) {

        MyYield myYield = new MyYield();
        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();
        /*
        礼让的结果:			未礼让的结果:
        a线程开始执行			a线程开始执行
        b线程开始执行			a线程停止执行
        b线程停止执行			b线程开始执行
        a线程停止执行          b线程停止执行 
        */
    }
}
class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开始执行");
        Thread.yield();//礼让
        System.out.println(Thread.currentThread().getName() + "线程停止执行");
    }
}

合并线程

Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

package se5.thread;
/*
线程合并
 */
public class ThreadTest13 {
    public static void main(String[] args) {
        
        System.out.println("main begin");
        Thread t = new Thread(new MyRunnable7());
        t.setName("t");
        t.start();
        //合并线程
        try {
            t.join();//t合并到当前线程,当前线程受阻塞,t线程执行直到结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main over");
        /**
         * main begin
         * t--->1
         * t--->2
         * t--->3
         * ...
         * t--->8
         * t--->9
         * t--->10
         * 这里1-10执行完后main over才会继续执行
         * main over
         */
    }
}
class MyRunnable7 implements Runnable{

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

线程调度概述(了解)

常见的线程调度模型有哪些?

  1. 抢占式调度模型:

    哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些

    java采用的就是抢占式调度模型

  2. 均分式调度模型:

    平均分配CPU时间片,每个线程占用CPU时间片时间长度一样

    平均分配,一切平等

线程调度的方法(了解)

实例方法:

  1. void setPriority(int newPriority) 设置线程的优先级
  2. int getPriority() 获取线程优先级

​ 最低优先级1,默认优先级是5,最高优先级是10

package com.thread2;
//测试线程的优先级
public class TestPriority {
    public static void main(String[] args) {
        //主线程默认优先级
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        Thread t6 = new Thread(myPriority);

        System.out.println("最高优先级" + Thread.MAX_PRIORITY);//最高优先级10
        System.out.println("最低优先级" + Thread.MIN_PRIORITY);//最低优先级1
        System.out.println("默认优先级" + Thread.NORM_PRIORITY);//默认优先级5
        System.out.println("#############################################");

        //先设置优先级,再启动
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY = 10
        t4.start();

        t5.setPriority(8);
        t5.start();

        t6.setPriority(7);
        t6.start();
        /**
         * main--->5
         * 最高优先级10
         * 最低优先级1
         * 默认优先级5
         * #############################################
         * Thread-3--->10
         * Thread-0--->5
         * Thread-4--->8
         * Thread-5--->7
         * Thread-2--->4
         * Thread-1--->1
         */
    }
}
class MyPriority implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
    }
}

观测线程

线程状态:

  1. NEW:尚未启动的线程处于次状态
  2. RUNNABLE:在Java虚拟机中执行的线程处于次状态
  3. BLOCKED:被阻塞等待监听器锁定的线程处于此状态
  4. WAITING:正在等待另一个线程执行特定动作的线程处于次状态
  5. TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于次状态
  6. TERMINATED:已退出的线程处于此状态
package com.thread2;
//观察测试线程的状态
public class TestState {
    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("*****");
        });
        //观察状态
        Thread.State state = thread.getState();
        System.out.println(state);//NEW

        //观察启动后
        thread.start();//启动线程
        thread.getState();
        System.out.println(state);//Run

        while (state != Thread.State.TERMINATED){//只要线程不终止,就一直输出状态
            Thread.sleep(100);
            state = thread.getState();//更新线程状态
            System.out.println(state);//输出状态
        }
        /*
        thread.start();
        死亡之后的线程不能再次启动,会报错
         */
    }
}

守护线程

java语言中线程分为两大类:

  1. 一类是用户线程

  2. 一类是守护线程(后台线程),其中具有代表性的就是垃圾回收线程

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

注意:主线程main方法是一个用户线程。

package se5.thread;
//守护线程
public class ThreadTest14 {
    public static void main(String[] args) {

        Thread t = new Thread();
        t.setName("备份数据的线程");
        //启动线程之前将线程设置为守护线程
        t.setDaemon(true);
        //若没有设置守护线程,main线程执行完后,备份数据的线程会陷入死循环,一直执行!!!
        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{
    public void run(){
        int i = 0;
        //即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止
        while (true){
            System.out.println(Thread.currentThread().getName() + "--->" + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

定时器

作用:间隔特定的时间,执行特定的程序。例如:每周要进行银行账户的总账操作,每天要进行数据的备份操作。在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的。

在java中其实可以采用多种方式实现:

  1. 可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
  2. java类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。在实际开发中,目前使用较多的是Spring框架中提供的SpringTask框架。这个框架只要进行简单的配置,就可以完成定时器的任务。
package se5.thread;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

//使用定时器指定定时任务
public class TimerTest {
    public static void main(String[] args) throws Exception {
        //创建定时器对象
        Timer timer = new Timer();
        //Timer timer = new Timer(true);守护线程的方式

        //指定定时任务
        //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime = sdf.parse("2020-11-24 15:58:30");
        timer.schedule(new LogTimerTask(),firstTime,1000 * 10);//这里间隔10秒
        /**
         * 运行结果:
         * 2020-11-24 15:58:53成功完成了一次数据备份!
         * 2020-11-24 15:59:03成功完成了一次数据备份!
         * 2020-11-24 15:59:13成功完成了一次数据备份!
         * 2020-11-24 15:59:23成功完成了一次数据备份!
         * 2020-11-24 15:59:33成功完成了一次数据备份!
         */
    }
}
//编写一个定时任务类
//假设这是一个记录日志的定时任务
class LogTimerTask extends 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 + "成功完成了一次数据备份!");
    }
}

推荐阅读