【Java 多线程】线程实现,线程的状态

前言

之前线程的概念已经有总结过,这里主要总结 Java 线程类的创建方式,以及实现同步和互斥

另外也会剖析一些源码

线程的创建方式

  • Thread class (继承 Thread 类)
  • Runnable 接口 (实现Runnable 接口)
  • Callable 接口 (实现 Callable 接口)

1、继承 Thread 类创建线程

start 用于启动线程,当调用 start 后,线程并不会马上运行,而是处于就绪状态,是否要运行取决于cpu给的时间片

run用于子类重写来实现线程的功能,我们一般调用的是start方法,系统调用的是run方法

应用:下面我们实现一个多线程的应用,同时从网络上下载三张图片

需要一个工具包,网上搜索下载到本地,然后导入到 idea 项目中

在这里插入图片描述

在 IDEA 中创建一个 package,命名为 lib ,然后右击 lib ,选择 add as 属性,最后点击确定就可以了

在这里插入图片描述

下面是线程代码

package myPthread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;

/**
 * @Title:
 * @Package
 * @Description: 实现一个下载器
 * @author: maze
 * @date 2020/10/22下午 15:18
 */
public class myPthread2 extends Thread{
    private String url; //网络地址
    private String name; //保存的文件名

    public myPthread2(String url,String name){
        this.name = name;
        this.url = url;
    }
    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
    }

    public static void main(String[] args) {
        myPthread2 mypthread1 = new myPthread2("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=172771859,2215530038&fm=26&gp=0.jpg","小姐姐1.jpg");
        myPthread2 mypthread2 = new myPthread2("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=214719225,1330378216&fm=26&gp=0.jpg","小姐姐2.jpg");
        myPthread2 mypthread3 = new myPthread2("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3584899235,2312422629&fm=26&gp=0.jpg","小姐姐3.jpg");

        mypthread1.start();
        mypthread2.start();
        mypthread3.start();
    }
}

// 下载器
class WebDownloader{
     // 下载方法
     public void downloader(String url,String name){
         try{
             FileUtils.copyURLToFile(new URL(url),new File(name));
         }
         catch (IOException e){
             e.printStackTrace();
             System.out.println("io异常,downloader方法出现异常");
         }
     }
}

运行完之后,我们可以看到红色框内多了三种图片, idea 也是可以打开的

在这里插入图片描述
在这里插入图片描述

作为一个程序猿,就应该多去看美女,才有动力学习!!

另外我们发现,黄色框内的下载顺序并不是我们代码的顺序

这个是和 CPU 调度有关的,说明:多线程之间是并发执行的,这个和进程的优先级有关,当然也和图片大小有关,比如有的图片大,所以需要更多的时间才能下载好…

小总结
1、自定义线程继承 Thread 类
2、重写 run 方法,编写线程执行体
3、创建线程对象,调用 start 方法启动线程

2、 实现 Runnable 接口来创建线程

由于 thread 继承了 runnable 接口,所以使用 Runnable 接口创建线程,首先需要先创建 runnbale 对象,然后把对象丢到 thread 中进行创建线程

public class myPhread3 implements Runnable{

    public static void main(String[] args) {
        // 创建实现类对象
        myPhread3 phread3 = new myPhread3();
        // 创建代理类对象
        Thread thread = new Thread(phread3);
        thread.start();
        for(int i = 0;i<20;++i){
            System.out.println("我在写代码--"+i);
        }
    }

    @Override
    public void run() {
        for(int i = 0;i<20;++i){
            System.out.println("我在听课!-------"+i);
        }
    }
}

小总结
1、不建议使用继承 Thread 类创建对象,因为 java 是单继承的
2、建议使用 Runnable 接口,灵活方便,避免单继承的局限性,方便同一个对象被多个线程使用
3、启动方式:Thread 是子对象.start , Runnable 是传入一个目标对象+Thread对象.start 来启动线程

下面模拟龟兔赛跑,设计一个程序实现 Runnable 接口,如何让乌龟赢

public class myPthread5 implements Runnable{
    // 胜利者
    private static String winner;

    public static void main(String[] args) {
        myPthread5 pthread5 = new myPthread5();

        new Thread(pthread5,"乌龟").start();
        new Thread(pthread5,"兔子").start();
    }

    // 判断比赛是否结束
    private boolean gameOver(int steps){
        if(winner != null){
            return true;
        }
        if(steps >= 100){
            winner = Thread.currentThread().getName();
            System.out.println("winner is"+winner);
            return true;
        }
        return false;
    }

