线程以及多线程(一)

一、什么是线程、进程、多线程

进程:系统中正在运行的程序

线程:进程中负责控制执行的一个控制单元

多线程:一个进程中有多个线程执行称之为多线程

!!

一个进程中最少要有一个线程

一个进程中可以有多个线程

开启多个线程是为了同时执行多段代码,每个线程都有自己的执行的内容,这个内容称之为线程要执行的 任务

在java语言中:

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

对于java程序来说,代码在开始执行时,会先启动JVM,而JVM就是一个进程

JVM再启动一个主线程调用main方法(main方法就是主线程)。
同时再启动一个垃圾回收线程负责看护,回收垃圾。

也就是说现在的java程序中至少有两个线程并发,一个是 垃圾回收线程,一个是 执行main方法的主线程

二. 如何创建线程

线程的三种创建方式

1.Thread:Thread class ————>继承Thread类(重点)

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法,启动线程

注意:线程不一定立即执行,由CUP安排调度(主线程和副线程交替执行)

package com.example.demo;

public class TestThread extends Thread  {
//    重写继承Thread的run方法
    @Override
    public void run() {
        super.run();
        for (int i = 0;i<=20;i++){
            System.out.println("这是使用重写run方法创建线程--------"+i);

        }
    }


    public static void main(String[] args) {
        //创建一个线程对象
        TestThread testThread = new TestThread();

        //调用start()方法开启线程
        testThread.start();
        for (int i = 0; i < 200; i++) {
            System.out.println("这是主线程main方法---" + i);
        }
    }

}

注意!!!!!!如果 直接调用run方法,则线程不会并发执行而是先根据代码顺序进行执行

  public static void main(String[] args) {
        //创建一个线程对象
        TestThread testThread = new TestThread();

        //调用start()方法开启线程
        testThread.run();
        for (int i = 0; i < 200; i++) {
            System.out.println("这是主线程main方法---" + i);
        }
    }

2.Runnable:Runnable接口 ————>实现Runnable接口(重点)

  1. 定义MyRunnable类实现Runnable接口。
  2. 实现run()方法,编写线程执行体。
  3. 创建线程对象,调用start()方法启动线程

因为是实现的Runnable接口,接口中没有start的方法,所以需要创建一个Thread对象来调用start方法,Thread源码中可以创建时携带一个Ruannable对象,

  public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

实现创建线程 

package com.example.demo;

public class TestRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0;i<=20;i++){
            System.out.println("这是实现Runnable接口实现run方法创建线程--------"+i);
        }
    }

    public static void main(String[] args) {
        //实现Runnable接口
        TestRunnable testRunnable = new TestRunnable();
        //创建一个线程对象,并将Runnable接口对象传入
        Thread thread = new Thread(testRunnable);
        //调用start()方法开启线程
        thread.start();

        //启动线程  可以简写为   new Thread(testRunnable).start();
        for (int i = 0; i < 200; i++) {
            System.out.println("这是主线程main方法---" + i);
        }
    }
}

3.Callable:Callable接口 ————>实现Callable接口(了解)

可以定义返回值
可以抛出异常

实现Callable接口,需要返回值类型。
重写call方法,需要抛出异常。
创建目标
创建执行服务:ExecutorService xx = Executors.newFixedThreadPool(2);
提交执行:Future r1 = ser.submit(t1);
获取结果:boolean rs1 = r1.get();
关闭服务:ser.shutdownNow();

/**
 * @author yxf
 */
 //线程创建方式三:实现callable接口,重写call()方法。
 public class TestCallable implements Callable<Boolean> {

    private String url; //网路图片地址
    private String name; //保存的文件名

    public TestCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @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 {
        TestCallable t1 = new TestCallable("https://wx2.sinaimg.cn/mw2000/b7a7a24fgy1h82cus8xxlj22qj3pqkjo.jpg","shaking.jpg");
        TestCallable t2 = new TestCallable("https://wx2.sinaimg.cn/orj360/b7a7a24fly1h6edp8a91jj22ph3ttkjq.jpg","想我了没.jpg");

        //创建执行服务:
        ExecutorService ser = Executors.newFixedThreadPool(2);

        //提交执行
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);

        //获取结果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        //关闭服务
        ser.shutdownNow();
    }

    //下载器
    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方法出现问题");
            }
        }
    }
}

总结:

继承Thread类
1. 子类继承Thread类具备多线程能力。
2. 启动线程:子类对象.start();
3. 不建议使用:避免OOP单继承局限性。

