【JAVA SE】——多线程

线程简介

  • 进程和线程
    – 进程(process):执行程序的一次执行过程,是系统资源分配的单位;
    – 线程(thread):一个进程可包含多个线程,是cpu和执行的单位。
    – 要区别多核(真正的并行)和单核(模拟)
    – main主线程,gc线程

  • 线程的调度和操作系统相关,不可人工干预

  • 同一资源需要并发控制

  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

线程创建

1. Thread:继承
  • 步骤:

    1.自定义的线程类继承Thread类

    2.重写run()方法,编写线程执行体

    3.创建线程对象,调用start()方法启动线程

    总结:线程不一定立即执行,而是并行交替进行,由cpu安排调度

  • 代码实现:

    //1. 继承Thread类
    public class TestThread1 extends Thread{
        //2. 重写run方法
        @Override
        public void run(){
            //线程体
            for(int i = 0;i < 20;i++){
                System.out.println("run:"+i);
            }
        }
    
        public static void main(String[] arg){
            //main线程,主线程
    
            //创建线程对象
            TestThread1 testThread1 = new TestThread1();
            //3. 调用start()方法开启线程(交替执行,并行)
            testThread1.start();
    
            //先run后main(不是并发)
    //        testThread1.run();
    
            for (int i = 0; i < 20; i++) {
                System.out.println("main:"+i);
            }
        }
    }
    
  • 实例:图片下载器

    //实现多线程同步下载图片
    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);
    
        }
    
        public static void main(String[] args) {
            TestThread2 testThread1 = new TestThread2("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_220,limit_1", "./pic1.png");
            TestThread2 testThread2 = new TestThread2("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_640,limit_1", "./pic2.png");
            TestThread2 testThread3 = new TestThread2("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_220,limit_1", "./pic3.png");
    
            //下载结果不是按顺序执行,同时进行的
            testThread1.start();
            testThread2.start();
            testThread3.start();
        }
    
    
    }
    
    //下载器
    class WebDownloader{
        //下载方法
        public void downloader(String url, String name){
            try {
                //把url返回到一个file
                FileUtils.copyURLToFile(new URL(url),new File(name));
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("IO异常,downloader方法出现问题");
            }
        }
    }
    
2. Runnable:实现
  • 步骤

    1.定义MyRunnable类实现Runnable接口

    2.重写run()方法,编写线程执行体

    3.创建线程对象,调用Thread对象.start()方法启动线程

    总结:推荐使用,避免单继承局限性,方便同一个对象被多个线程使用

  • 静态代理模式

    – 特点

    真实对象和代理对象都要实现同一个接口

    代理对象要代理真实角色

    – 好处

    代理角色可以做很多真实对象做不了的事

    真实对象专注做自己的事情

  • 代码

    //实现runnable接口
    public class TestThread3 implements Runnable{
        //2. 重写run方法
        @Override
        public void run(){
            //线程体
            for(int i = 0;i < 20;i++){
                System.out.println("run:"+i);
            }
        }
    
        public static void main(String[] arg){
            //main线程,主线程
    
            //创建runnable接口的实现类对象
            TestThread3 testThread3 = new TestThread3();
            //创建线程对象,通过线程对象来开启我们的线程,代理
            Thread thread = new Thread(testThread3);
            thread.start();
    
    //        new Thread(testThread3).start();
    
            for (int i = 0; i < 20; i++) {
                System.out.println("main:"+i);
            }
        }
    }
    
    
  • 实例一:修改图片下载器

    //实现多线程同步下载图片
    public class TestThread4 implements Runnable{
        private String url;
        private String name; //保存文件名
    
        public TestThread4(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) {
            TestThread2 testThread1 = new TestThread2("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_220,limit_1", "./pic1.png");
            TestThread2 testThread2 = new TestThread2("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_640,limit_1", "./pic2.png");
            TestThread2 testThread3 = new TestThread2("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_220,limit_1", "./pic3.png");
    
            //下载结果不是按顺序执行,同时进行的
            new Thread(testThread1).start();
            new Thread(testThread2).start();
            new Thread(testThread3).start();
        }
    }
    
    
  • 实例二:买票 说明同一个对象被多个线程使用

    //发现问题:多个线程不安全
    public class TestThread5 implements Runnable{
    
        //票数
        private int ticketNums = 10;
    
        @Override
        public void run(){
            while(true){
                if(ticketNums<=0){
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "-->拿到了第"+ticketNums+"票");
                ticketNums--;
            }
        }
    
        public static void main(String[] args) {
            TestThread5 ticket = new TestThread5();
    
            new Thread(ticket,"小明").start();
            new Thread(ticket,"老师").start();
            new Thread(ticket,"黄牛").start();
        }
    }
    
  • 案例三:龟兔赛跑

    public class Race implements Runnable{
        //胜利者
        private static String winner;
    
        @Override
        public void run(){
            //模拟赛道
            for (int i = 0; i <= 100; i++) {
                //模拟兔子休息
                if(Thread.currentThread().getName().equals("兔子") && i%20==0){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
                //判断比赛是否结束
                boolean flag = gameOver(i);
    
                //比赛结束就停止程序
                if(flag){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"-->跑了" + i + "步");
            }
        }
        
        //查看游戏是否结束
        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;
        }
    
        public static void main(String[] args) {
            Race race = new Race();
    
            new Thread(race,"兔子").start();
            new Thread(race,"乌龟").start();
        }
    
3. Callable:实现(了解)
  • 代码

    //实现多线程同步下载图片
    //多了返回值
    //可以加异常
    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 testThread1 = new TestCallable("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_220,limit_1", "./pic1.png");
            TestCallable testThread2 = new TestCallable("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_640,limit_1", "./pic2.png");
            TestCallable testThread3 = new TestCallable("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_220,limit_1", "./pic3.png");
    
            //1.创建执行服务
            ExecutorService ser = Executors.newFixedThreadPool(3);
    
            //2.提交执行
            Future<Boolean> r1 = ser.submit(testThread1);
            Future<Boolean> r2 = ser.submit(testThread2);
            Future<Boolean> r3 = ser.submit(testThread3);
    
            //3. 获取结果
            boolean rs1 = r1.get();
            boolean rs2 = r2.get();
            boolean rs3 = r3.get();
    
            //4.关闭服务
            ser.shutdown();
    
        }
    }
    