    @Override
    public void run() {
        for(int i = 1;i<=101;++i) {
            if(Thread.currentThread().getName().equals("兔子") && i%10==0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
            //判断比赛是否结束
            boolean flag = gameOver(i);
            if(flag){
                break;
            }
        }
    }
}

3、实现 Callable 接口(了解即可)

  • 实现接口,需要返回值类型
  • 重写 call 接口,需要抛出异常
  • 创建目标对象
  • 创建执行服务
  • 提交执行
  • 获取结果
  • 关闭服务
package myPthread;

import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;


/**
 * @Title: myPthread6
 * @Package myPthread
 * @Description:
 * @author: maze
 * @date 2020/10/22下午 22:38
 */
public class myPthread6 implements Callable<Boolean> {
    private String url; //网络地址
    private String name; //保存的文件名

    public myPthread6(String url,String name){
        this.name = name;
        this.url = url;
    }
    @Override
    public Boolean call() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        myPthread6 mypthread1 = new myPthread6("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=172771859,2215530038&fm=26&gp=0.jpg","小姐姐1.jpg");
        myPthread6 mypthread2 = new myPthread6("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1603387219628&di=dd7f0c62bcd9f2c3a7af43e5dd1fc6fe&imgtype=0&src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201110%2F28%2F084714m51zkooi5omrcinx.jpg","小姐姐2.jpg");
        myPthread6 mypthread3 = new myPthread6("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1603387219625&di=0d053377748ed62367bb5565656a3d42&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20181209%2F38467a58f9264ca68eefa37719b4b739.jpeg","小姐姐3.jpeg");

        //创建执行服务
        ExecutorService pool = Executors.newFixedThreadPool(3);

        //提交执行
        Future<Boolean> submit1 = pool.submit(mypthread1);
        Future<Boolean> submit2 = pool.submit(mypthread2);
        Future<Boolean> submit3 = pool.submit(mypthread3);
        // 获取结果
        boolean res1 = submit1.get();
        boolean res2 = submit2.get();
        boolean res3 = submit3.get();
        //关闭服务
        pool.shutdown();
    }
}

// 下载器
class WebDownloader {
    // 下载方法
    public void downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("io异常,downloader方法出现异常");
        }
    }
}

静态代理模式

要求:真实角色,代理角色;真实角色和代理角色要实现同一个接口,代理角色要持有真实角色的引用

在 Java 中线程的设计就使用了静态代理设计模式,其中自定义线程类实现 Runable 接口,Thread 类也实现了 Runalbe 接口,在创建子线程的时候,传入了自定义线程类的引用,再通过调用 start()方法,调用自定义线程对象的 run()方法实现了线程的并发执行

package myPthread;

/**
 * @Title: myPthread7Static
 * @Package
 * @Description: 静态代理模式:真实对象和代理对象都要实现同一个接口
 * 好处:(1)代理对象对象可以做很多真实对象做不了的事情  (2) 真实对象专注做自己的事情
 * @author: maze
 * @date 2020/10/22下午 23:24
 */
public class myPthread7Static {
    public static void main(String[] args) {

        // 这个也相当于一个代理, Thread 实现了 Runnable 接口
        new Thread(()-> System.out.println("我爱你")).start();

        // You 传入给代理 WeddingCompay 类
        new WeddingCompay(new You()).HappyMarry();
    }
}

interface Marry{
    void HappyMarry();
}

// 真实角色
class You implements Marry{
    @Override
    public void HappyMarry() {
        System.out.println("要结婚了,超开心");
    }
}

// 代理角色
class WeddingCompay implements Marry{
    //要结婚的对象
    private Marry marry;

    public WeddingCompay(Marry marry) {
        this.marry = marry;
    }

    @Override
    public void HappyMarry() {
        before();
        this.marry.HappyMarry();
        after();
    }

    private void after() {
        System.out.println("结婚之后,收尾款");
    }

    private void before() {
        System.out.println("结婚之前布置现场");
    }
}

Lambda 表达式

把函数作为参数传递到方法中,使用 lambda 可以是代码简洁紧凑

lambda 表达式特点

  • 不需要声明参数类型,编译器可识别
  • 一个参数不需要定义圆括号,但是多个参数必须定义圆括号
  • 主体中如何有一行语句,可以省略大括号,否则不可省略
  • 如果主体只有一个表达式返回值,则编译器会自动返回值,大括号需要指明表达式返回一个数值

类的实现方式有,外部实现类,静态内部类,局部内部类,匿名内部类

下面是通过 lambda 表达式来实现