实现Runnable接口
1. 实现接口Runnable具有多线程能力。
2. 启动线程:传入目标对象+Thread对象.start();
3. 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。

实现Callable接口-------了解即可

三、线程的状态

线程有五种状态

1.创建状态 

当线程被创建时,该线程就进入了创建状态 

2.就绪状态

线程对象调用start方法时进入该就绪状态,但是并不以为着该线程马上就进行了调度执行

3.运行状态 

当线程处于就绪状态时CPU为其分配内存资源后线程就进入了运行状态,这时线程才会执行线程体中的代码块,处于运行状态的线程也可以进行线程调度,将cpu资源释放,从而转为就绪状态

4.阻塞状态  

当调用sleep,wait 或同步锁定synchronized时,线程进入阻塞状态就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行。

5.死亡状态

线程中断或者结束,线程就进入了死亡状态。一旦进入死亡状态,就不能再次启动

下面我会用龟兔赛跑的案例来介绍线程的 线程休眠sleep(),线程停止标志,线程礼让yield(),线程强制执行join()

1.线程休眠sleep()

当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,如果有多个线程则将CPU内存让给其它线程使用。
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。

Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次

package com.example.demo;


public class GuiTuRace implements Runnable{
//      龟兔赛跑案例,理解线程休眠sleep,线程停止标志,线程礼让yield(),线程强制执行join()
    private static String winner;

