草庐IT

day32-线程基础02

liyuelian 2023-04-16 原文

线程基础02

3.继承Thread和实现Runnable的区别

  1. 从java的设计来看,通过继承Thread或者实现Runnable接口本身来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable接口

3.1多线程售票问题

编程模拟三个售票窗口售票100张,分别使用继承Thread类和实现Runnable接口的方法,并分析有什么问题?

1.使用继承Thread的方法:

package li.thread;

//使用多线程,模拟三个窗口同时售票共100张
public class SellTicket {
    public static void main(String[] args) {

        SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();

        sellTicket01.start();//启动售票线程
        sellTicket02.start();//启动售票线程
        sellTicket03.start();//启动售票线程
    }
}

//1.使用继承Thread类的方式
class SellTicket01 extends Thread {

    //多个对象共享同一个静态成员变量(多个实例的static变量会共享同一块内存区域)
    private static int ticketNum = 100;//让多个线程共享ticketNum

    @Override
    public void run() {
        while (true) {

            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                break;
            }

            //休眠50毫秒,模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票 "
                    + "剩余票数:" + (--ticketNum));
        }
    }
}

一个显然的问题是,剩余票数竟然是负数!

原因是:每个线程都要进行票数判断才能进行下一步操作,假设某时刻票数还剩2张,此时线程0判断条件ticketNum <= 0不成立;于此同时,线程1线程2也同时进行了判断,三者都通过了判断,于是都认为此刻票数为2,都进行-1售票操作。于是三者结束后就会出现总票数为-1 的情况。

可以看到,造成票数超卖的主要原因是三个线程同时操作一个资源。

2.使用实现接口Runnable的方式:

package li.thread;

//使用多线程,模拟三个窗口同时售票共100张
public class SellTicket {
    public static void main(String[] args) {

        SellTicket02 sellTicket02 = new SellTicket02();
        new Thread(sellTicket02).start();//第1个线程-窗口
        new Thread(sellTicket02).start();//第2个线程-窗口
        new Thread(sellTicket02).start();//第3个线程-窗口
    }
}

class SellTicket02 implements Runnable {

    private int ticketNum = 100;

    @Override
    public void run() {
        while (true) {

            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                break;
            }

            //休眠50毫秒,模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票 "
                    + "剩余票数:" + (--ticketNum));
        }
    }

}

可以看到,实现接口Runnable的方式同样发生了票数为负数的情况,原因与上面一致,是由于多个线程同时操作一个资源而造成的。

要解决类似的问题,就要引入线程的同步和互斥的概念。该问题将在之后解决。

4.线程终止

  • 基本说明:
  1. 当线程完成任务后,会自动退出
  2. 还可以通过使用变量来控制run方法退出的方式来停止线程,即通知方式

例子:

启动一个线程t,要求在main线程中去停止线程t,请编程实现。

package li.thread.exit_;

public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.start();

        //如果希望main线程可以去控制 t1线程的终止,必须可以修改loop
        //让 t1退出run方法,从而终止 t1线程 -->称为 通知方式

        //让主线程休眠 10秒,在通知 t1线程退出
        System.out.println("主线程休眠10秒...");
        Thread.sleep(10*1000);

        t.setLoop(false);
    }
}

class T extends Thread {
    int count = 0;

    //设置一个控制变量
    private boolean loop = true;

    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T 运行中..."+(++count));
        }
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

可以用于一个线程通过变量控制另一个线程终止的情况。

5.线程常用方法

  • 常用方法第一组:
  1. setName //设置线程名称,使之与参数name相同
  2. getName //返回该线程的名称
  3. start //使该线程开始执行;Java虚拟机底层调用该线程的start0()方法
  4. run //调用线程对象run方法
  5. setPriority //更改线程的优先级
  6. getPriority // 获取线程的优先级
  7. sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  8. interrupt //中断线程

注意事项和细节:

  • start方法底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程
  • 线程优先级的范围
  • interrupt,中断线程,但并没有真正地结束线程。所以一般用于中断正在休眠的线程
  • sleep:线程的静态方法,使当前线程休眠

例子1:

package li.thread.method;

public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        //测试相关方法
        T t = new T();
        t.setName("jack");//设置线程的名称
        t.setPriority(Thread.MIN_PRIORITY);
        t.start();//启动子线程

        //主线程打印5句hi,然后中断子线程的休眠
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi" + i);
        }

        System.out.println(t.getName() + "线程的优先级=" + t.getPriority());
        t.interrupt();//当执行到这里的时候,就会中断 t线程的休眠
    }
}

class T extends Thread {//自定义的线程类

    @Override
    public void run() {
        while (true) {//每隔5秒吃100个包子,然后休眠5秒,再吃...
            for (int i = 0; i < 100; i++) {
                //Thread.currentThread().getName()获取当前线程的名称
                System.out.println(Thread.currentThread().getName() + "吃包子~~~" + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + "休眠中~~~");
                sleep(20000);//休眠20秒
            } catch (InterruptedException e) {
                //当该线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码
                //InterruptedException是捕获到一个中断异常
                System.out.println(Thread.currentThread().getName() + "被interrupt了");
            }
        }
    }
}
  • 常用方法第二组:
  1. yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功。

  2. join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程的所有任务