public class Test1Lambda {
   public static void main(String[] args) {
       ILike like = new Like();
        // lambda 表达式
        like = ()->{
            System.out.println("i like lambda5");
        };
        like.lambda();
    }
}

interface ILike{
    void lambda();
}

class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("i like lambda1");
    }
}

停止线程

不推介使用 JDK 提供的 stop ,destroy 方法来停止线程,更好的办法是自己写一个标志位来让线程主动停止

public class myPthread8 implements Runnable{
    private boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        myPthread8 pthread8 = new myPthread8();
        new Thread(pthread8).start();

        for(int i = 0;i<1000;++i){
            System.out.println("main"+i);
            if(i == 900){ // 调用 stop
                pthread8.stopPthread();
                System.out.println("线程停止...");
            }
        }
    }

    @Override
    public void run() {
        int num = 0;
        while(flag){
            System.out.println("run....thread"+num++);
        }
    }
    public void stopPthread(){
        this.flag = false;
    }
}

思考:为什么 JDK 文档不建议使用 stop ,destroy 方法来停止线程呢?
答:stop 方法是从外部强行 终止一个线程,是一种粗暴的线程终止行为,在线程终止之前没有对其做任何的清除操作,例如:使用IO流时不能关流,或者会有其它不可预知的错误

线程休眠

  • sleep 指定当前线程阻塞的毫秒
  • sleep 存在异常 InterruptedException
  • sleep 时间达到后线程进入就绪状态
  • sleep 模拟网络延时,倒计时等
  • 每个对象都有一把锁,sleep 不会释放锁

下面我们模拟网络延时和倒计时

public class myPthread9Sleep implements Runnable{
    private int ticknum = 10;

    public static void main(String[] args) {
        myPthread9Sleep pthread9Sleep = new myPthread9Sleep();
        new Thread(pthread9Sleep,"小明").start();
        new Thread(pthread9Sleep,"小亮").start();
        new Thread(pthread9Sleep,"小铭").start();
    }

    @Override
    public void run() {
        while(true){
            if(ticknum < 0){
                break;
            }
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknum-- + "票");
        }
    }
}

倒计时

public class myPthread9Sleep2 {
    private int num = 10;
    
    public void tendo() throws InterruptedException {
        while(true){
            if(num <=0){
                break;
            }
            Thread.sleep(1000);
            System.out.println(num--);
        }
    }
    public static void main(String[] args) {
        try {
            new myPthread9Sleep2().tendo();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class myPthread9Sleep2 {
    // 打印系统当前时间
    public static void main(String[] args) {
        Date startime = new Date(System.currentTimeMillis());
        while(true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startime));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            startime = new Date(System.currentTimeMillis());
        }

    }
}

观察线程状态

new 状态:线程对象一旦被创建就进入到新生状态

当调用 start 方法,线程立即进入就绪状态,但不意味着立刻调度执行

线程进入运行状态,线程才真正的执行线程体的代码块

当调用 sleep ,wait 或者同步锁定时,线程进入阻塞状态,阻塞解除后,重新进入就绪态,等待 CPU 调度运行

线程运行完毕后,进入死亡状态,无法再次启动线程

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(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("");
        });

        //观察状态
        Thread.State state = thread.getState();
        System.out.println(state); // NEW 新生

        thread.start();
        state = thread.getState();
        System.out.println(state); // Run 启动

        //只要线程不终止,就一直输出状态
        while(state != Thread.State.TERMINATED){
            Thread.sleep(100);
            state = thread.getState();
            System.out.println(state);
        }
    }
}

线程优先级

Java 提供了一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定调用哪个线程来执行

public class myPthreadPriority {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        MyPriority priority = new MyPriority();

        Thread thread1 = new Thread(priority);
        Thread thread2 = new Thread(priority);
        Thread thread3 = new Thread(priority);

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

        thread2.setPriority(1);
        thread2.start();

        thread3.setPriority(10);
        thread3.start();
    }
}

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

守护线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 守护线程有,后台记录操作日志,监控内存,垃圾回收等到
public class myPthreadDaemon {
    public static void main(String[] args) {
        God god = new God();
        you you = new you();
        Thread thread = new Thread(god);
        // 默认是 false 表示用户线程,正常的线程都是用户线程
        thread.setDaemon(true);
        thread.start(); //上帝守护线程启动

        new Thread(you).start();//用户线程启动
    }
}

class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("上帝保佑着你");
        }
    }
}

class you implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你一生都开心的活着!");
        }
        System.out.println("========goodby world=======");
    }
}

文章字数太多,线程的同步与安全在下一篇总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序猿的温柔香

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

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

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

打赏作者

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

抵扣说明:

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

余额充值