• Java多线程


    Java多线程

    什么是进程?

    电脑中时会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。比如下图中的QQ、酷狗播放器、电脑管家等等。在这里插入图片描述

    什么是线程?

    进程想要执行任务就需要依赖线程。换句话说,就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。

    • 进程是程序执行的一次过程,它是一个动态的概念。是系统资源分配的单位。
    • 通常在一个进制中可以包含多个线程,线程是CPU调度和执行的单位
    • 多线程是模拟出来的,在一个CPU下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以有同时执行的错觉
    • main()被称为主线程,为系统入口,用于执行整个程序;
    • 在一个进程中,如果开辟了多个线程,线程的运行由调度器调度,调度器与操作系统紧密相关的,先后顺序是不能人为干预。
    • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
    • 线程会带来额外的开销,如cpu调度时间,并发开销控制
    • 每个线程在自己的工作内存交互,内存控制不会造成数据不一致

    线程的创建

    Thread. Runnable .Callabele

    三种创建方式

    • Thread class 继承 Thread 类

    • Runnable 接口 实现Runnable 接口

    • Callable 接口 实现Callable 接口

    • 自定义线程类继承 Thread类

    • 重写Run()方法,编写线程执行体

    • 创建线程对象,使用start()方法启动线程

    package Thread;
    
    //线程开启,不一定立即执行,由cpu调度
    //创建线程方式一 : 继承Thread类,重写run()方法,调用 start 开启 线程
    public class TestThread extends Thread {
        @Override
        public void run() {
           //run方法线程体
            for (int i = 0; i < 20; i++) {
                System.out.println("你好呀"+i);
            }
        }
    
        public static void main(String[] args) {
            //开启 Thread线程
            TestThread testThread = new TestThread();
            //调用start方法
            testThread.start();
            //main 线程,主线程
            for (int i = 0; i < 200; i++) {
                System.out.println("我在学习多线程--"+i);
            }
        }
    }
    

    案例:下载图片

    package Thread;
    
    import org.apache.commons.io.FileUtils;
    
    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    
    //练习Thread, 实现多线程同步下载图片
    public class TestThread extends Thread {
    
        private String url;//网络图片地址
        private String name;//网络图片保存地址
    
        public TestThread(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);
        }
    
    
        public static void main(String[] args) {
            TestThread testThread1 = new TestThread("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1113%2F041620103S8%2F200416103S8-4-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1611366394&t=5011c9066b705d0e9713f989cd6d44c8", "1.jpg");
            TestThread testThread2 = new TestThread("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1113%2F041620103S8%2F200416103S8-4-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1611366394&t=5011c9066b705d0e9713f989cd6d44c8", "2.jpg");
            TestThread testThread3 = new TestThread("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1113%2F041620103S8%2F200416103S8-4-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1611366394&t=5011c9066b705d0e9713f989cd6d44c8", "3.jpg");
    
            testThread1.start();
            testThread2.start();
            testThread3.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 异常,dwonloader方法出现问题");
            }
        }
    
    }
    
    

    实现Runnable

    • 定义MyTunnable类实现Runnable接口
    • 实现Run()方法,编写线程执行体
    • 创建线程执行体,调用start()方法启动
    package Thread;
    
    public class TestThread3 implements Runnable{
        @Override
        public void run() {
            //run方法体
            for (int i = 0; i < 200; i++) {
                System.out.println("我在看代码---"+i);
            }
        }
    
        public static void main(String[] args) {
            //创建runnable接口实现类对象
            TestThread3 testThread3 = new TestThread3();
            //创建线程对象,通过线程对象来开启我们的线程,代理
            //把 runnable的接口对象 丢进去
     //     Thread thread = new Thread(testThread3);
            //threa 调用 start
     //     thread.start();
    
            new Thread(testThread3).start();
            for (int i = 0; i < 200; i++) {
                System.out.println("我在学习!!"+i);
            }
    
        }
    }
    

    建议使用 Runnable

    避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用

    //一份资源
    StratThread station = new StartThread();
    //多个代理
    new Thread(Startion,"小明").start();
    new Thread(Startion,"小红").start();
    new Thread(Startion,"小强").start();
    

    并发问题

    多个线程同时操作 同一个对象 有一个问题 : 发生了数据不安全 数据紊乱

    抢火车票的例子

    package Thread;
    
    import static java.lang.Thread.sleep;
    
    //多个线程同时操作 同一个对象  有一个问题  : 发生了数据不安全 数据紊乱
    //抢火车票模拟
    public class TesrThread4 implements Runnable{
    
    
        //票书
        private int ticketNums = 10;
    
        @Override
        public void run() {
            while (true) {
                if (ticketNums <= 0) {
                    break;
                }
                //制造延时
                try {
                    sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "张票");
            }
        }
    
        public static void main(String[] args) {
            TesrThread4 ticket = new TesrThread4();
    
            new Thread(ticket,"小明").start();
            new Thread(ticket,"老师").start();
            new Thread(ticket,"黄牛党").start();
        }
    
    }
    
    输出结果:
    小明拿到了第10张票
    老师拿到了第9张票
    黄牛党拿到了第10张票
    小明拿到了第8张票
    黄牛党拿到了第7张票
    老师拿到了第8张票
    小明拿到了第6张票
    黄牛党拿到了第4张票
    老师拿到了第5张票
    老师拿到了第3张票
    小明拿到了第1张票
    黄牛党拿到了第2张票
    

    案例 龟兔赛跑

    • 需要赛道 , 越跑距离终点越近

    • 判断比赛是否结束

    • 打印出胜利者

    • 龟兔赛跑开始

    • 乌龟赢,兔子需要睡觉

    • 乌龟赢了比赛

    package Thread;
    
    public class Race implements Runnable{
    
        //胜利者
        private static String winner;
        @Override
        public void run() {
    
            for (int i = 0; i <= 100; i++) {
    
                //判断比赛是否结束
                boolean flag = gameOver(i);
                if(flag){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
            }
        }
    
        //判断 是否 完成比赛
        private boolean gameOver(int steps){
            //判断是否有胜利者
            if(winner != null){
                return true;
            }else {
                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();
        }
    
    }
    
    输出结果:
    兔子-->跑了68步
    兔子-->跑了69步
    兔子-->跑了70步
    兔子-->跑了71步
    兔子-->跑了72步
    兔子-->跑了73步
    兔子-->跑了74步
    乌龟-->跑了91步
    兔子-->跑了75步
    兔子-->跑了76步
    兔子-->跑了77步
    兔子-->跑了78步
    兔子-->跑了79步
    兔子-->跑了80步
    乌龟-->跑了92步
    乌龟-->跑了93步
    乌龟-->跑了94步
    乌龟-->跑了95步
    乌龟-->跑了96步
    乌龟-->跑了97步
    乌龟-->跑了98步
    乌龟-->跑了99步
    Winner is 乌龟
    兔子-->跑了81步
    

    静态代理

    举办 婚礼

    • 人 婚礼的主体
    • 婚庆公司 代理承办婚礼
    • 婚礼 去实现婚礼
    package Thread;
    
    //静态代理模式总结
    //真实对象和代理对象都要实现同一个接口
    //代理对象要代理真实角色
    
    //好吃 :
    //代理对象可也做很多代理对象做不了的事情
    //真实对象做自己的事情
    public class StaticProxy {
        public static void main(String[] args) {
            WeddingComany weddingComany = new WeddingComany(new You());
            weddingComany.HappyMarry();
        }
    }
    
    interface Marry{
        void HappyMarry();
    }
    
    //真实角色 结婚的人
    class You implements Marry{
        @Override
        public void HappyMarry() {
            System.out.println("我要结婚了,超开心");
        }
    }
    
    //代理角色 承办结婚的机构
    class WeddingComany implements Marry{
    
        //真实目标角色
        private Marry target;
    
        public WeddingComany(Marry You) {
            this.target = You;
        }
    
        @Override
        public void HappyMarry() {
            before();
            this.target.HappyMarry();//这就是真实对象。
            after();
    
        }
        public void before(){
            System.out.println("布置现场");
        }
    
        public void after(){
            System.out.println("打扫干净");
        }
    
    
    }
    输出结果:
    布置现场
    我要结婚了,超开心
    打扫干净 
    

    Lamda 表达式

    函数式接口定义

    • 任何接口 , 如果只含唯一一个抽象方法,那么它就是一个函数式接口

    • Public interface Runnable{
      	public abstract void run();
      }
      
    • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。

    无参数

    package Thread;
    //推导Lambda 表达式
    public class Lambda {
    
        //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 用 lamdba 简化  只有一个 接口 和 一个 方法 所有 中 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 Thread;
    
    public class Lambda_CanShu {
    
        public static void main(String[] args) {
    
            ILove love = (int a)-> {
                    System.out.println("i love"+a);
                };
    
            //1Lambda 表达式简化 去掉 int 多个参数 也可以都去掉 但是必须加上括号。
            love = (int a)-> {
                System.out.println("i love"+a);
            };
            //2Lambda简化    去掉括号 
            love =a -> {
                System.out.println("i love"+a);
            };
            //3 Lambda 简化  去掉花括号
            love = a-> System.out.println("i Love u ");
            love.love(1);
        }
    }
    //接口必须为函数式接口   : 接口只有一个方法
    interface ILove{
        void love(int a);
    }
    

    线程的五大状态

    停止进程

    • 不推荐使用JDK停供stop(),destory() 方法【已废弃】
    • 推荐线程自己停止下来
    • 建议使用一个标志位终止变量 当Flag = false 则终止程序运行
    package Thread;
    //测试 stop
    //1 建议 线程正常 停止, 利用次数,不建议死循环
    //2 建议使用标志位  设置 一个 标志位
    //3 不要使用 stop 或者 destory 等 过时或者 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 < 1000; i++) {
                System.out.println("main" + i);
                if(i == 500){
                    //调用stop()方法
                    testStop.stop();
                    System.out.println("该线程停止");
                    }
    
            }
        }
    }
    
    

    线程休眠

    • sleep(毫秒)

    • 存在 InterruptException 中断异常

    • sleep时间到达后 线程进入就绪状态

    • sleep可以模拟网络延时 倒计时等

    • 每个对象都有一个锁,sleep不会释放锁

    • 放大问题的发生性

    package Thread;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    //模拟网络 延时  : 放大问题的发生性
    public class TestSleep {
        public static void main(String[] args) throws InterruptedException {
            //tenDown();倒计时
            //打印当前时间
            Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间
    
            while (true){
                Thread.sleep(1000);
                 startTime = new Date(System.currentTimeMillis());//刷新当前时间
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
    
            }
        }
    
        //模拟倒计时
        public static void tenDown() throws InterruptedException{
            int num = 10;
            while (true){
                Thread.sleep(1000);
                System.out.println(num--);
                if(num <= 0){
                    break;
                }
            }
        }
    
    }
    

    Join

    • Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
    • 可以想象成插队
    • 少用 join 会让线程堵塞
    package Thread;
    
    import javax.sound.midi.Soundbank;
    
    //测试Join方法  想象成 插队
    public class Testjoin implements Runnable{
        public static void main(String[] args) throws InterruptedException {
    
            //启动我们的线程
            Testjoin testjoin = new Testjoin();
            Thread thread = new Thread(testjoin);
            thread.start();
    
            //主线程
            for (int i = 0; i < 500; i++) {
                if(i==200){
                    thread.join();//插队
                }
                System.out.println("main"+i);
            }
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("线程VIP来了"+i);
            }
        }
    }
    
    

    观测线程状态

    • new
      • 新生
    • runnable
      • 就绪(虚拟机)
    • blocked
      • 阻塞
    • waitting
      • 等待另一个线程的特定动作
    • Timed_watting
      • 等待另一个线程执行完
    • terminated
      • 已退出的线程处于此状态 不能被再次 start()
    观测线程状态
    package Thread;
    
    //观测线程状态
    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(500);//睡觉5秒 线程一直等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("睡醒了");
            });
    
            //观测状态
            Thread.State state = thread.getState();
            System.out.println(state); // 新生
    
            //观测启动后
            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);//输出状态
    
            }
            
            thread.start();//会报错 ,停止后的线程 不能继续开始 
    
        }
    
    }
    
    输出结果:
    NEW
    RUNNABLE
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    
    
    ///////
    TERMINATED
    

    线程优先级

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

    • 线程优先级 用数字表示 范围 1~~10

      • Thread.MIN_PRIORITY = 1;
      • Thread.MAX_PRIORITY = 1;
      • Thread.NORM_PRIORITY = 1;
    • 使用以下方法改变或获取优先级

      • getPriority()
      • setPriority()
    package Thread;
    
    //测试线程优先级
    public class TestPriority extends Thread{
        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);
            //先设置优先级再启动
            //t1 给个默认优先级
            t1.start();
            t2.setPriority(1);
            t2.start();
            t3.setPriority(8);
            t3.start();
            t4.setPriority(9);
            t4.start();
            
        }
    }
    
    class MyPriority implements Runnable{
        @Override
        public void run() {
            //线程名字+线程优先级
            System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        }
    }
    
    
    输出
        main-->5
    Thread-0-->5
    Thread-1-->1
    Thread-3-->9
    Thread-2-->8
    

    守护(deamon)线程

    • 线程分为 用户线程守护线程
    • 虚拟机 必须 确保 用户线程 执行完毕
    • 虚拟机 不用 等待守护线程执行完毕
    • 如 后台操作日志 , 监控内存,垃圾回收

  • 相关阅读:
    java全栈day01-03注释、关键字与标识符
    java全栈day01-02入门案例
    java全栈day01-01
    Python中list常用的10个基本方法----list的灰魔法
    python开发[第二篇]------str的7个必须掌握的方法以及五个常用方法
    Python开发【第二篇】:Python基本数据类型
    爬虫相关
    存储库-MongoDB简单的操作
    解析库-beautifulsoup模块
    拉勾网自动发送简历
  • 原文地址:https://www.cnblogs.com/AronJudge/p/14187098.html
Copyright © 2020-2023  润新知