090_多线程(一)

2021-03-12 09:28

阅读:412

标签:VID   out   jpg   定义   返回   使用   game   exec   executor   

目录
  • 简介
  • 线程创建
    • 继承Thread类
      • 网图下载
    • 实现Runnable接口
      • 继承Thread类和实现Runnable接口的区别
      • 并发问题
      • 龟兔赛跑
    • 实现Callable接口
  • 静态代理模式
    • 静态代理模式的好处
  • Lambda表达式
    • 为什么要使用Lambda表达式
    • 函数式接口Functional Interface
  • 线程状态
    • 线程停止
    • 线程休眠
      • 模拟网络延时:放大问题发生可能性
      • 模拟倒计时
      • 模拟时钟
    • 线程礼让
    • 线程强制执行
    • 线程状态观测
      • 线程状态
  • 线程优先级
  • 守护线程(daemon)

https://www.bilibili.com/video/BV1V4411p7EF/

简介

  1. 程序:指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
  2. 进程Process:执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。一个进程中可以包含多个线程,至少有一个线程,否则没有存在的意义。
  3. 线程Thread:CPU调度和执行的单位。
  4. 真正的多线程指多核。很多多线程是模拟出来的,即在一个CPU的情况下,快速切换线程。

线程创建

继承Thread类

  1. 自定义线程类继承Thread类
  2. 重写run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动线程
package com.qing.demo01;

/**
 * 创建线程方式一:继承Thread类,重写run()方法,调用start()开启线程
 * 注意:线程开启不一定立即执行,由CPU调度执行
 */
public class TestThread1 extends Thread {
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i 
main线程--83
main线程--84
run线程--0
run线程--1
run线程--2
run线程--3
run线程--4
run线程--5
run线程--6
run线程--7
run线程--8
run线程--9
run线程--10
run线程--11
run线程--12
run线程--13
run线程--14
run线程--15
run线程--16
run线程--17
run线程--18
run线程--19
run线程--20
run线程--21
run线程--22
main线程--85
run线程--23
run线程--24
main线程--86
main线程--87
main线程--88
main线程--89
main线程--90

网图下载

package com.qing.demo01;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

/**
 * 练习Thread,实现多线程同步下载图片
 */
public class TestThread2 extends Thread {

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

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

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

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605724974060&di=abc373a617b841a8e78b3c53fcbd9b5c&imgtype=0&src=http%3A%2F%2Fimg.ewebweb.com%2Fuploads%2F20191203%2F16%2F1575361350-YwtjLgUXBq.jpg", "1.jpg");
        TestThread2 t2 = new TestThread2("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1578345841,1712921595&fm=26&gp=0.jpg", "2.jpg");
        TestThread2 t3 = new TestThread2("https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3131683833,708813674&fm=26&gp=0.jpg", "3.jpg");

        t1.start();
        t2.start();
        t3.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方法出现问题");
        }
    }
}
下载文件,name: 2.jpg,url: https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1578345841,1712921595&fm=26&gp=0.jpg
下载文件,name: 3.jpg,url: https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3131683833,708813674&fm=26&gp=0.jpg
下载文件,name: 1.jpg,url: https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605724974060&di=abc373a617b841a8e78b3c53fcbd9b5c&imgtype=0&src=http%3A%2F%2Fimg.ewebweb.com%2Fuploads%2F20191203%2F16%2F1575361350-YwtjLgUXBq.jpg

实现Runnable接口

  1. 定义类实现Runnable接口
  2. 实现run()方法,编写线程执行体
  3. 传入类对象创建线程对象Thread,调用start()方法启动线程
package com.qing.demo01;

//创建线程方式二:实现Runnable接口,重写run()方法,传入类对象创建Thread对象,调用start()方法启动线程
public class TestThread3 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i 
main线程--0
run线程--0
run线程--1
run线程--2
run线程--3
run线程--4
run线程--5
run线程--6
run线程--7
run线程--8
run线程--9
run线程--10
run线程--11
run线程--12
main线程--1
main线程--2
main线程--3
main线程--4
main线程--5
main线程--6
main线程--7
main线程--8
main线程--9
main线程--10
main线程--11
main线程--12
run线程--13

继承Thread类和实现Runnable接口的区别

  1. 继承Thread类
    1. 子类继承Thread类具备多线程能力
    2. 启动线程:子类对象.start()
    3. 不建议使用:避免单继承局限性
  2. 实现Runnable接口
    1. 实现接口Runnable具有多线程能力
    2. 启动线程:new Thread(实现类对象).start()
    3. 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

并发问题

package com.qing.demo01;