    @Override
    public void run() {

        for (int i = 1; i <= 200; i++) {
            //线程停止标志符,----可以是一个方法,也可以是一个常量
            if (gameOver(i)){
//           如果  gameOver为true  打破for循环 比赛结束,结束线程
                break;
            }
//            使用线程休眠sleep,使乌龟的线程每隔50步停止1毫秒
            if (Thread.currentThread().getName()=="乌龟" && i % 50 == 0){
                try {
                    System.out.println("乌龟线程停止了---------100ms");
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }


            System.out.println(Thread.currentThread().getName()+"--->跑了第"+i+"步");
        }
    }

    public boolean gameOver(int steps){
        if (winner!=null){//判断是否有获胜者,有则返回true,使线程结束
            return true;
        }else{//判断线程的结束条件,即两个线程中哪一个先达到线程结束的条件,达成条件后返回true
            if (steps>=200){
                winner=Thread.currentThread().getName();
                System.out.println("获胜者为----->"+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        GuiTuRace guiTuRace = new GuiTuRace();

        Thread TuZi = new Thread(guiTuRace,"兔子");
        Thread WuGui = new Thread(guiTuRace,"乌龟");


        WuGui.start();
        TuZi.start();
    }
}

每当乌龟跑了50步,就停下休息100ms

2.线程结束/中断方法

2.1自定义结束标记

在这个例子中我定义的一个gameover方法作为停止标志

public boolean gameOver(int steps){
        if (winner!=null){//判断是否有获胜者,有则返回true,使线程结束
            return true;
        }else{//判断线程的结束条件,即两个线程中哪一个先达到线程结束的条件,达成条件后返回true
            if (steps>=200){
                winner=Thread.currentThread().getName();
                System.out.println("获胜者为----->"+winner);
                return true;
            }
        }
        return false;
    }

若达到了线程停止的条件(即兔子或者乌龟到达了200步终点),则将标识符设置为true

  @Override
    public void run() {

        for (int i = 1; i <= 200; i++) {
            //线程停止标志符,----可以是一个方法,也可以是一个常量
            if (gameOver(i)){
//           如果  gameOver为true  打破for循环 比赛结束,结束线程
                break;
            }
//            使用线程休眠sleep,使乌龟的线程每隔50步停止1毫秒
            if (Thread.currentThread().getName()=="乌龟" && i % 50 == 0){
                try {
                    System.out.println("乌龟线程停止了---------100ms");
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }


            System.out.println(Thread.currentThread().getName()+"--->跑了第"+i+"步");
        }
    }

线程中run方法中的代码块在接收到标识符判断是否结束该线程,即打破代码块中的for循环

2.2 调用Tread的方法interrupt()

2.2.1 interrupt方法应用场景
用来打断正在阻塞的线程:sleep/wait/join
打断正常的线程
2.2.2 interrupt() 方法
        Thread类的实例方法,其作用是中断此线程(此线程不一定是当前线程,而是指调用该方法的Thread实例所代表的线程),但实际上只是给线程设置一个中断标志,线程仍会继续运行。)作用与正常线程会将中断标记设置为true,但是作用于阻塞线程会将中断标志刷新false(中断标记默认为false,刷新就是重新刷会默认)。

2.2.3 interrupted() 方法
        Thread类的静态方法,作用是测试当前线程是否被中断(检查中断标志),返回一个boolean并清除中断状态,第二次再调用时中断状态已经被清除,将返回一个false。

2.2.4 isInterrupted() 方法
        Thread类的实例方法,作用是只测试此线程是否被中断,不清除中断状态。

作用于正常线程时

package com.example.demo;

public class InterruptDemo implements Runnable{

    @Override
    public void run() {
//        Thread.currentThread().isInterrupted()
//        isInterrupted判断线程是否中断


        while (!Thread.currentThread().isInterrupted()) {
            System.out.println(Thread.currentThread().getName() + "----------线程运行中");
            // 添加一些耗时操作,以便观察线程的打断情况
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        InterruptDemo interruptDemo = new InterruptDemo();
        Thread thread1 = new Thread(interruptDemo);

        thread1.start();

//            主线程休眠3s后打断线程1
         Thread.sleep(3000);

//         设置thread1.isInterrupted()为true
        System.out.println("设置thread1.isInterrupted()前为--"+thread1.isInterrupted());
        thread1.interrupt();//打断正在正常运行的thread1
        System.out.println("设置thread1.isInterrupted()前后--"+thread1.isInterrupted());




    }
}

作用于阻塞线程时

package com.example.demo;

public class InterruptDemo implements Runnable{

    @Override
    public void run() {

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }

    public static void main(String[] args) throws InterruptedException {
        InterruptDemo interruptDemo = new InterruptDemo();
        Thread thread1 = new Thread(interruptDemo);

        thread1.start();

//         设置thread1.Interrupted()
        System.out.println("设置thread1.isInterrupted()前为--"+thread1.isInterrupted());
        thread1.interrupt();//打断正在已经进入sleep状态的阻塞线程thread1
        System.out.println("设置thread1.isInterrupted()后为--"+thread1.isInterrupted());




    }
}

3.线程礼让yield()

线程让位,即让当前线程暂停,回到就绪状态,将此次抢占的CPU资源分配让给其它线程。

上面的例子中我只是把sleep代码块稍作修改,如下

            if (Thread.currentThread().getName() == "乌龟" && i % 50 == 0) {
              Thread.yield();
                System.out.println("乌龟线程进行了线程礼让---------");

            }

每当乌龟跑了50步时,并进行一次线程礼让,即将cpu资源释放,让其他线程占用cpu资源

要注意的是并不是每次都让成功的,有可能它又抢到时间片了。

4.线程的join()方法

让主线程等待(进入WAITING状态),一直等到其他子线程不再活动为止。

package com.example.demo;


public class GuiTuRace implements Runnable {
    //      龟兔赛跑案例,理解线程休眠sleep,线程停止标志,线程礼让yield(),线程强制执行join()
    private static String winner;

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"---------开始");

        for (int i = 1; i <= 100; i++) {
            //线程停止标志符,----可以是一个方法,也可以是一个常量
            if (gameOver(i)) {
//           如果  gameOver为true  打破for循环 比赛结束,结束线程
                break;
            }


            System.out.println(Thread.currentThread().getName() + "--->跑了第" + i + "步");
        }
        System.out.println(Thread.currentThread().getName()+"---------结束");

    }

    public boolean gameOver(int steps) {
        if (winner != null) {//判断是否有获胜者,有则返回true,使线程结束
            return true;
        } else {//判断线程的结束条件,即两个线程中哪一个先达到线程结束的条件,达成条件后返回true
            if (steps >= 100) {
                winner = Thread.currentThread().getName();
                System.out.println("获胜者为----->" + winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {


        System.out.println("main---------开始");

        GuiTuRace guiTuRace = new GuiTuRace();

        Thread TuZi = new Thread(guiTuRace, "兔子");
        Thread WuGui = new Thread(guiTuRace, "乌龟");
        TuZi.start();
        WuGui.start();

        for (int i = 0; i < 10; i++) {
            if (i==5){
                try {
                    System.out.println("TuZi---------强制执行");
                    System.out.println("wugui---------强制执行");
                    WuGui.join();
                    TuZi.join(); // 乌龟兔子合并到主线程中,当前主线程受阻塞,乌龟兔子线程执行直到结束。


                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("主线程执行--------"+i);
        }

        System.out.println("main---------结束");

    }
}

当主线程执行了一半时,使主线程进入等待,等待子线程执行结束继续执行主线程的方法

  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值