Lamda表达式

  1. 必须是函数式接口:任何接口,只有唯一一个抽象方法

    – 静态内部类:放在类里

    – 局部内部类:放在方法里

    – 匿名内部类:没有类的名称,必须借助接口或者父类

    – lambda简化:

    like = ()->{
    	System.out.pringln("");
    };
    like.lambda();
    
  2. 多行就用代码块包裹

  3. 多个参数也可以去掉参数类型,必须加括号

线程状态

1. 前言

在这里插入图片描述

  • 解释

    创建:new

    就绪:start

    阻塞:sleep,wait,同步锁定等

    运行:cpu真正让其运行

    死亡:线程中断或结束

  • 常用方法

    setPriority():设置一个线程的优先级

    sleep():强迫一个线程睡眠N毫秒

    join():等待线程终止

    yield():线程礼让

    interrupt():中断【不推荐】

    isAlive():判断一个线程是否存活

2. 状态:线程停止
  • 方法:通过设置停止标志位,线程调用用户自己写的停止线程的方法,使得线程停止

  • 实例:当主线程打印输出900次时,将线程Thread停止

    //1. 建议线程正常停止:利用次数,不建议死循环
    //2. 建议使用标志位:设置标志位
    //3. 不要使用srop destroy等过时方法
    
    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==900){
                    //调用Stop方法切换,让线程停止
                    testStop.stop();
                    System.out.println("线程该停住了");
                }
            }
        }
    }
    
    
3. 状态:线程休眠
  • 方法:线程通过调用sleep()方法,实现线程休眠。

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

  • 实例:实现10秒倒计时,每打印一个数字,线程休眠一秒,再打印下一个数字。

    // 线程休眠
    public class TestSleep{
        public static void main(String[] args) {
            try {
                tenDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public static void tenDown() throws InterruptedException {
            int num = 10;
            while (true){
                Thread.sleep(1000);
                System.out.println(num--);
                if (num < 0){
                    break;
                }
            }
        }
    }
    
4. 状态:线程礼让
  • 方法:线程通过调用yield()方法,让正在执行的线程暂停,但不阻塞,转为就绪状态。

  • 注意:礼让不一定成功,看cpu心情

  • 实例:

    //礼让
    //不一定成功
    public class TestYield {
        public static void main(String[] args) {
            MyYield myYield = new MyYield();
    
            new Thread(myYield,"a").start();
            new Thread(myYield,"b").start();
    
        }
    }
    
    class MyYield implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "线程开始执行");
            Thread.yield();
            System.out.println(Thread.currentThread().getName() + "线程停止执行");
        }
    }
    
5. 状态:线程强制执行
  • 方法:利用join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞,插队

  • 实例

    //join方法,插队
    public class TestJoin implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("线程vip来了"+i);
            }
        }
    
        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);
            }
        }
    }
    