例子2:创建一个子线程,每个1秒输出hello,输出20次;主线程每隔1秒输出hi,输出20次。要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续。(join)

package li.thread.method;

public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        T2 t2 = new T2();
        t2.start();

        for (int i = 0; i <=20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程吃了 "+i+" 个包子");
            if (i == 5) {
                System.out.println("主线程让子线程先吃包子...");
                t2.join();//相当于让 t2线程先执行完毕
                //Thread.yield();//如果这里是yield,不一定会礼让成功
                System.out.println("主线程接着吃包子...");
            }
        }
    }
}

class T2 extends Thread {
    int loop = 0;

    @Override
    public void run() {
        for (int i = 0; i <= 20; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程吃了 "+i+" 个包子");
        }
    }
}

  • 课堂练习:
    1. 主线程每隔一秒输出hi,一共十次
    2. 当输出到5次时,启动一个子线程(要求实现Runnable),该子线程每隔1s输出hello,等该宣称输出10次后,退出
    3. 主线程继续输出hi,直到主线程退出
package li.thread.method;

public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(new T3());
        for (int i = 0; i <= 10; i++) {
            Thread.sleep(1000);
            System.out.println("hi" + i);
            if (i == 5) {
                thread.start();//启动子线程
                thread.join();//将子线程先执行
            }
        }
    }
}

class T3 implements Runnable {

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

线程基础02

3.继承Thread和实现Runnable的区别

  1. 从java的设计来看,通过继承Thread或者实现Runnable接口本身来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable接口

3.1多线程售票问题

编程模拟三个售票窗口售票100张,分别使用继承Thread类和实现Runnable接口的方法,并分析有什么问题?

1.使用继承Thread的方法:

package li.thread;

//使用多线程,模拟三个窗口同时售票共100张
public class SellTicket {
    public static void main(String[] args) {

        SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();

        sellTicket01.start();//启动售票线程
        sellTicket02.start();//启动售票线程
        sellTicket03.start();//启动售票线程
    }
}

//1.使用继承Thread类的方式
class SellTicket01 extends Thread {

    //多个对象共享同一个静态成员变量(多个实例的static变量会共享同一块内存区域)
    private static int ticketNum = 100;//让多个线程共享ticketNum

    @Override
    public void run() {
        while (true) {

            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                break;
            }

            //休眠50毫秒,模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票 "
                    + "剩余票数:" + (--ticketNum));
        }
    }
}

一个显然的问题是,剩余票数竟然是负数!

原因是:每个线程都要进行票数判断才能进行下一步操作,假设某时刻票数还剩2张,此时线程0判断条件ticketNum <= 0不成立;于此同时,线程1线程2也同时进行了判断,三者都通过了判断,于是都认为此刻票数为2,都进行-1售票操作。于是三者结束后就会出现总票数为-1 的情况。

可以看到,造成票数超卖的主要原因是三个线程同时操作一个资源。

2.使用实现接口Runnable的方式:

package li.thread;

//使用多线程,模拟三个窗口同时售票共100张
public class SellTicket {
    public static void main(String[] args) {

        SellTicket02 sellTicket02 = new SellTicket02();
        new Thread(sellTicket02).start();//第1个线程-窗口
        new Thread(sellTicket02).start();//第2个线程-窗口
        new Thread(sellTicket02).start();//第3个线程-窗口
    }
}

class SellTicket02 implements Runnable {

    private int ticketNum = 100;

    @Override
    public void run() {
        while (true) {

            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                break;
            }

            //休眠50毫秒,模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票 "
                    + "剩余票数:" + (--ticketNum));
        }
    }

}

可以看到,实现接口Runnable的方式同样发生了票数为负数的情况,原因与上面一致,是由于多个线程同时操作一个资源而造成的。

要解决类似的问题,就要引入线程的同步和互斥的概念。该问题将在之后解决。

4.线程终止

  • 基本说明:
  1. 当线程完成任务后,会自动退出
  2. 还可以通过使用变量来控制run方法退出的方式来停止线程,即通知方式

例子:

启动一个线程t,要求在main线程中去停止线程t,请编程实现。

package li.thread.exit_;

public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.start();

        //如果希望main线程可以去控制 t1线程的终止,必须可以修改loop
        //让 t1退出run方法,从而终止 t1线程 -->称为 通知方式

        //让主线程休眠 10秒,在通知 t1线程退出
        System.out.println("主线程休眠10秒...");
        Thread.sleep(10*1000);

        t.setLoop(false);
    }
}

class T extends Thread {
    int count = 0;

    //设置一个控制变量
    private boolean loop = true;

    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T 运行中..."+(++count));
        }
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

