zoukankan      html  css  js  c++  java
  • 多线程详解

    Java.Thread

    01线程简介

    一个进程-->多个线程

    进程:执行程序的一次执行过程

    线程:就是独立的执行路径

    现在的多线程 多是 模拟出来的

    02线程实现(重点)

    三种创建方式:

    • 继承Thread(重点)

      自定义线程类继承Thread类
      重写run()方法,编写线程执行体
      创建线程对象,调用start()方法启动线程
      
    • 实现Runnable接口(重点)

      定义MyRunnable类实现Runnable接口
      实现run()方法,编写线程执行体
      创建线程对象,调用start()方法启动线程
      
    • 实现Callable接口(了解 )

      实现Callable接口,需要返回值类型
      重写call方法,需要抛出异常
      创建目标对象
      创建执行服务:ExecutorService ser + Executors.newFixedThreadPool(1);
      提交Future<Boolean> result1 = ser.submit(t1);
      获取结果: boolean r1 = result1.get()
      关闭服务:ser.shutdownNow();
      

    Lambda表达式

    为什么要使用Lambda表达式 :

    • 避免匿名内部类定义过多

    • 可以让你的代码看起来很简洁

    • 去掉了一堆没有意义的代码,只留下核心的逻辑.

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

      对于函数式接口,我们可以通过lambda表达式来创建该接口的对象.


    推导lambda表达式:

    /*
    推导lambda表达式 最初代码
     */
    public class TestLambda1 {
        public static void main(String[] args) {
            ILike like = new Like();
            like.lambda();
        }
    }
    
    //1.定义一个函数式接口
    interface ILike{
        void lambda();
    }
    
    //2.实现类
    class Like implements ILike{
        public void lambda() {
            System.out.println("i like lambda");
        }
    }
    
    /*
    推导lambda表达式 2.3.4.5.6是各种实现接口的方法,逐层推进
    idea若是报错:将ProjectSettings 中Language level改成 8
    参考:https://blog.csdn.net/fenghuibian/article/details/52704057
     */
    public class TestLambda1 {
    
        //3.静态内部类
        static class Like2 implements ILike{
            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{
                public void lambda() {
                    System.out.println("i like lambda3");
                }
            }
            like = new Like3();
            like.lambda();
    
            //5.匿名内部类,没有类的名称,必须借助接口或父类
            like = new ILike() {
                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{
        public void lambda() {
            System.out.println("i like lambda");
        }
    }
    

    总结:

    • lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹.
    • 前提是接口为函数式接口
    • 如果去掉参数类型, 就必须所有参数都去掉(多个参数必须加括号)

    线程常用方法

    1. 线程停止,引入标记位,让线程自己运行我完停止,不建议强行停止
    2. 线程休眠:sleep()
    3. 线程礼让:yield()
    4. 线程强行执行:join()

    03线程状态

    创建-->就绪状态-->(阻塞状态)-->运行状态-->dead

    setPriority(int newPriority) : 更改线程的优先级
    static void sleep(long millis) : 在指定的毫秒内让当前正在执行的线程休眠
    void join() : 等待该线程终止
    static void yield() : 暂停当前正在执行的线程对象,并执行其他线程
    void interrupt() : 中断线程,别用这个方式
    boolean isAlive() : 测试线程是否处于活动状态
    

    死亡后的线程不能再次启动

    测试线程优先级:setPriority(int); int型:1~10

    守护(daemon)线程:thread.setDaemon(true)

    04线程同步(重点)

    • 由于同一进程的多个线程共享一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:

      • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
      • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题;
      • 如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题;
    • 死锁

      线程A拥有线程B的资源,现在需要线程B的资源,但线程B此时也再等A的资源,两个线程都无法向下运行,造成死锁。

    • 产生死锁的四个必要条件

      1. 互斥条件:一个资源每次只能被一个进程使用
      2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不发。
      3. 不剥夺条件:进程已获得的资源,在使用完之前,不能强行剥夺。
      4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
    • 通过Lock(锁)类实现锁

    • synchronized与Lock的对比

      • Lock是显示锁(手动开启,手动关闭),synchronized是隐式锁,出了作用域自动释放

      • Lock只有代码块锁,synchronized有代码块锁和方法锁

      • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

      • 优先使用顺序:

        • Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)

    05线程通信问题

    Producer(生产者)--> 数据缓冲区 --> Consumer(消费者)

    package gaoji;
    
    //测试:生产者消费者模型-->利用缓冲区解决:管程法
    //生产者,消费者,产品,缓冲区
    public class TestPC {
        public static void main(String[] args) {
            SynContainer container = new SynContainer();
    
            new Productor(container).start();
            new Consumer(container).start();
        }
    }
    
    //生产者
    class Productor extends Thread{
        SynContainer container;
    
        public Productor(SynContainer container){
            this.container = container;
        }
        
        //成产
        @Override
        public void run(){
            for (int i = 0; i < 100; i++) {
                container.push(new Chicken(i));
                System.out.println("生产了"+i+"只鸡");
            }
        }
    }
    
    //消费者
    class Consumer extends Thread{
        SynContainer container;
    
        public Consumer(SynContainer container){
            this.container = container;
        }
    
        //消费
        @Override
        public void run(){
            for (int i = 0; i < 100; i++) {
                System.out.println("消费了--》"+container.pop().id+"只鸡");
            }
        }
    }
    
    //产品
    class Chicken{
        int id;//产品编号
    
        public Chicken(int id) {
            this.id = id;
        }
    }
    
    //缓冲区
    class SynContainer{
    
        //需要一个容器大小
        Chicken[] chickens = new Chicken[10];
        //容器计数器
        int count = 0;
    
        //生产者放入产品
        public synchronized void push(Chicken chicken){
            //如果容器满了,就需要等待消费者
            if(count == chickens.length){
                //通知消费者消费,生产等待
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            //如果没有满,我们就需要丢入产品
            chickens[count] = chicken;
            count++;
    
            //可以通知消费者了
            this.notifyAll();
        }
    
        //消费者消费产品
        public synchronized Chicken pop(){
            //判断是否能消费
            if(count==0){
                //等待生产者生产,消费者等待
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            //如果可以消费
            count--;
            Chicken chicken = chickens[count];
    
            //吃完了,通知生产者生产
            this.notifyAll();
    
            return chicken;
        }
    
    }
    

    线程池

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

    • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁的创建和销毁,实现重复利用。类似生活中的公共交通工具。

    • 好处

      • 提高响应速度(减少了创建新线程的时间)

      • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)

      • 便于线程管理(....)

        • corePoolSize:核心池的大小
        • maximumPoolSize:最大线程数
        • keepAliveTime:线程没有任务时最多保持多长时间后会终止

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

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

  • 相关阅读:
    UVA.12096 The SetStack Computer ( 好题 栈 STL混合应用)
    HDOJ(HDU).1412 {A} + {B} (STL SET)
    UVA.10474 Where is the Marble ( 排序 二分查找 )
    HDOJ(HDU).1754 I Hate It (ST 单点替换 区间最大值)
    HDOJ(HDU).1166 敌兵布阵 (ST 单点更新 区间求和)
    17 西安
    17 沈阳
    13 南京
    10/11 作战会议
    2019牛客国庆集训派对day5
  • 原文地址:https://www.cnblogs.com/fzly-88/p/12376007.html
Copyright © 2011-2022 走看看