/**
 * 买火车票的例子
 * 多个线程同时操作同一个对象
 * 发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱。
 */
public class TestThread4 implements Runnable {

    //票数
    private int ticketNums = 10;

    @Override
    public void run() {
        while (true) {
            if (ticketNums 买到了第" + ticketNums-- + "票");
        }
    }

    public static void main(String[] args) {
        TestThread4 testThread4 = new TestThread4();
        new Thread(testThread4,"杨康").start();
        new Thread(testThread4,"郭靖").start();
        new Thread(testThread4,"黄蓉").start();
    }
}
黄蓉-->买到了第10票
杨康-->买到了第9票
郭靖-->买到了第10票
黄蓉-->买到了第8票
郭靖-->买到了第7票
杨康-->买到了第6票
杨康-->买到了第5票
郭靖-->买到了第5票
黄蓉-->买到了第5票
杨康-->买到了第4票
郭靖-->买到了第2票
黄蓉-->买到了第3票
郭靖-->买到了第1票
黄蓉-->买到了第-1票
杨康-->买到了第0票

龟兔赛跑

package com.qing.demo01;

/**
 * 模拟龟兔赛跑
 */
public class Race implements Runnable {

    //胜利者
    private static String winner;

    @Override
    public void run() {
        for (int i = 1; i 跑了" + i + "步");
        }
    }

    //判断是否完成比赛
    private boolean gameOver(int steps) {
        //判断是否有胜利者
        if (winner != null) {
            return true;
        }
        //判断是否跑完100步
        if (steps >= 100) {
            winner = Thread.currentThread().getName();
            System.out.println("winner is " + winner);
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}
乌龟-->跑了98步
乌龟-->跑了99步
winner is 乌龟
兔子-->跑了28步

实现Callable接口

  1. 实现Callable接口,需要返回值类型
  2. 重新call()方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService service = Executors.newFixedThreadPool(3);
  5. 提交执行:Future r1 = service.submit(t1);
  6. 获取结果:boolean rs1 = r1.get();
  7. 关闭服务:service.shutdownNow();
package com.qing.demo02;

import com.qing.demo01.TestThread2;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

/**
 * 实现多线程同步下载图片
 * 线程创建方式三:实现Callable接口
 * Callable的好处
 * 1.可以定义返回值
 * 2.可以抛出异常
 */
public class TestCallable implements Callable {

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

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

    @Override
    public Boolean call() throws Exception {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url, name);
        System.out.println("下载文件,name: " + name + ",url: " + url);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605724974060&di=abc373a617b841a8e78b3c53fcbd9b5c&imgtype=0&src=http%3A%2F%2Fimg.ewebweb.com%2Fuploads%2F20191203%2F16%2F1575361350-YwtjLgUXBq.jpg", "1.jpg");
        TestCallable t2 = new TestCallable("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1578345841,1712921595&fm=26&gp=0.jpg", "2.jpg");
        TestCallable t3 = new TestCallable("https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3131683833,708813674&fm=26&gp=0.jpg", "3.jpg");

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

        //提交执行
        Future r1 = service.submit(t1);
        Future r2 = service.submit(t2);
        Future r3 = service.submit(t3);

        //获取结果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();
        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);

        //关闭服务
        service.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方法出现问题");
        }
    }
}
下载文件,name: 3.jpg,url: https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3131683833,708813674&fm=26&gp=0.jpg
下载文件,name: 1.jpg,url: https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605724974060&di=abc373a617b841a8e78b3c53fcbd9b5c&imgtype=0&src=http%3A%2F%2Fimg.ewebweb.com%2Fuploads%2F20191203%2F16%2F1575361350-YwtjLgUXBq.jpg
下载文件,name: 2.jpg,url: https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1578345841,1712921595&fm=26&gp=0.jpg
true
true
true

静态代理模式

  1. 真实对象和代理对象都要实现同一个接口
  2. 代理对象要代理真实对象,真实对象作为代理对象的属性
//真实对象
public class TestThread3 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i 
//代理对象
public class Thread implements Runnable {
    //真实对象
    private Runnable target;
    
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

静态代理模式的好处

  1. 代理对象可以做很多真实对象做不了的事情
  2. 真实对象专注做自己的事情

Lambda表达式

为什么要使用Lambda表达式

  1. 避免匿名内部类定义过多
  2. 可以让代码看起来简洁
  3. 去掉了一堆没有意义的代码,只留下核心的逻辑

函数式接口Functional Interface

