• 一、多线程基础


    一、线程与进程

    • 线程是程序最小的执行单元。
    • 进程是操作系统进行资源分配和调度的一个基本单位。
    • 一个程序至少有一个进程,一个进程又至少包含一个线程,在程序运行时,即使自己没有创建线程,后台也会存在多个线程。如GC线程、主线程等。main线程称为主线程,为系统的入口点,用于执行整个程序。
    • 多个线程的执行是由操作系统进行调度的,执行顺序是不能人为干预的。操作系统为线程分配CPU时间片,线程之间进行争夺。
    • 每个线程都有自己的工作内存,但是加载和存储主内存(共享内存)控制不当会造成数据不一致。因此对同一份共享资源操作时,需要加入并发控制。
    • 引入线程的目的是为了充分利用CPU资源,使其可以并行处理多个任务,减少时间消耗,提升效率。但线程会带来额外的开销,如线程的创建和销毁时间,cpu调度时间,并发控制开销。因此,并不是线程创建的越多越好,为此引入了线程池,更好的进行资源控制。
    • 多线程并不一定快,受硬件、网络等其它因素的影响,比如网络带宽为2m,下载速度为1m/s,启动10个线程也不会达到10m/s。

    案例:多线程下载图片

    public class TDownLoader extends Thread {
    
        private String url;
        private String name;
    
        public TDownLoader(String url, String name) {
            this.url = url;
            this.name = name;
        }
    
        @Override
        public void run() {
            WebDownLoader downLoader = new WebDownLoader();
            downLoader.downLoad(url, name);
            System.out.println(name);
        }
    
        public static void main(String[] args) {
            TDownLoader t1=new TDownLoader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606411566549&di=9dfa479ec43c80d34ea4a84fc2c3f866&imgtype=0&src=http%3A%2F%2Fgss0.baidu.com%2F94o3dSag_xI4khGko9WTAnF6hhy%2Fzhidao%2Fpic%2Fitem%2F14ce36d3d539b6002ac5706de850352ac75cb7e4.jpg","C:\Users\Administrator\Desktop\ttt\t1.jpg");
            TDownLoader t2=new TDownLoader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606411566548&di=f901ee991c205196f80ca1f03508d31b&imgtype=0&src=http%3A%2F%2Fpic1.win4000.com%2Fpic%2F6%2Fa6%2F39b2375640.jpg","C:\Users\Administrator\Desktop\ttt\t2.jpg");
            TDownLoader t3=new TDownLoader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606411566547&di=a3caf55c682e66b095a4f0990ea3c0f7&imgtype=0&src=http%3A%2F%2Fpic1.win4000.com%2Fpic%2F8%2F09%2Fa615556832.jpg","C:\Users\Administrator\Desktop\ttt\t3.jpg");
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

    二、线程的分类

    线程分为用户线程和守护线程:
           用户线程(User Thread):一般是程序中创建的线程
           守护线程(Daemon Thread):为用户线程服务的线程,当所有用户线程结束时才会终止,如GC线程。通过Thread的setDaemon(true)方法将一个用户线程转为守护线程。

    三、线程的创建

    线程的创建方式主要有四种:
           1. 继承Thread类
           2. 实现Runnable接口
           3. 实现Callable接口,可以有返回值,需要结合FutureTask一块使用
           4. 通过线程池来进行任务执行

    方式一:继承Thread类

           声明一个Thread类的子类,这个子类重写run方法。使用时必须创建此子类的实例,调用start()方法启动线程

    package com.whw.thread.generic;
    
    /**
     * @description 线程的创建:继承Thread类
     */
    public class MyThread01 extends Thread {
        @Override
        public void run() {
            System.out.println("我是MyThread01");
        }
    }
    

    方式二:实现Runnable接口

           声明一个类实现Runnable接口,这个类实现run方法。使用时必须new一个Thread类,然后创建此子类的实例作为Thread类的参数。调用new出来的Thread类的start()方法

    package com.whw.thread.generic;
    
    /**
     * @description 线程的创建:实现Runnable接口
     */
    public class MyThread02 implements Runnable{
        @Override
        public void run() {
            System.out.println("我是MyThread02");
        }
    }
    

    使用:start()方法启动线程,并不是立即执行线程run方法,具体什么时候,需cpu调度,人为无法干预

    public class TestThread {
        public static void main(String[] args) {
            //通过继承Thread类创建线程,直接new一个子类实例,调用start()方法启动线程
            MyThread01 myThread01=new MyThread01();
            myThread01.start();
    
            //通过实现Runnable接口创建线程,需先new一个子类实例,先后再new一个Thread类,
            //将子类实例作为参数传入后调用start()方法启动线程
            MyThread02 myThread02=new MyThread02();
            new Thread(myThread02).start();
        }
    }
    

    注意:直接调用run()方法,将以普通方法执行,依然是单线程顺序调用。

    方式三、实现Callable接口

    class Thread3 implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("线程开始执行...");
            int i = 10 / 2;
            System.out.println("执行结果i=:" + i);
            System.out.println("线程执行结束...");
            return i;
        }
    }
    

    调用Callbale接口的线程

    public static void main(String[] args) throws ExecutionException, InterruptedException {
    	FutureTask<Integer> futureTask = new FutureTask<Integer>(new Thread3());
        new Thread(futureTask).start();
        Integer i = futureTask.get();// 获取返回值
        System.out.println(i);
    }
    

    方式四:使用线程池

    public static void main(String[] args) {
            // 创建线程池
    	ExecutorService executorService = Executors.newFixedThreadPool(5);
    	executorService.execute(new Thread1());
    }
    

    四种方式的比较:
           1只能单继承,2、3可以多继承,扩展性更好
           1、2实现的方法是run(),3实现的方法是call()
           1、2不能得到返回值,不能抛异常,3可以获取返回值,可以抛异常
           1、2、3都不能控制资源,4可以直接控制资源,性能稳定

    四、线程创建的简化方式

    1、常规使用

    package com.whw.thread.generic;
    
    public class NormalThread implements Runnable {
        
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println("我是MyThread01---" + i);
            }
        }
    
        public static void main(String[] args) {
            new Thread(new NormalThread()).start();
        }
    }
    

    2、使用静态内部类

    package com.whw.thread.generic;
    
    public class TestThread {
        // 定义静态内部类
        static class Test implements Runnable {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println("我是MyThread01---" + i);
                }
            }
        }
    
        public static void main(String[] args) {
            // 使用静态内部类
            new Thread(new Test()).start();
        }
    }
    

    3、使用局部内部类

    package com.whw.thread.generic;
    
    public class TestThread {
    
        public static void main(String[] args) {
            // 定义局部内部类
            class Test implements Runnable {
                @Override
                public void run() {
                    for (int i = 0; i < 20; i++) {
                        System.out.println("我是MyThread01---" + i);
                    }
                }
            }
    
            // 使用局部内部类
            new Thread(new Test()).start();
        }
    }
    

    4、使用匿名内部类

    package com.whw.thread.generic;
    
    public class TestThread {
    
        public static void main(String[] args) {
            // 使用匿名内部类:必须借助接口或者父类
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 20; i++) {
                        System.out.println("我是MyThread01---" + i);
                    }
                }
            }).start();
        }
    }
    

    5、jdk8,使用Lambda表达式

    package com.whw.thread.generic;
    
    public class TestThread {
    
        public static void main(String[] args) {
            // 使用Lambda表达式
            new Thread(() -> {
                for (int i = 0; i < 20; i++) {
                    System.out.println("我是MyThread01---" + i);
                }
            }).start();
            
            new Thread(() -> {
                for (int j = 0; j < 20; j++) {
                    System.out.println("我是MyThread02---" + j);
                }
            }).start();
        }
    }
    

    lambda表达式基础使用:

    package com.whw.thread.generic;
    
    class Love {
        public static void main(String[] args) {
            //此段代码相当于创建了一个ILove的实现类,{}内的为实现类重写方法的方法体
            /*ILove love = (String name) -> {
                System.out.println("I Love -->" + name);
            };*/
    
            //一个参数时可以不用写参数类型
            /*ILove love = (name) -> {
                System.out.println("I Love -->" + name);
            };*/
    
            //一个参数时可以不用写括号
            /*ILove love = name -> {
                System.out.println("I Love -->" + name);
            };*/
    
            //一个参数时,方法体只有一行内容时,可以不用写{},但要写在一行
            //ILove love = name -> System.out.println("I Love -->" + name);
    
            //love.lambda("王伟");
    
            //接口两个参数,并且带返回值
            IInterest interest = (a, b) -> {
                return a + b;
            };
            Integer result = interest.lambda(1, 2);
            System.out.println(result);
        }
    }
    
    interface ILove {
        void lambda(String name);
    }
    
    interface IInterest {
        Integer lambda(Integer a, Integer b);
    }
    

    五、线程的常用方法

    1、currentThread()

           获取当前正在被调用的线程

    2、getId()

           获取线程的唯一id号

    3、getName()

           获取线程的名字

    4、getState()

           获取线程的状态

    5、isDaemon()

           判断是否是守护线程

    6、setDaemon(boolean on)

           设为守护线程,只能在线程启动之前把它设为后台线程

    7、isAlive()

           判断当前线程是否处于活动状态

    8、activeCount()

           判断当前线程组中存活的线程数

    9、getThreadGroup()

           获取当前线程所在的线程组

    10、sleep(long)

           让线程休眠指定的毫秒数,休眠过程中资源不释放

    11、停止线程

           stop():已被废弃
           interrupt():
                  并不会终止正在运行的线程,需要加入一个判断才可以停止线程
                  判断线程是否被中断this.isInterrupted()
           run():运行结束

    异常法停止线程:

    public class StopThread {
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            myThread.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 停止线程
            myThread.interrupt();
        }
    }
    
    class MyThread extends Thread {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 1000000; i++) {
                    System.out.println(i);
                    if (this.isInterrupted()) {
                        System.out.println("我被终止了");
                        // 抛出异常,中断for循环下面代码的执行
                        throw new InterruptedException();
                    }
                }
                System.out.println("我被输出,说明线程并未停止");
            } catch (InterruptedException e) {
                System.out.println("异常法结束线程的执行");
                e.printStackTrace();
            }
        }
    }
    

    标记法停止线程:

    package com.whw.thread.state;
    
    /**
     * @description 终止线程
     * 1、线程正常执行完毕-->次数
     * 2、外部干涉-->加入标识
     * 不要使用stop,destroy方法
     */
    public class TerminateThread implements Runnable {
    
        //1、加入标识,标记线程体是否可以运行
        private boolean flag = true;
        private String name;
    
        public TerminateThread(String name) {
            this.name = name;
        }
    
        @Override
        public void run() {
            int i = 0;
            //2、关联标识,true-->运行 false-->停止
            while (flag) {
                System.out.println(name + "-->" + i++);
            }
        }
    
        //3、对外提供方法改变标识
        public void terminate() {
            this.flag = false;
        }
    
        public static void main(String[] args) throws InterruptedException {
            TerminateThread tt = new TerminateThread("A");
            new Thread(tt).start();
    
            for (int i = 0; i <= 99; i++) {
                if (i == 88) {
                    tt.terminate();//线程终止,解决死锁
                    System.out.println("tt game over");
                }
                System.out.println("main-->" + i);
            }
        }
    }
    

    12、暂停与恢复线程

           暂停suspend():已被弃用,使用不当容易造成独占锁和数据不同步
           恢复resume():已被弃用,使用不当容易造成独占锁和数据不同步

    13、yield()

           线程礼让,不一定每次都成功,只是让当前线程放弃本次CPU资源的争夺,有可能刚放弃马上又得到了cpu资源

    14、setPriority()

           设定线程的优先级,默认是5,1~10个等级,有助于线程规划器尽可能多的将资源分给优先级高的线程
           具有继承性
           高优先级的不一定每次都先执行完

    15、join(long millis)

           join合并线程,插队线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。他是一个成员方法

  • 相关阅读:
    第二高的薪水
    leecode 删除排序数组中的重复项
    leecode 17. 电话号码的字母组合
    dubbo 限流之TpsLimitFilter
    G1总结
    leecode 3. 无重复字符的最长子串
    mysql是如何解决脏读、不可重复读、幻读?
    归并排序
    PostgreSQL管理数据库安全
    Oracle Database 19c 技术架构(三)
  • 原文地址:https://www.cnblogs.com/giswhw/p/15533814.html
Copyright © 2020-2023  润新知