• 多线程核心基础


    1 进程和线程

    ​ 进程是OS分配资源的最进本的单位,线程是执行调度的最基本单位。分配资源最重要的是:独立的内存空间,线程调度执行(线程共享进程的内存空间,没有自己的独立空间)。
    JVM线程与操作系统的线程是一一对应的,在JVM中启动一个线程,会交给操作系统启动一个线程。
    纤程:用户太的线程,线程中的线程,切换和调度不需要经过OS。优势:占有资源很少,可以启动很多个(10W+)。目前支持向纤程的语言:GO、Python(lib)。Java目前不支持纤程。

    2 实现多线程

    //方法一:继承Thread
    static class ThreadExtends extends Thread {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ":T1");
            }
        }
    
    //方法二:实现Runnable接口
    static class ThreadImplRunnable implements Runnable {
    	@Override
    	public void run() {
    		System.out.println(Thread.currentThread().getName() + ":T2");
    	}
    }
    
    // 方式3 实现Callble接口(线程执行带返回值的)
        class MyRun implements Callable<String>{
            @Override
            public String call() throws Exception {
                return "success";
            }
        }
        @Test
        public void testCall(){
            MyRun t1 = new MyRun();
            try {
                String call = t1.call();
                System.out.println(call);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    //方式4 使用线程池创建(带返回值)
    	public void threadPool(){
            ExecutorService pool = Executors.newCachedThreadPool();
            pool.execute(()->{
                System.out.println("hello ThreadPool");
            });
            Future<String> future = pool.submit(() -> {
                return "success";
            });
            try {
                //阻塞
                String msg = future.get();
                System.out.println(msg);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    //方式5 使用FutureTask创建(带返回值)
        @Test
        public void testFutureTask(){
            FutureTask<String> task = new FutureTask<>(()->{
                return  "success";
            });
            new Thread(task).start();
            try {
                //阻塞
                String msg = task.get();
                System.out.println(msg);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    

    3 启动线程

    ​ 线程启动使用start()方法。为什么不能用run()方法?start()会启动新线程执行(不能重复start),run()直接当前线程启动,不会使用新线程执行。

    	//错误演示:使用run启动线程
    	public static void main(String[] args) {
            ThreadExtends thread1 = new ThreadExtends();
            new Thread(thread1).run();
            System.out.println(Thread.currentThread().getName() + ":Main");
        }
    //输出:
    //	main:T1
    //	main:Main
    //结论:使用run()方法启动,执行线程为main线程,并非是thread1线程,并没有使用多线程执行
    
    	//正确使用start()启动线程
    	public static void main(String[] args) {
            ThreadExtends thread1 = new ThreadExtends();
            new Thread(thread1).start();
            System.out.println(Thread.currentThread().getName() + ":Main");
        }
    //输出:
    //	main:Main
    //  Thread-1:T1
    //结论:使用start方法启动线程,会开启多个线程执行,从而达到多线程执行的目的。
    
    //jdk8使用Lambda表达式启动线程,实际也是通过实现Runnable接口
    new Thread(()->{
    	System.out.println(Thread.currentThread().getName() + ":T3");
    }).start();
    

    4 线程常用方法

    4.1 Thread类中线程常用重要方法

    • sheep():sleep方法可以让线程进入waiting状态,并且不占用CPU资源,但是不释放锁,直到规定时间后再执行,休眠期间如果被中断,会爆出异常并清除中断状态。
    • yield():短暂让出CPU进入排队队列。
    • join(): 因为其他线程加入了我们,所以我们要等他执行完了再出发。例:在A线程中执行B.join()即 让B线程执行完后再执行A线程。原理:自己进入waiting状态,加入的线程执行后自动notifyAll()。
    	@Test
        public void testJoin() throws InterruptedException {
            Thread t1 = new Thread(() -> {
                System.out.println("T1执行");
                System.out.println("T1结束");
            });
            Thread t2 = new Thread(() -> {
                try {
                    System.out.println("T2执行");
                    t1.join();
                    System.out.println("T2结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            Thread t3 = new Thread(() -> {
                try {
                    System.out.println("T3执行");
                    t2.join();
                    System.out.println("T3结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
    
            t3.start();
            t1.start();
            t2.start();
            /**
             * 执行结果:
             * T3执行
             * T2执行
             * T1执行
             * T1结束
             * T2结束
             * T3结束
             * 结论:无论谁先抢到执行,都需要等T1执行结束后T2才能执行结束,最后T3执行结束。
             */
        }
    

    4.2 Object类中线程常用重要方法

    wait(),notify(), notifyAll()必须拥有monitor,属于Object类,和Condition类似。
    notify唤醒

    • notify方法只应该被拥有该对象的monitor的线程调用;
    • 一旦线程被唤醒,线程便会从对象的“等待线程集合”中被移除,所以可以重新参与到线程调度当中
    • 要等刚才执行notify的线程退出被synchronized保护的代码并释放monitor
      wait等待以下情况之一才会被唤醒
    • 另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程;
    • 另一个线程调用这个对象的notifyAll()方法;
    • 过了wait(long timeout)规定的超时时间,如果传入0就是永久等待;
    • 线程自身调用了interrupt()

    4.3 Condition中常用方法

    Condition是个接口,基本的方法就是await()和signal()方法;Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()。调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用。Condition中的**await()对应Object的wait();Condition中的signal()对应Object的notify();Condition中的signalAll()**对应Object的notifyAll()。

    /**
     * 实现一个固定容量同步容器,拥有put和get方法,以及getCount方法.
     * 能够支持2个生产者线程以及10个消费者线程的阻塞调用
     */
    public class WaitAndNotilfy {
        List list = new ArrayList();
        //容器最大容量
        private final static int MAX = 10;
        private static int COUNT = 0;
        Lock lock = new ReentrantLock();
        //生产者
        Condition producer = lock.newCondition();
        //消费者
        Condition consumer = lock.newCondition();
    
        public Object get() {
            Object o = null;
            try {
                lock.lock();
                while (list.size() == 0) {
                    System.err.println("消费者暂停");
                    consumer.await();
                }
                o = list.get(0);
                list.remove(0);
                COUNT--;
                //通知生产者生产
                producer.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            return o;
        }
    
        public void put(Object o) {
            try {
                lock.lock();
                while (list.size() == MAX) {
                    System.err.println("生产者暂停");
                    producer.await();
                }
                list.add(o);
                ++COUNT;
                //通知生产者生成
                consumer.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public int getCount() {
            return COUNT;
        }
    
        public static void main(String[] args) {
            WaitAndNotilfy c = new WaitAndNotilfy();
            for (int i = 0; i < 20; i++) {
                new Thread(() -> {
                    for (int j = 1; j <= 5; j++) {
                        c.put(j);
                        System.out.println("生产者生产了:" + j + ",目前容量:" + c.getCount());
                    }
                }).start();
            }
    
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 1; j <= 5; j++) {
                        Object o = c.get();
                        System.out.println("消费者消费了:" + o + ",目前容量:" + c.getCount());
                    }
                }).start();
            }
        }
    }
    

    5 线程6个状态

    • new:刚创建线程还没有启动;
    • Runnable:可运行;
    • Blocked:被阻塞;
    • Waiting:等待;
    • Timed waiting:限期等待;
    • Terminated:终止
      一般习惯而言,把Blocked(被阻塞)、Waiting(等待)、Timed_waiting(计时等待)都称为阻塞状态。

    6 停止线程

    通常情况下,我们不会手动停止一个线程,而是允许线程运行到结束,然后让它自然停止。但是依然会有许多特殊的情况需要我们提前停止线程,比如:用户突然关闭程序,或程序运行出错重启等。在这种情况下,即将停止的线程在很多业务场景下仍然很有价值。尤其是我们想写一个健壮性很好,能够安全应对各种场景的程序时,正确停止线程就显得格外重要。但是Java 并没有提供简单易用,能够直接安全停止线程的能力。

    6.1 interrupt介绍

    • interrupt() 设置线程打断标志位
    • isInterrupted() 查询某线程是否被打断(查询表示位)
    • static interrupted() 查询当前线程是否被打断过,并重置打断标志
        @Test
        public void testInterrupted() throws InterruptedException {
            Thread t = new Thread(()->{
                while (true){
                    if(Thread.currentThread().isInterrupted()){
                        System.out.println("Thread is interrupted!");
                        System.out.println(Thread.currentThread().isInterrupted());
                        break;
                    }
                }
            });
            t.start();
            Thread.sleep(1000);
            t.interrupt();
        }
    	/**
         * 输出:
         * 	Thread is interrupted!
         * 	true
    	 * 结论:isInterrupted()查询不重置标志位
         */
    
    	@Test
        public void testInterrupted2() throws InterruptedException {
            Thread t = new Thread(()->{
                while (true){
                    if(Thread.interrupted()){
                        System.out.println("Thread is interrupted!");
                        System.out.println(Thread.interrupted());
                        break;
                    }
                }
            });
            t.start();
            Thread.sleep(1000);
            t.interrupt();
            //当前线程
            System.out.println("main:"+t.isInterrupted());
        }
        /**
         * 输出:
         *  main:false
         *  Thread is interrupted!
         *  false
         * 结论:Thread.interrupted() 会重置标志位
         */
    

    注意:interrupted不能中断正在竞争锁的线程。如果允许打断,在线程加锁时Lock的lockInterruptibly()加锁,当interrupted对该线程设置标志位的时候,该线程会保InterruptedException异常。

    6.2 使用interrupt停止线程

    对于 Java 而言,最正确的停止线程的方式是使用 interrupt。但 interrupt 仅仅起到通知被停止线程的作用。而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。

    ** 使用interrupt中断线程**
    	@Test
        public void testInterruptNormal() throws InterruptedException {
            Thread t = new Thread(()->{
                while (!Thread.interrupted()){
    
                }
                System.out.println("t end");
            });
            t.start();
            Thread.sleep(500);
            t.interrupt();
        }
    

    一旦调用某个线程的 interrupt() 之后,这个线程的中断标记位就会被设置成 true。每个线程都有这样的标记位,当线程执行时,应该定期检查这个标记位,如果标记位被设置成true,就说明有程序想终止该线程。

    public class StopThread implements Runnable {
        @Override
        public void run() {
            int count = 0;
            while (!Thread.currentThread().isInterrupted() && count < 1000) {
                System.out.println(count++);
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new StopThread());
            thread.start();
            Thread.sleep(5);
            thread.interrupt();
        }
    }
    // 运行结果,输出没有到1000会被main线程中断
    当线程处于sheep或await状态时,被其他线程interrupt,会报java.lang.InterruptedException
    public class StopThread implements Runnable {
    
        @Override
        public void run() {
            int count = 0;
            try {
                while (!Thread.currentThread().isInterrupted() && count < 1000) {
                    if(count==10){
                        Thread.sleep(10);
                    }
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new StopThread());
            thread.start();
            Thread.sleep(5);
            thread.interrupt();
        }
    }
    //运行结果:输出10个数后报异常
    

    7获取子线程执行结果

    查看Runnable接口可以发现,run()方法的返回是void,且未声明为抛出任何已检查的异常,而咱们实现并重写这个方法,自然也不能返回值,也不能抛出异常,因为在对应的Interface / Superclass中没有声明它。
    Runnable为什么设计成这样?
    如果run()方法可以返回值,或者可以抛出异常,也无济于事,因为我们并没办法在外层捕获并处理,这是因为调用run()方法的类(比如Thread类和线程池)也不是我们编写的。所以如果我们真的想达到这个目的,可以看看下面的补救措施:

    1. 使用Callable,声明了抛出异常并且有返回值;
    @FunctionalInterface
    public interface Callable<V> {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }
    
    1. 用Future来获得子线程的运行结果;
  • 相关阅读:
    redis分布式锁解决超卖问题
    redis使用
    Xcode 解决日志打印不全问题
    苹果电脑系统怎么重装?这几步就可以轻松搞定
    Mac 一键显示所有隐藏文件 不要那么六好吧
    iOS导入高德地图出现缺失armv7--"Undefined symbols for architecture armv7"
    如何生成.a文件,小心有坑!!
    保护你的代码,生成.a文件以及.framework文件需要注意的地方
    二维码扫描工具实现
    iOS 调整图片尺寸,告诉你的UI,别问我尺寸!我要最大的
  • 原文地址:https://www.cnblogs.com/dooor/p/thread.html
Copyright © 2020-2023  润新知