week04_day04_Thread01-2_Thread相关API&&Runnable接口&&线程安全

设置或获取线程的名称
public final String getName()
public final void setName(String name)

思考,如何获取main线程的名称呢?
static Thread currentThread() //返回对当前正在执行的线程对象的引用。

······················································································································································································································

线程调度
假设在单CPU的情况下,线程的两种主要调度模型
协同式线程调度(Cooperative Thread-Scheduling)
抢占式调度(Preemptive Thread-Scheduling)

Java使用哪种调度模型?抢占式调度
在这里插入图片描述
多线程的优先级
public final int getPriority() 返回线程的优先级。
public final void setPriority(int priority) 更改线程的优先级。
注意事项:
1. 线程的默认优先级的5
2. 多线程的优先级的取值范围1 <= priority <=10
3. 然而,我们在java语言中设置的线程优先级,它仅仅只能被看做是一种"建议"(对操作系统的建议),
实际上,操作系统本身,有它自己的一套线程优先级 (静态优先级 + 动态优先级)
所以记住:千万不要试图,利用线程优先级,来间接控制线程调度的顺序,换句话说线程优先级不是一种稳定的调节手段
java官方: 线程优先级并非完全没有用,我们Thread的优先级,它具有统计意义,总的来说,高优先级的线程占用的cpu执行时间多一点,低优先级线程,占用cpu执行时间,短一点

注意事项:
1. 多个线程,交替执行
2. “同时执行”
a. 并发: 同一时间段内,同时执行(但是同一时间点,可能并没有同时执行)
b. 并行: 同一时间点, 同时执行

······················································································································································································································

  • public static native void sleep(long millis)
  • 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  • millis - 以毫秒为单位的休眠时间。
  • native表示这个方法底层实现不是用java语言实现的
            // 让当前线程休眠3秒,因为sleep运行在子线程中,所以就是让子线程休眠3秒
            Thread.sleep(3000);

            //还有另外一种让当前线程休眠的方式  TimeUnit 时间单位
            TimeUnit.SECONDS.sleep(3);

public final void join()

  • (谁)等待(谁)该线程终止。
  • 谁等待谁终止?
  1. 调用该方法的线程等待
  2. 调用的是哪个线程对象的join方法,就等待哪个线程的run方法执行完毕
    本例中,main线程等待joinThread线程的run()执行完毕
    在这里插入图片描述

public static void yield() (礼让方法)不重要(了解即可)

  1. 暂停当前正在执行的线程对象
  2. 并执行其他线程。(yield方法实现不了该功能)
    因为执行其他线程是由操作系统决定的,当前线程yield()后,将处于阻塞状态,而CPU会空闲后会从当前阻塞队列中取一个线程出来执行,所以下一个执行的线程依然可能是本线程。

public final void setDaemon(boolean on)

  • 1.将该线程标记为守护线程(参数为true)或用户线程(简单理解就是我们用户自己创建的线程,之前学的都是用户线程)(参数为false)。如果为 true,则将该线程标记为守护线程
  • 2.该方法必须在启动线程前调用
  • 3.我们创建的普通线程,都不是守护线程

守护线程的执行特征:

  • 当正在运行的线程都是守护线程时,Java虚拟机退出。
  • 守护线程:守护 非守护线程
    比如: 垃圾回收其所在的线程,就是一个典型的守护线程
    只要有普通的用户线程在执行,就有可能创建对象,有创建对象,对象就有可能变成垃圾,
    你产生垃圾,我就默默的帮你回收垃圾。

······················································································································································································································
public void interrupt() 中断线程。
如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,
或者该类(Thread)的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int)
方法过程中受阻,它还将收到一个 InterruptedException。

我的理解:为啥要将一个线程打断?可能是因为这个线程占用的时间太长了,一直在sleep,或者线程里面有死循环。(相当于一个应用程序卡了,我们将其结束进程),但你打断完,还要要求线程做一些工作(如回收资源等),就写在catch后

InterruptedException: 有啥意义

  • 该异常,其实是针对,被异常终止的线程(被interrupt打断),给它提供了一个机会,能够在线程异常终止的情况下(线程被打断后),仍然能够正确执行一些资源处理的工作

如果线程并不处于休眠状态也可以用interrupt对其打断对吧?

  1. 中断一个没有处于阻塞状态的线程不会有任何效果