可以用于一个线程通过变量控制另一个线程终止的情况。

5.线程常用方法

  • 常用方法第一组:
  1. setName //设置线程名称,使之与参数name相同
  2. getName //返回该线程的名称
  3. start //使该线程开始执行;Java虚拟机底层调用该线程的start0()方法
  4. run //调用线程对象run方法
  5. setPriority //更改线程的优先级
  6. getPriority // 获取线程的优先级
  7. sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  8. interrupt //中断线程

注意事项和细节:

  • start方法底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程
  • 线程优先级的范围
  • interrupt,中断线程,但并没有真正地结束线程。所以一般用于中断正在休眠的线程
  • sleep:线程的静态方法,使当前线程休眠

例子1:

package li.thread.method;

public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        //测试相关方法
        T t = new T();
        t.setName("jack");//设置线程的名称
        t.setPriority(Thread.MIN_PRIORITY);
        t.start();//启动子线程

        //主线程打印5句hi,然后中断子线程的休眠
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi" + i);
        }

        System.out.println(t.getName() + "线程的优先级=" + t.getPriority());
        t.interrupt();//当执行到这里的时候,就会中断 t线程的休眠
    }
}

class T extends Thread {//自定义的线程类

    @Override
    public void run() {
        while (true) {//每隔5秒吃100个包子,然后休眠5秒,再吃...
            for (int i = 0; i < 100; i++) {
                //Thread.currentThread().getName()获取当前线程的名称
                System.out.println(Thread.currentThread().getName() + "吃包子~~~" + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + "休眠中~~~");
                sleep(20000);//休眠20秒
            } catch (InterruptedException e) {
                //当该线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码
                //InterruptedException是捕获到一个中断异常
                System.out.println(Thread.currentThread().getName() + "被interrupt了");
            }
        }
    }
}
  • 常用方法第二组:
  1. yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功。

  2. join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程的所有任务

例子2:创建一个子线程,每个1秒输出hello,输出20次;主线程每隔1秒输出hi,输出20次。要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续。(join)

package li.thread.method;

public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        T2 t2 = new T2();
        t2.start();

        for (int i = 0; i <=20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程吃了 "+i+" 个包子");
            if (i == 5) {
                System.out.println("主线程让子线程先吃包子...");
                t2.join();//相当于让 t2线程先执行完毕
                //Thread.yield();//如果这里是yield,不一定会礼让成功
                System.out.println("主线程接着吃包子...");
            }
        }
    }
}

class T2 extends Thread {
    int loop = 0;

    @Override
    public void run() {
        for (int i = 0; i <= 20; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程吃了 "+i+" 个包子");
        }
    }
}

  • 课堂练习:
    1. 主线程每隔一秒输出hi,一共十次
    2. 当输出到5次时,启动一个子线程(要求实现Runnable),该子线程每隔1s输出hello,等该宣称输出10次后,退出
    3. 主线程继续输出hi,直到主线程退出
package li.thread.method;

public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(new T3());
        for (int i = 0; i <= 10; i++) {
            Thread.sleep(1000);
            System.out.println("hi" + i);
            if (i == 5) {
                thread.start();//启动子线程
                thread.join();//将子线程先执行
            }
        }
    }
}

class T3 implements Runnable {

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

有关day32-线程基础02的更多相关文章

  1. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  2. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  3. postman——集合——执行集合——测试脚本——pm对象简单示例02 - 2

    //1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json

  4. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  5. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  6. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  7. ruby - 如何让Ruby捕获线程中的语法错误 - 2

    我正在尝试使用ruby​​编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?

  8. 牛客网专项练习30天Pytnon篇第02天 - 2

    1.在Python3中,下列关于数学运算结果正确的是:(B)a=10b=3print(a//b)print(a%b)print(a/b)A.3,3,3.3333...B.3,1,3.3333...C.3.3333...,3.3333...,3D.3.3333...,1,3.3333...解析:    在Python中,//表示地板除(向下取整),%表示取余,/表示除(Python2向下取整返回3)2.如下程序Python2会打印多少个数:(D)k=1000whilek>1:    print(k)k=k/2A.1000 B.10C.11D.9解析:    按照题意每次循环K/2,直到K值小于等

  9. ruby - 如何在 ruby​​ 中运行后台线程? - 2

    我是ruby​​的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp

  10. ruby - Rails 开发服务器、PDFKit 和多线程 - 2

    我有一个使用PDFKit呈现网页的pdf版本的Rails应用程序。我使用Thin作为开发服务器。问题是当我处于开发模式时。当我使用“bundleexecrailss”启动我的服务器并尝试呈现任何PDF时,整个过程会陷入僵局,因为当您呈现PDF时,会向服务器请求一些额外的资源,如图像和css,看起来只有一个线程.如何配置Rails开发服务器以运行多个工作线程?非常感谢。 最佳答案 我找到的最简单的解决方案是unicorn.geminstallunicorn创建一个unicorn.conf:worker_processes3然后使用它:

随机推荐