zoukankan      html  css  js  c++  java
  • 多线程学习

    一、简介

    程序:指令和数据的集合

    进程:程序的一次执行过程,是系统资源分配的基本单位

    线程:是cpu调度和执行的单位

    二、线程实现

    1. 继承Thread
    • ThreadImpl extends Thread
    • 重写run()
    • new Thread().start()
    1. 实现Runnable接口
    • ThreadImpl implements Runnable
    • 重写run()
    • new Thread(new ThreadImpl( )).start()
    1. 实现Callable接口
    • ThreadImpl implements Callable< call()的返回值 >
    • 重写call()
    • TestCallable t1 = new TestCallable()
    • 创建执行服务 ExcutorService ser = Excutors.newFixedThreadPool(3);创建池子数量
    • 提交执行 Future< bollean > result = ser.submit( t1 );
    • 获取结果:result.get()
    • 关闭服务:ser.shutdownNow();

    推荐使用实现Runnable接口,避免了单继承的局限性,灵活方便,方便同一个对象被多个线程使用

    三、Lambda表达式

    • λ是希腊字母中,排第十一的字母==>英语名称为Lamda
    • 优点:
      • 避免匿名内部类过多,代码看起更加简洁;
      • 去掉了一堆没有意义的代码,只留下核心的逻辑
    • 实质属于函数式编程
    (param)->expression [ 表达式 ]
    
    	(param)->statement [ 语句 ]
    
    		(param)->{statements}
    		
    			lambda演化:外部类 --> 静态内部类 --> 局部类 --> 匿名内部类(借助接口或父类实现) --> lambda表达式【 (参数)-> { 方法体 } 】
    

    函数式接口

    定义:只包含一个抽象方法。

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

    四、线程状态

    1. 新建:线程被创建,处于新建状态

    2. 就绪:线程对象调用start()方法开启线程,等待被cpu调度执行

    3. 运行:就绪状态的线程获得了cpu时间片,执行run()方法

    4. 阻塞: 线程放弃了对cpu的使用权,暂时停止运行

      ​ 等待阻塞:线程调用wait()方法,进入等待阻塞

      ​ 同步阻塞:线程获取synchronized同步锁失败,进入同步阻塞

      ​ 其他阻塞:线程调用sleep()方法,进入阻塞状态

      wait() sleep()
      会释放锁,属于Object类 不会释放锁,属于Thread类
      只能在同步方法或者代码块里使用,不需要抛异常 可以在任何地方使用,需要抛出异常
      需要notify()或notifyAll()唤醒 不需要唤醒,休眠之后自动退出阻塞
    5. 死亡:线程执行完了run()方法,或者因为异常而退出了run()方法,线程的生命周期结束

    方法 说明
    setPriority( int ) 更改线程的优先级
    sleep( long ) 指定的毫秒内休眠
    join() 等待线程终止
    yield() 让出cpu,再和其他线程一起争抢cpu
    interrupt() 中断线程,别用
    isAlive() 测试线程是否处于活动状态

    守护线程(setDaemon(true))

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

    死锁

    ​ 两个或多个线程,同时阻塞,都在等待某个资源的释放,而这个资源又被其他的线程占用,所以线程就一致阻塞,导致线程死锁。比如:现在有A、B两个线程,同时申请对方的资源,就会相互等待,线程一直处于等待状态,导致死锁。

    ​ 一个同步代码块同时拥有 " 两个以上对象的锁 ",就可能发生 " 死锁 "。

    线程死锁的四个条件

    1. 互斥:一个资源任意时刻,只能被一个线程占用
    2. 不剥夺:线程资源在使用完之前,不能被其他线程剥夺
    3. 请求和保持:线程保持了多个资源,又发出了新的资源请求,该资源被其他线程占用
    4. 循环等待:在资源等待链中,每一个线程获得的资源同时,被下一个线程请求

    破坏死锁:破坏四个条件中的一种, 互斥条件没办法破坏。

    1. 破坏不剥夺:申请不到资源时,可以主动释放当前占有的资源
    2. 破坏请求和保持:一次性申请所有资源
    3. 破坏循环等待:按照顺序请求和释放资源

    五、线程同步

    一、synchronized

    ​ 为了保证数据在方法中被正确访问的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源其他线程必须等待,存在以下问题:

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

    CopyOnWriteArrayList是线程安全的集合

    同步方法以及同步块

    ​ private关键字保证数据只能被方法访问,所以只需要对方法提出一套机制--->synchronized关键字,它包括两种用法,同步方法和同步代码块

    • 同步方法:public synchronized void method(){},锁的是this,缺陷—>影响效率
    • 同步代码块:synchronized (Obj){},需要锁的是增删改的对象

    二、Lock

    ​ JDk5.0开始,提供了更强大的线程同步机制——通过显示显示定义同步锁。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,访问资源之前应先获得Lock对象。

    ​ ReentrantLock(可重入锁)类实现了Lock,拥有和synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁和释放锁。

    三、Lock和Synchronized的对比

    Lock Synchronized
    显示锁,需要手动开启和关闭锁 隐式锁,出了作用域会自动释放锁
    只能锁代码块 锁代码块和方法
    JVM花费更少的时间去调度线程,性能好

    六、线程间通信

    java提供了几个方法解决了线程间的通信问题

    方法名 描述
    wait() 线程一直等待,直到其他线程通知,会释放锁
    wait(long timeout) 指定等待的毫秒数
    notify() 随机唤醒一个处于等待状态的线程
    notifyAll() 唤醒同一个对象上所有调用wait()的线程,优先级别高的优先调度

    生产者消费者问题

    这是一个线程同步问题,生/消共享同一个资源,仅有一个synchronized是不够的

    • synchronized可以阻止并发更新同一个共享资源,实现了同步
    • 但是,不能实现线程间的通信

    解决方式一:管程法

    • 生产者:负责生产数据的模块(模块可以是方法、对象、线程、进程)
    • 消费者:负责处理数据的模块(模块可以是方法、对象、线程、进程)
    • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个 " 缓冲区 " ; 生产者将生产好的数据放入缓冲区,消费者从缓冲区取出数据

    package thread;
    
    
    /**
     *
     * @author 柯神_
     * @date 2020-11-29 17:17:17
     * @Description 线程间通信,
     *                  生产者消费者问题
     *                         解决方式:一、管程法
     *                                  二、信号灯法
    */
    public class ThreadCommunication {
        public static void main(String[] args) {
            Buffer buffer = new Buffer();
            Producer producer = new Producer(buffer);
            Consumer consumer = new Consumer(buffer);
    
            new Thread(producer).start();
            new Thread(consumer).start();
    
        }
    }
    
    /**
     * 一、管程法
     */
    //生产者
    class Producer implements Runnable{
        Buffer buffer;
    
        public Producer(Buffer buffer) {
            this.buffer = buffer;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                buffer.push(new Product(i));
                System.out.println("生产了" + i + "只鸡!");
            }
        }
    }
    
    //消费者
    class Consumer implements Runnable{
        Buffer buffer;
    
        public Consumer(Buffer buffer) {
            this.buffer = buffer;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("消费了==>" + buffer.getP().id + "只鸡!");
            }
        }
    }
    
    //产品
    class Product{
        int id;
    
        public Product(int id) {
            this.id = id;
        }
    }
    
    //缓冲区
    class Buffer{
    
        //定义一个容器大小
        Product[] products = new Product[10];
    
        //容器计数器
        int count = 0;
    
        //1.生产者生产产品
        public synchronized void push(Product product){
            /**
             * 缓冲区满了,生产等待,通知消费
             */
            if (count == products.length - 1){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            /**
             * 缓冲区未满,可以生产
             */
            products[count] = product;
            count++;
    
            /**
             * 有产品了
             * 通知消费者消费
             */
            this.notifyAll();
        }
    
        //2.消费者消费产品
        public synchronized Product getP(){
            /**
             * 缓冲区空了,等待消费,通知生产
             */
            if (count == 0){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            /**
             * 缓冲区有产品,可以消费
             */
            count--;
            Product product = products[count];
    
            /**
             * 吃了,缓冲区没满了
             * 通知生产者生产
             */
            this.notifyAll();
            return product;
        }
    }
    

    解决方式二:信号灯法

    信号灯法是通过标志位来实现

    /**
     * 二、信号灯法:通过标志位来实现
     */
    package thread;
    
    
    import lombok.SneakyThrows;
    
    /**
     * 二、信号灯法:通过标志位来实现
     */
    public class ThreadCommunication2 {
        public static void main(String[] args) {
    
            TV tv = new TV();
            Player player = new Player(tv);
            Watcher watcher = new Watcher(tv);
    
            new Thread(player).start();
            new Thread(watcher).start();
    
        }
    }
    
    //生产者 ==>演员录制
    class Player implements Runnable{
        TV tv;
    
        public Player(TV tv) {
            this.tv = tv;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                if (i % 2 == 0) {
                    this.tv.play("录制快本中。。。");
                } else {
                    this.tv.play("新闻联播录制...");
                }
            }
        }
    }
    
    //消费者 ==>观众
    class Watcher implements Runnable{
        TV tv;
    
        public Watcher(TV tv) {
            this.tv = tv;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                tv.watch();
            }
        }
    }
    
    //产品 ==>小视频节目
    class TV{
        private String name;
        private boolean flag = true;   //标志位
    
        /**
         *  表演录制:观众等待
         */
        @SneakyThrows
        public synchronized void play(String name){
            if (!flag){
                wait();
            }
    
            System.out.println("演员表演了" + name);
            this.notify();
            this.name = name;
            this.flag = !this.flag;
        }
    
        /**
         * 观众观看:演员等待
         */
        @SneakyThrows
        public synchronized void watch(){
            if (flag){
                this.wait();
            }
    
            System.out.println("观众观看=======>" + name);
            this.notifyAll();
            this.flag = !this.flag;
        }
    }
    
    

    七、线程池

    背景:线程频繁地创建和销毁,对系统的性能影响很大

    线程池:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,实现资源重复利用。

    优点:

    • 提高了响应速度(减少了创建线程的时间)
    • 降低了资源的消耗(线程使用完放回池中,实现资源重复利用)
    • 便于线程管理
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时,多长时间后终止

    JDK5.0开始,提供了线程池相关的API:ExcutorService 和 Excutors

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

    • shutdown()关闭连接池

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

    package thread;
    
    import java.util.concurrent.Executor;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     *
     * @author 柯神_
     * @date 2020-11-29 20:21:10
     * @Description
     *                  测试线程池
    */
    public class PoolTest {
        public static void main(String[] args) {
            //创建线程服务,线程池,参数为线程池的大小
            ExecutorService service = Executors.newFixedThreadPool(10);
            service.execute(new MyPool());
            service.execute(new MyPool());
            service.execute(new MyPool());
            service.execute(new MyPool());
    
            service.shutdown();
        }
    }
    
    class MyPool implements Runnable {
    
        @Override
        public void run() {
                System.out.println(Thread.currentThread().getName());
        }
    }
    
    
  • 相关阅读:
    c++0.9-----c++ primer之noexcept解读
    c++0.8-----快速定位c++源码位置的小技巧
    c++0.7-----源码分析:iostate及badbit/failbit/eofbit/goodbit以及io文件的包含关系<原创>
    c++0.6-----如何在自己搭建的c++环境中使用extern变量
    c++0.5-----如何在widows下面搭建最简洁的c++环境
    c++0.4-----面向对象的三种关系(继承/复合/委托)
    c++0.3----this指针/static/namespace
    c++0.2-----基于对象的类(包含指针)
    3、静态代理模式
    2、工厂方法模式
  • 原文地址:https://www.cnblogs.com/qqkkOvO/p/14057806.html
Copyright © 2011-2022 走看看