class InterruptedThread extends Thread {
    @Override
    public void run() {
        System.out.println("begin");
        //依然会输出0到99
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

  1. 但是,如果在该线程对象正在执行sleep, join, wait等方法正处于阻塞状态,然后在该对象上调用interrupt方法之后,就会抛出InterruptedException异常。
class InterruptedThread extends Thread {
    @Override
    public void run() {
        System.out.println("begin");
        //测试,在线程被中断之后,就不能再进入阻塞状态了
        try {
            Thread.sleep(3000);
            System.out.println("休眠3秒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

······················································································································································································································
线程的状态转化:
新建:线程处于刚刚创建的状态
就绪:有执行资格,等待cpu调度获得执行权
运行:取得执行权,正在cpu上执行
阻塞:无执行资格,无执行权
死亡:线程正常或异常终止(run()方法执行完毕),线程对象成为垃圾,等待垃圾回收器回收
在这里插入图片描述
······················································································································································································································

实现线程的第二种方式:

  1. 定义实现Runnable接口的子类
  2. 实现Runnable接口的run方法
  3. 创建该子类对象
  4. 在创建Thread对象的时候,将创建好的Runnable子类对象作为初始化参数,传递给Thread对象
  5. 启动Thread对象(启动线程)

注意事项:

  1. 我们Runnable接口子类的run()方法代码,会运行在子线程当中

  2. 所以,在线程的第二种实现方式中,我们自己定义子类,实现Runnable接口的run方法,将要在子线程中执行的代码,放在run()方法中。

  3. 但是,Runnable子类对象,并不代表线程,它只代表,要在线程中执行的任务。

我们认为,从逻辑上说,第二种方法逻辑十分清晰:

  1. 线程就是一条执行路径,至于在线程这条执行路径上,究竟执行的是什么样的具体代码,应该和线程本身没有关系的
  2. 也就是说,线程,和在线程(执行路径)执行的任务应该是没有什么直接关系的
  3. 线程实现的第二种方式,把线程(Thread对象代表线程) 和在 线程上执行的任务(Ruannable子类对象) 分开

Runnable的run方法运行在子线程thread中。
在这里插入图片描述
······················································································································································································································
再次粘贴出方式一的代码:
Thread实现方式一:
1.继承Thread
2.重写子类的run方法
3.创建该子类的对象
4.启动线程 start()

方式二一 VS 方式二:
1. 方式一实现步骤较方式二少
2. 方式一的实现方式,存在单重继承的局限性 方式一是继承父类,方式二是实现接口
3. 方式二将线程和任务解耦。将线程和线程执行的任务分开了
4. 方式二,便于多线程数据的共享

关于数据共享问题,看一个题目:
假设A电影院正在上映某电影,该电影有100张电影票可供出售,现在假设有3个窗口售票。请设计程序模拟窗口售票的场景。

分析:
3个窗口售票,互不影响,同时进行。
3个窗口共同出售这100张电影票

package com.cskaoyan.thread.threadsafe;

/**
 * @author shihao
 * @create 2020-04-30 22:59
 *
假设A电影院正在上映某电影,该电影有100张电影票可供出售,现在假设有3个窗口售票。请设计程序模拟窗口售票的场景。
 *
 * 1. 3个窗口要用3个线程来模拟
 * 2. 3个共同售卖100张票(数据共享)
 */
public class Demo01 {

    public static void main(String[] args) {
        //用线程的第一种方式实现,变量的共享
        firstType();

        //利用线程的第二种实现方式,方便的实现了,多线程的数据共享
        //secondType();

    }

    private static void secondType() {
        SalesTask salesTask = new SalesTask();

        //三个线程(执行路径)调用一个实现Runnable接口的子类的对象(执行一个任务)
        Thread window1 = new Thread(salesTask, "窗口1");
        Thread window2 = new Thread(salesTask, "窗口2");
        Thread window3 = new Thread(salesTask, "窗口3");
        window1.start();
        window2.start();
        window3.start();
    }

    private static void firstType() {
        WindowThread window1 = new WindowThread("窗口1");
        WindowThread window2 = new WindowThread("窗口2");
        WindowThread window3 = new WindowThread("窗口3");
        window1.start();
        window2.start();
        window3.start();
    }

}

//第一种实现方式
class WindowThread extends Thread {
    //表示待售卖的100张票
    //加上static修饰符后就实现了数据共享
    static int tickets = 100;

    public WindowThread(String name) {
        super(name);
    }

    @Override
    public void run() {

        //有票,就卖票
        while (tickets > 0) {
            //模拟售票动作
            System.out.println(getName() + "卖出第" + tickets-- + "张票");
        }


    }
}

//线程的第二种实现方式
class SalesTask implements Runnable {

    //表示待售卖的100张票
    int tickets = 100;


    @Override
    public void run() {

        //有票,就卖票
        while (tickets > 0) {
            //模拟售票动作
            System.out.println(Thread.currentThread().getName() + "卖出第" + tickets-- + "张票");
            //无法再用this.getName了,因为方式一中的getName是Thread的getName()方法。
            //Thread.currentThread(),当前运行的线程
        }
    }
}

关于第一种方式中的static:
回顾week02_day03_static中:
三、static关键字的特点:

  1. 被类的所有 对象所共享:(判定是否使用static的关键)
    a. 当static修饰了成员变量,该成员变量的值,就不在存储与对象中了,而是单独存储了一份,被类的所有对象所共享。
    b. 当static修饰成员方法的时候,该方法被当前类的所有对象共享当前类对象.方法(和普通成员方法从共享的角度,几乎没有太大区别)

  2. 可以通过类名访问
    a. 可以通过类名直接访问,static成员变量的值
    如:Student.SchoolName
    b. 直接通过类名直接调用,static成员方法
    如:Student.getSchoolName();

  3. 随着类的加载而加载
    a. static成员变量,随着类加载过程,其实就已经在方法区中,分配了内存
    b. static成员方法, 一旦类加载完毕,我们就可以直接访问,static方法,而不必创建对象,然后在对象名. 访问方法

  4. 优先于对象而存在,不依赖于对象而存在
    a. 成员变量的角度来理解,static修饰的成员变量,不在依赖于对象而存在,因为static修饰的成员变量的值,不在存储在,该类的每个对象中。作为对比,没有被static修饰的成员变量,都依赖于对象而存在,因为他们的值,都存储在对象中
    b. 成员方法角度,被static修饰的成员方法,在没有对象存在的情况下,也可以直接通过类名来调用方法,作为对比,没有被static修饰的,普通成员方法,它依赖于对象而存在, 原因是,普通成员方法中,可以访问,普通成员变量的值,而普通对象的值又是依赖于对象而存在的

  5. 先出现在内存
    静态成员变量, 一定先于没有被static修饰的普通成员变量,出现在内存中。因为静态成员变量随着类的创建而创建,随着类的销毁而销毁,而普通成员变量随着对象的创建而创建,随着对象的销毁而销毁,而一定是先有类后才有对象的。

在这里插入图片描述

············································································································································································································

  • 在原有代码基础上,增加了售票延迟之后(之所以增加延迟效果,是因为延迟时调用sleep()方法,当前线程进入阻塞状态,给了其他线程占用CPU的机会,此时就会发生重复读),我们就发现了售票过程中两种错误
package com.cskaoyan.thread.threadsafe;

/**
 * @author shihao
 * @create 2020-04-30 23:55
 *
假设A电影院正在上映某电影,该电影有100张电影票可供出售,现在假设有3个窗口售票。请设计程序模拟窗口售票的场景。
 *
 * 1. 3个窗口要用3个线程来模拟
 * 2. 3个共同售卖100张票(数据共享)
 *
 * 在原有代码基础上,增加了售票延迟之后,我们就发现了售票过程中两种错误
 * 1. 相同的票被不同的窗口,多次售出(多卖问题)
 * 2. 售出了不存在的票(超卖问题)
 *
 * 分析,代码中以上两种问题,出现的原因
 * 1. 多卖问题
 *  窗口1卖出第94张票
窗口3卖出第94张票
窗口2卖出第94张票

2. 超卖问题
窗口2卖出第1张票
窗口1卖出第0张票
窗口3卖出第-1张票

无论是多卖还是超卖问题,本质上都是多线程的数据安全问题:
多线程的数据安全问题是指:在多线程共享数据前提现,在访问共享数据的时候,访问到了错误的共享数据

 *
 *
 */
public class Demo02 {

    public static void main(String[] args) {
        SalesTask2 salesTask = new SalesTask2();
        Thread window1 = new Thread(salesTask, "窗口1");
        Thread window2 = new Thread(salesTask, "窗口2");
        Thread window3 = new Thread(salesTask, "窗口3");
        window1.start();
        window2.start();
        window3.start();
    }

}

//线程的第二种实现方式
class SalesTask2 implements Runnable {

    //表示待售卖的100张票
    int tickets = 100;


    @Override
    public void run() {

        //有票,就卖票
        while (tickets > 0) {

            //超卖问题分析:
            // tickets 1
            // 窗口2线程:发现tickets的值为1 > 0,进入while循环, 发生了线程切换 ->窗口1
            // 窗口1线程:发现tickets的值为1 > 0,进入while循环, 发生了线程切换 ->窗口3
            // 窗口3线程: 发现tickets的值为1 > 0,进入while循环,


            //增加售票延迟
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //分析多卖问题
            // tickets-- 分3步完成(没有实现原子操作):  1.读取tickets变量的值;  2. tickets - 1;  3. tickets = tickets - 1
            // tickets 的是 94
            // 窗口1: 假设首先窗口1准备售票,拼接结果字符串:窗口1卖出第94, tickets - 1,此时线程切换->窗口3
            // 窗口3: 假设窗口3准备售票,拼接结果字符串:窗口3卖出了第94,线程切换 -> 2
            // 窗口2: 窗口2准备售票,拼接结果字符串;窗口2卖出第94

            //模拟售票动作
            System.out.println(Thread.currentThread().getName() + "卖出第" + tickets-- + "张票");
        }

    }
}

············································································································································································································
作业:

  1. 自己利用线程的第一种实现方式,实现如下功能(多线程引例):

a. 程序不停地在屏幕上输出一句问候的语句(比如“你好”)
(时间间隔一定要大些比如3s(或大于3s),因为在控制台窗口,输入和输出不能同时发生,
我们只能在两次输出“你好”的间隙,从键盘输入数据,才能保证键盘输入被正确接收)

b.同时,当我通过键盘输入固定响应的时候,程序停止向屏幕输出问候的语句

public class MultiThread {

 static boolean flag = true;

 public static void main(String[] args) throws InterruptedException {
   sayHelloRecycling();
   waitToStop();
   System.out.println("main end");
 }

 /**
  *  在子线程中,接收键盘输入,并根据键盘输入
  *  ,决定是否终止,在屏幕上输出问候语句
  */
 private static void waitToStop() {
   new Thread() {
     @Override
     public void run() {
       Scanner scanner = new Scanner(System.in);
       while (true) {
         String s = scanner.nextLine();
         if ("gun".equals(s)) {
           flag = false;
           scanner.close();
           break; //如果输入了gun,就终止程序的运行
         }
       }
     }
   }.start();
 }

 /**
  *  在子线程中,不停的在屏幕上输出问候语句,
  *  直到,循环的控制变量flag的值被改为false
  */
 private static void sayHelloRecycling() throws InterruptedException {
   new Thread() {
     @Override
     public void run() {
       while (flag) {
         System.out.println("哈哈, 你好!");

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

2.阅读如下代码:

  1. 回答输出的是什么内容
    输出的是:Thread匿名子类的run方法
  2. 为什么
    new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println(“Runnable匿名子类的run方法”);
    }
    }) {
    @Override
    public void run() {
    System.out.println(“Thread匿名子类的run方法”);
    }
    }.start();

原因如下:

  1. 首先,我们创建了一个Thread的匿名内部类对象

  2. 该匿名Thread子类对象接收一个Ruannable接口子类的匿名内部类对象

  3. 在Thread子类的匿名内部类定义中,我们覆盖了父类(Thread)的run方法

  4. 接着我们在Thread的匿名内部类对象上调用了start()方法,启动该Thread匿名内部类对象所表示的子线程

  5. 在该子线程(即Thread的匿名内部类对象)上调用start()方法,start()方法会调用Thread类的run()方法,但这个run方法在Thread的匿名子类定义中被子类覆盖了。所以实际在子线程中执行并非是Thread类的run()方法,而是Thread的匿名子类中定义的run()方法(即多态效果)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-玫瑰少年-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值