6. 线程状态观测
  • 线程状态:

    1)NEW:尚未启动的线程处于该状态。

    2)RUNNABLE:在java虚拟机中执行的线程处于此状态。

    3)BLOCKED:被阻塞等待监视器锁定的线程处于此状态。

    4) WAITING:正在等待另一个线程执行特定动作的线程处于此状态。
    5)TIMED_WAINTING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。

    6)TERMINATED:已退出的线程处于此状态。

  • 方法:线程调用getState()方法获取线程状态

  • 注意:死亡后的线程,不能再次启动

  • 案例

    public class TestState{
    
        public static void main(String[] args) throws InterruptedException {
            //利用lambda表达式重写run
            Thread thread = new Thread(()->{
                for (int i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(1000); //1s
                    } 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); //输出状态
            }
        }
    }
    
7. 线程优先级
  • 内容:线程的优先级有1-10级,数字越大优先级越高。但是优先级越高cpu也不一定优先调用。

  • 方法:
    获取线程优先级:getPriority()
    设置线程优先级:setPriority(int priority)

  • 注意:先设置优先级再启动

  • 实例:设置不同线程的优先级并查看调用结果

    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);
    
            //先设置优先级,再启动
            t1.start();
    
            t2.setPriority(1);
            t2.start();
    
            t3.setPriority(4);
            t3.start();
    
            t4.setPriority(Thread.MAX_PRIORITY);
            t4.start();
    //
    //        t5.setPriority(-1);
    //        t5.start();
        }
    
    }
    
    class MyPriority implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        }
    }
    
    
8. 守护线程
  • 内容:线程分为用户线程和守护线程。

  • 注意:

    • 用户线程(main())虚拟机需要等它执行完毕后再停
    • 守护线程(gc())虚拟机无需等它执行完毕再停止。守护线程可用于后台记录操作的日志、监控内存、垃圾回收等待时。
  • 方法:线程**调用setDaemon(true)**方法。默认为false为用户线程。

  • 实例:依照视频中例子,模拟人类(用户线程)的生命有限,而上帝(守护线程)可以无限守护。当用户线程完成停止时,虚拟机停止,守护线程也无法继续进行。

    public class TestDaemon {
        public static void main(String[] args) {
            God god = new God();
            You you = new You();
    
            Thread thread = new Thread(god);
            thread.setDaemon(true); //守护线程,false是用户线程
    
            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("=======goodbye!world!======");
        }
    }
    

线程同步

1. Synchronize
  • 并发问题:同一对象被多个线程同时操作(内存)

    – 线程不安全,有负数

    – ArrayList不安全,copyOnWriteArrayList安全(JUC)

  • 同步方法:

    条件:队列 + 锁(对象)

    synchronized修饰

    public synchronized void play(xx){}
    

    默认锁的是this(类本身),可以修改锁的对象(需要增删改

  • 死锁:多个线程各自占有一些对方需要的资源,等待对方释放资源
    在这里插入图片描述

2. Lock
  • 可重入锁,可显式加锁

  • 类:ReentrantLock lock

  • 方法:lock.lock(); 加锁

    ​ lock.unlock(); 解锁

线程协作

1. 生产者和消费者
  • 方法:(只能在同步和通信的代码块中用)

    wait(): 线程等待,且和sleep不同,会释放锁

    notify(): 唤醒

    notifyAll():唤醒同一对象上所有调用wait()方法的线程

  • 方法一:共用缓冲区(管程法)

    方法二:标志位(信号灯法)

线程协作

1. 生产者和消费者
  • 方法:(只能在同步和通信的代码块中用)

    wait(): 线程等待,且和sleep不同,会释放锁

    notify(): 唤醒

    notifyAll():唤醒同一对象上所有调用wait()方法的线程

  • 方法一:共用缓冲区(管程法)

    方法二:标志位(信号灯法)

线程池

  • 问题:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

  • 解决:创建多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。

  • 好处:
    1) 提高响应速度;
    2) 降低资源消耗;
    3) 便于线程管理

  • 实现步骤

    1. 创建一个线程池服务

      ExecutorService:真正的线程池接口,常见子类为ThreadPoolExecutor。

      Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

      ExecutorService service = Executors.newFixedThreadPool(10);
      
    2. 执行

      service.execute(new MyThread());
      service.execute(new MyThread());
      
    3. 关闭链接

      service.shutdown();
      

本文是参考B站遇见狂神说的相关视频进行学习记录的,如有问题也欢迎大家一起交流~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值