  1. 定义:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
public interface Runnable {
    public abstract void run();
}
  1. 对于函数式接口,可以通过lambda表达式来创建该接口的对象。
  2. 推导lambda表达式
package com.qing.lambda;

/**
 * 推导lambda表达式
 */
public class TestLambda01 {
    //3.静态内部类
    static class Like2 implements ILike {
        @Override
        public void lambda() {
            System.out.println("I like lambda2");
        }
    }

    public static void main(String[] args) {
        ILike like = new Like();
        like.lambda();

        like = new Like2();
        like.lambda();

        //4.局部内部类
        class Like3 implements ILike {
            @Override
            public void lambda() {
                System.out.println("I like lambda3");
            }
        }

        like = new Like3();
        like.lambda();

        //5.匿名内部类:没有类的名称,必须借助接口或者父类
        like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("I like lambda4");
            }
        };
        like.lambda();

        //6.用lambda表达式简化
        like = ()->{
            System.out.println("I like lambda5");
        };
        like.lambda();
    }
}

//1.定义一个函数式接口:只有一个抽象方法的接口
interface ILike {
    void lambda();
}

//2.实现类
class Like implements ILike {
    @Override
    public void lambda() {
        System.out.println("I like lambda");
    }
}
I like lambda
I like lambda2
I like lambda3
I like lambda4
I like lambda5
package com.qing.lambda;

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

//1.定义一个函数式接口:只有一个抽象方法的接口
interface ILike {
    void lambda();
}
  1. 有参数lambda表达式
package com.qing.lambda;

public class TestLambda02 {
    public static void main(String[] args) {
        //1.lambda表达式简化
        ILove love = (int i)->{
            System.out.println("I love you-->" + i);
        };
        love.love(520);

        //简化1.简化参数类型
        love = (i)->{
            System.out.println("I love you-->" + i);
        };
        love.love(521);

        //简化2.简化参数括号
        love = i->{
            System.out.println("I love you-->" + i);
        };
        love.love(522);

        //简化3.简化方法括号
        love = i->System.out.println("I love you-->" + i);
        love.love(523);
    }
}

interface ILove {
    void love(int i);
}
I love you-->520
I love you-->521
I love you-->522
I love you-->523
package com.qing.lambda;

public class TestLambda02 {
    public static void main(String[] args) {
        ILove love = i->System.out.println("I love you-->" + i);
        love.love(523);
    }
}

interface ILove {
    void love(int i);
}

线程状态

  1. 创建状态
  2. 就绪状态
  3. 运行状态
  4. 阻塞状态
  5. 死亡状态

技术图片
技术图片
技术图片

线程停止

  1. 不推荐使用JDK提供的stop()、destroy()方法(已废弃)。
  2. 推荐线程自己停止下来。
  3. 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行。
package com.qing.state;

/**
 * 测试停止线程
 * 1.建议线程正常停止-->利用次数,不建议死循环。
 * 2.建议使用标志位-->设置一个标志位。
 * 3.不要使用stop()或destroy()等过时或者JDK不建议使用的方法。
 */
public class TestStop implements Runnable {

    //1.设置一个标志位
    private boolean flag = true;

    @Override
    public void run() {
        int i = 0;
        while (flag) {
            System.out.println("run...Thread" + i++);
        }
    }

    //2.设置一个公开的方法停止线程:转换标志位
    public void stop() {
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();

        for (int i = 0; i 
main...899
main...900
run...Thread1366
run...Thread1367
线程该停止了
main...901
main...902

线程休眠

  1. sleep(毫秒)指定当前线程阻塞的毫秒数。
  2. sleep存在异常InterruptedException。
  3. sleep时间达到后线程进入就绪状态。
  4. sleep可以模拟网络延时、倒计时等。
  5. 每一个对象都有一个锁,sleep不会释放锁。

模拟网络延时:放大问题发生可能性

package com.qing.state;

/**
 * 模拟网络延时:放大问题发生可能性。
 */
public class TestSleep implements Runnable {

    //票数
    private int ticketNums = 10;

    @Override
    public void run() {
        while (true) {
            if (ticketNums 买到了第" + ticketNums-- + "票");
        }
    }

    public static void main(String[] args) {
        TestSleep testSleep = new TestSleep();
        new Thread(testSleep,"杨康").start();
        new Thread(testSleep,"郭靖").start();
        new Thread(testSleep,"黄蓉").start();
    }
}
郭靖-->买到了第9票
杨康-->买到了第8票
黄蓉-->买到了第10票
郭靖-->买到了第7票
杨康-->买到了第6票
黄蓉-->买到了第5票
黄蓉-->买到了第4票
杨康-->买到了第3票
郭靖-->买到了第2票
杨康-->买到了第1票
郭靖-->买到了第-1票
黄蓉-->买到了第0票

模拟倒计时

package com.qing.state;

/**
 * 模拟倒计时
 */
public class TestSleep2 {

    public static void tenDown() throws InterruptedException {
        int num = 10;
        while (true) {
            Thread.sleep(1000);
            System.out.println(num--);
            if (num 
10
9
8
7
6
5
4
3
2
1

模拟时钟

package com.qing.state;

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

/**
 * 模拟时钟
 */
public class TestSleep3 {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        while (true) {
            try {
                System.out.println(sdf.format(new Date()));
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
23:45:39
23:45:40
23:45:41
23:45:42
23:45:43

线程礼让

  1. 礼让线程,让当前正在执行的线程暂停,但不阻塞。
  2. 将线程从运行状态转为就绪状态。
  3. 让CPU重新调度,礼让不一定成功,由CPU决定。
package com.qing.state;

/**
 * 测试礼让线程
 * 礼让不一定成功,由CPU决定
 */
public class TestYield {
    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "线程开始执行");
            Thread.yield();
            System.out.println(Thread.currentThread().getName() + "线程执行完成");
        },"a").start();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "线程开始执行");
            System.out.println(Thread.currentThread().getName() + "线程执行完成");
        },"b").start();
    }
}
//礼让成功
a线程开始执行
b线程开始执行
b线程执行完成
a线程执行完成
//礼让不成功
a线程开始执行
a线程执行完成
b线程开始执行
b线程执行完成

线程强制执行

  1. join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。
  2. 可以想象成插队。
package com.qing.state;

/**
 * 测试join方法,想象为插队
 */
public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i 
main线程98
main线程99
线程vip来了82
线程vip来了83
线程vip来了84
...
线程vip来了498
线程vip来了499
main线程100
main线程101

线程状态观测

public class Thread implements Runnable {
    public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
}

线程状态

  1. NEW:未启动状态。
  2. RUNNABLE:就绪状态。
  3. BLOCKED:阻塞状态。
  4. WAITING:阻塞状态。
  5. TIMED_WAITING:阻塞状态。
  6. TERMINATED:死亡状态。
package com.qing.state;

/**
 * 观察测试线程的状态
 */
public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i 
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
--------------------------
TIMED_WAITING
--------------------------
TIMED_WAITING
TIMED_WAITING
--------------------------
TIMED_WAITING
TIMED_WAITING
--------------------------
TIMED_WAITING
TIMED_WAITING
--------------------------
TERMINATED

线程优先级

  1. Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
  2. 线程的优先级用数字表示,范围从1~10。
    1. Thread.MIN_PRIORITY = 1;
    2. Thread.MAX_PRIORITY = 10;
    3. Thread.NORM_PRIORITY = 5;
  3. 使用以下方式改变或获取优先级。
    1. getPriority()
    2. setPriority(int xxx)
  4. 优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度。
  5. 优先级高的大部分情况先执行,但并不绝对。
package com.qing.state;

/**
 * 测试线程的优先级
 */
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);

        //先设置优先级在启动线程
        t1.start();
        t2.setPriority(Thread.MIN_PRIORITY);
        t2.start();
        t3.setPriority(Thread.NORM_PRIORITY);
        t3.start();
        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();
        t5.setPriority(3);
        t5.start();
        t6.setPriority(7);
        t6.start();
    }
}

class MyPriority implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
    }
}
main-->5
Thread-3-->10
Thread-5-->7
Thread-1-->1
Thread-0-->5
Thread-4-->3
Thread-2-->5

守护线程(daemon)

  1. 线程分为用户线程和守护线程。
  2. 虚拟机必须确保用户线程执行完毕。
  3. 虚拟机不用等待守护线程执行完毕。
  4. 守护线程,如:后台记录操作日志,监控内存,垃圾回收等待等。
package com.qing.state;

/**
 * 测试守护线程
 */
public class TestDaemon {
    public static void main(String[] args) {
        Thread god = new Thread(()->{
            while (true) {
                System.out.println("上帝保佑你");
            }
        });
        //设置为守护线程,默认是false,表示是用户线程,正常的线程都是用户线程
        god.setDaemon(true);
        god.start();
        Thread you = new Thread(()->{
            for (int i = 0; i 
上帝保佑你
上帝保佑你
上帝保佑你
0
1
2
3
97
98
99
上帝保佑你
上帝保佑你
上帝保佑你
===========================
上帝保佑你
上帝保佑你
上帝保佑你

090_多线程(一)

标签:VID   out   jpg   定义   返回   使用   game   exec   executor   

原文地址:https://www.cnblogs.com/wl3pb/p/14085993.html


评论


亲,登录后才可以留言!