zoukankan      html  css  js  c++  java
  • 多线程笔记

    1、什么是进程

    应用程序

    进程:正在运行的程序。

    2、什么是线程

    • 线程:又称 轻量级进程(Light Weight Process)。
    • 进程中的一条执行路径,也是CPU的基本调度单位。
    • 一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程。

    3、进程和线程的区别

    • 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。
    • 一个程序运行后至少有一个进程。
    • 一个进程可以包含多个线程,但是至少需要有一个线程否则这个进程是没有意义。
    • 京城键不能共享数据段地址,但同进程的线程之间可以。

    4、线程的组成

    任何一个线程都具有基本的组成部分:

    • CPU时间片:操作系统(OS)会为每个线程分配执行时间。
    • 运行数据:
      • 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
      • 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。
    • 线程的逻辑代码

    5、线程的特点

    1、线程抢占式执行

    • 效率高
    • 可防止单一线程产时间独占CPU

    2、在单核CPU中,宏观上同时执行,微观上顺序执行。

    6、创建线程的三种方式

    1、继承 Thread类,重写run 方法

    package com.kingtl.ThreadDemo1;
    
    /**
     * 线程类
     * @author kingtl
     */
    public class MyThread extends Thread {
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("子线程..........."+ i);
            }
        }
    }
    
    package com.kingtl.ThreadDemo1;
    
    public class TestThread {
        public static void main(String[] args) {
            // 1、 创建线程对象
            MyThread myThread = new MyThread();
            // 2、启动线程
            myThread.start();
    
            // 主线程执行
            for (int i = 0; i < 50; i++) {
                System.out.println("主线程================" + i);
            }
        }
    }
    
    

    2、实现 Runnable 接口

    3、实现 Callable 接口

    7、获取和修改线程名称

    • 获取线程ID和线程名称
      • 在Thread的子类中调用 this,getId() 或 this.getName()
      • 使用 Thread.currentThread().getId() 和 Thread.currentThread().getName()
    package com.kingtl.ThreadDemo1;
    
    /**
     * 线程类
     * @author kingtl
     */
    public class MyThread extends Thread {
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                // 第一种  方式
                // this.getId() 获取线程id
                // this.getName()  获取线程的名称
                //System.out.println("线程id:"+this.getId()+"线程名称:"+ this.getName()+"子线程........"+i);
    
                // 第二种方式 Thread.currentThread()  获取当前线程
                System.out.println("线程id" + Thread.currentThread().getId() + "线程名称:" + Thread.currentThread().getName()+"子线程........"+i);
    
            }
        }
    }
    
    
    • 修改线程名称
      • 调用线程对象的setName() 方法
      • 使用线程子类的构造方法赋值。
    package com.kingtl.ThreadDemo1;
    
    /**
     * 线程类
     * @author kingtl
     */
    public class MyThread extends Thread {
    
        public MyThread() {
        }
    
        public MyThread(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                // 第一种  方式
                // this.getId() 获取线程id
                // this.getName()  获取线程的名称
                //System.out.println("线程id:"+this.getId()+"线程名称:"+ this.getName()+"子线程........"+i);
    
                // 第二种方式 Thread.currentThread()  获取当前线程
                System.out.println("线程id " +  Thread.currentThread().getId()  + " 线程名称:" + Thread.currentThread().getName()+" 子线程........"+i);
    
            }
        }
    }
    
    
    package com.kingtl.ThreadDemo1;
    
    public class TestThread {
        public static void main(String[] args) {
            // 1、 创建第一个线程对象
            MyThread myThread = new MyThread("我的子线程1");
            // 2、启动线程
            // 修改线程名称
            //myThread.setName("我的子线程1");
            myThread.start();
            // 1、 创建第一个线程对象
            MyThread myThread2 = new MyThread("我的子线程2");
            //myThread2.setName("我的子线程2");
            myThread2.start();
    
            // 主线程执行
            for (int i = 0; i < 50; i++) {
                System.out.println("主线程================" + i);
            }
        }
    }
    
    

    8、线程的状态(基本)

    NEW,  //初始状态
    
    RUNNABLE, //就绪 运行 状态
    
    BLOCKED,  // 阻塞状态
    
    WAITING,   // 无限时等待状态
    
    TIMED_WAITING,   // 限时等待状态
    
    TERMINATED;  // 终止状态
    

    初始状态 New

    线程对象被创建,即为初始状态。值在堆中开辟内存,与常规对象无异。

    RUNNABLE

    就绪状态 Ready

    调用start() 之后,进入就绪状态。等待OS选中,并分配时间片。

    运行状态 Running

    获得时间片之后,进入运行状态,如果时间片到期,则回到就绪状态。

    终止状态 Terminated

    主线程main() 或 独立线程run() 结束,进入终止状态,并释放持有的时间片。

    限时等待 Timed Waiting

    sleep() 到期,则回到就绪状态。

    无限期等待 Waiting

    Join() 加入线程执行完毕,阻塞的线程就可以继续执行。

    阻塞状态 Blocked

    synchronized

    9、常见方法

    休眠

    // 当前线程主动休眠 millis 毫秒
    public static void sleep(long millis)
    

    放弃

    // 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
    public static void yield()
    

    加入

    // 允许其他线程加入到当前线程中, 加入当前线程,并阻塞当前线程,直到加入线程执行完毕!当前线程才会继续执行!
    public final void join()
    

    优先级

    // 线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。
    线程对象.setPriority()
    

    守护线程

    线程对象.setDaemon(true); // 设置为守护线程
    // 线程有两类:用户线程(前台线程)、守护线程(后台线程)
    // 如果程序中 所有前台线程都执行完毕了,后台线程会自动结束。
    // 垃圾回收器线程属于守护线程。
    

    10、线程安全

    10.1 线程安全问题

    • 当多线程并发访问临界资源(共享资源)时,如果破话原子操作,可能会造成数据不一致。
      • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
      • 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。

    10.2 同步方式(1)

    • 同步代码块
    synchronized(临界资源对象) { // 对临界资源对象加锁
        //代码(原子操作)
    }
    

    注:

    • 每一个对象都有一个互斥锁标记,用来分配给线程的。
    • 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
    • 线程退出同步代码块时,会释放响应的互斥锁标记。

    10.3 同步方式(2)

    • 同步方法:
    synchronized 返回值类型 方法名(形参列表0){ // 对当前对象(this) 加锁
        // 代码(原子操作)
    }
    

    注:

    • 只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
    • 线程退出同步方法时,会释放响应的互斥锁标记。

    10.4 同步规则

    • 注意
      • 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
      • 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
    • 已知JDK中线程安全的类:
      • StringBuffer
      • Vector
      • Hashtable
      • 以上类中的公开方法,均为 synchronized 修饰的同步方法。

    11、死锁

    • 死锁
      • 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
      • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。

    12、线程通信

    等待

    public final void wait()
    public final void wate(long timeout)
    // 必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的队列中。释放鄋,进入等待队列
    

    通信

    public final void notify()
    public final void notifyAll()
    

    13、生产者和消费者的问题

    • 若干个生产者在生产产品,这些产品将提供给若干消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中去取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。

    13、线程池

    线程池的概念

    • 线程容器,可设定线程分配的数量上限。
    • 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
    • 避免频繁的创建和销毁。

    创建线程池

    常用的线程池接口和类(所在包java.util.concurrent)JUC

    • Executor:线程池的顶级接口。
    • ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。
    • Executors工具类:通过此类可以获得一个线程池。
    • 通过 newFixedThreadPool(int nThreads) 获取固定数量的线程池。参数:指定线程池中线程的数量。
    • 通过 newCachedThreadPool() 获得动态数量的线程池,如不够则创建新的,没有上线。
    package com.kingtl.JUCDemo;
    
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     *线程池的创建
     * Executor:线程池的根接口,execute()
     * ExecutorService:包含管理线程池的一些方法,submit  shutdown
     *      ThreadPoolExecutor
     *          ScheduleThreadPoolExector
     * Executors:创建线程池的工具类
     *          (1)创建固定线程个数线程池
     *          (2)创建缓存线程池,由任务的多少决定
     *          (3)创建单线程
     *          (4)创建调度线程  调度:周期、定期执行
     */
    public class Demo01 {
    
        public static void main(String[] args) {
            // 1.1 创建固定线程个数的线程池
            //ExecutorService es = Executors.newFixedThreadPool(4);
            // 1.2 创建缓存线程池,线程个数由任务个数决定
            ExecutorService es = Executors.newCachedThreadPool();
            // 1.3 创建单线程线程池
            //Executors.newSingleThreadExecutor();
            // 1.4 创建调度线程池  调度:周期、定时执行
            //Executors.newScheduledThreadPool(corePoolSize)
            //2、提交任务
            Runnable runnable = new Runnable() {
                private int ticket = 100;
                @Override
                public void run() {
                    while (true) {
                        if (ticket <= 0) {
                            break;
                        }
                        System.out.println(Thread.currentThread().getName()+" 卖了第"+ticket+"张票");
                        ticket--;
                    }
                }
            };
            //3、提交任务
            for (int i = 0; i < 4; i++) {
                es.submit(runnable);
            }
            // 4、关闭线程池
            es.shutdown(); //等待所有任务执行完毕,然后关闭线程池,不接受新任务。
    
        }
    
    }
    
    

    14、Callable接口

    Callable接口的使用

    package com.kingtl.JUCDemo;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * Callable接口的使用
     * Callable 和 Runnable 接口的区别
     *  (1)Callable接口中call方法有返回值,Runnable接口run方法没有返回值
     *  (2)Callable接口中call方法有声明异常,Runnable接口中run方法没有异常
     */
    public class Demo02 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            // 功能需求:使用 Callable 实现 1-100 的和
            // 1、创建 Callable对象
            Callable<Integer> callable = new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    System.out.println(Thread.currentThread().getName()+"开始计算");
                    int  sum = 0;
                    for (int i = 1; i <= 100; i++) {
                        sum += i;
                    }
                    return sum;
                }
            };
            // 2、把 Callable对象 转成可执行任务
            FutureTask<Integer> task = new FutureTask<>(callable);
    
            // 3、 创建线程
            Thread thread = new Thread(task);
    
            // 4、启动线程
            thread.start();
    
            // 5、获取结果(等待call执行完毕,才会返回)
            Integer sum = task.get();
            System.out.println("结果是:"+sum);
        }
    }
    
    

    Callable 和 Runnable 接口的区别

    (1) Callable接口中call方法有返回值,Runnable接口run方法没有返回值

    (2) Callable接口中call方法有声明异常,Runnable接口中run方法没有异常

    Callable结合线程池使用

    package com.kingtl.JUCDemo;
    
    import java.util.concurrent.*;
    
    /**
     * 使用线程池计算1-100的和
     *
     */
    public class Demo03 {
        public static void main(String[] args) throws Exception {
            // 1、创建线程池
            ExecutorService es = Executors.newFixedThreadPool(1);
            // 2、提交任务   Future 表示 将要执行完任务的结果
            Future<Integer> future = es.submit(new Callable<Integer>() {
    
                @Override
                public Integer call() throws Exception {
                    System.out.println(Thread.currentThread().getName() + "开始计算");
                    int sum = 0;
                    for (int i = 1; i <= 100; i++) {
                        sum += i;
                    }
                    return sum;
                }
            });
            // 3、获取结果
            System.out.println(future.get());
            // 4、关闭线程池
            es.shutdown();
        }
    }
    
    

    15、Future接口

    • Futrue:表示将要完成任务的结果。
    • 练习:使用两个线程,并发计算1-50、51-100 的和,再进行汇总统计。
    package com.kingtl.JUCDemo;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    /**
     * 使用两个线程,并发计算1-50、51-100 的和,再进行汇总统计。
     */
    public class Demo04 {
        public static void main(String[] args) throws Exception {
            // 1、创建线程池
            ExecutorService es = Executors.newFixedThreadPool(2);
            // 2、提交任务
            Future<Integer> future1 = es.submit(new Callable<Integer>() {
    
                @Override
                public Integer call() throws Exception {
                    int sum = 0;
                    for (int i = 1; i <= 50; i++) {
                        sum += i;
                    }
                    System.out.println("1-50 计算结束!");
                    return sum;
                }
            });
            Future<Integer> future2 = es.submit(new Callable<Integer>() {
    
                @Override
                public Integer call() throws Exception {
                    int sum = 0;
                    for (int i = 51; i <= 100; i++) {
                        sum += i;
                    }
                    System.out.println("51-100 计算结束!");
                    return sum;
                }
            });
            // 3、获取结果
            int sum = future1.get() + future2.get();
            System.out.println("结果是:" + sum);
    
            // 4、关闭线程池
            es.shutdown();
        }
    }
    
    
    • 表示ExecutorService.submit() 锁返回的状态结果,就是call() 的返回值。
    • 方法:V get() 以阻塞形式等待 Future 中的异步处理结果(call() 的返回值)

    16、线程的同步和异步

    线程的同步

    • 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。

    • 单条执行路径

    线程的异步

    • 形容一次方法调用,异步一旦开启,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行。

    • 多条执行路径

    17、Lock 接口

    • JDK5加入,与 synchronized 比较,显示定义,结构更灵活。
    • 提供更多实用性方法,功能更强大、性能更优越。
    • 常用方法
    // 获取锁,如锁被占用,则等待。
    void lock()
        
    // 尝试获取锁(成功返回 true。失败返回 false, 不阻塞)
    boolean tryLock()
        
    // 释放锁
    void unlock()
    

    重入锁

    • ReentrantLock:Lock接口的实现类,与synchronized 一样具有互斥锁功能。
    package com.kingtl.Lock;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Ticket implements Runnable {
        private int ticket = 100;
        private Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                lock.lock();
                try {
                    if (ticket <= 0) {
                        break;
                    }
                    System.out.println(Thread.currentThread().getName()+ "卖了第"+ticket+"张票");
                    ticket--;
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    
    
    package com.kingtl.Lock;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class TestTicket {
        public static void main(String[] args) {
            Ticket ticket = new Ticket();
            ExecutorService es = Executors.newFixedThreadPool(4);
            for (int i = 0; i < 4; i++) {
                es.submit(ticket);
            }
            es.shutdown();
        }
    }
    
    

    读写锁

    • ReentrantReadWriteLock
      • 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
      • 支持多次分配读锁,是多个读操作可以并发执行。
    • 互斥规则
      • 写-写:互斥,阻塞。
      • 读-写:互斥,读阻塞写、写阻塞读。
      • 读-读:不互斥、不阻塞。
      • 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。

    ReentrantReadWriteLock

    package com.kingtl.Lock;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class TestReadWriteLock {
        public static void main(String[] args) {
            final MyClass mc = new MyClass();
            Runnable task1 = new Runnable() {
                @Override
                public void run() {
                    try {
                        mc.setValue(1);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            Runnable task2 = new Runnable() {
                @Override
                public void run() {
                    try {
                        mc.getValue();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            ExecutorService es = Executors.newFixedThreadPool(20);
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 2; i++) {
                es.submit(task1);  // 提交两次写任务
            }
            for (int i = 0; i < 18; i++) {
                es.submit(task2); // 提交18次 读任务
            }
            es.shutdown();
            while (!es.isTerminated()) {} //如果线程未全部结束,则空转等待
            System.out.println(System.currentTimeMillis() - startTime);
        }
    }
    
    class MyClass{
       ReentrantReadWriteLock rwl =  new ReentrantReadWriteLock();
       ReentrantReadWriteLock.ReadLock readLock = rwl.readLock(); // 获得读锁
       ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock();  // 获得写锁
        private int value;
        //读 方法
        public int getValue() throws Exception{
            readLock.lock();
            try {
                Thread.sleep(1000); //休眠 1秒
                return value;
            } finally {
                readLock.unlock();  // 释放读锁
            }
        }
    
        // 写 方法
        public void setValue(int value) throws  Exception{
            writeLock.lock();
            try {
                Thread.sleep(1000);
                this.value = value;
            } finally {
                writeLock.unlock();
            }
        }
    }
    
    

    18、线程安全的集合

    Collection 体系集合

    除 Vector以外的线程安全集合。

    Map安全集合体系

    Collections 中的工具方法

    • Collections工具类中提供了多个可以获得线程安全集合的方法
    public static <T> Collection<T> synchronizedCollection(Collection<T> c)
        
    public static <T> List<T> snchronizedList(List<T> list)
        
    public static <T> Set<T> synchronizedSet(Set<T> s)
    
    public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
        
    public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
        
    public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
    
    • JDK1.2提供,接口统一、维护性高,但是性能没有提升,均以 synchronized实现!

    CopyOnWriteArrayList

    • 线程安全的ArrayList,加强版的读写分离。
    • 写有锁,读无锁,读写之间不阻塞,优于读写锁。
    • 写入时,先copy一个容器副本、再添加新元素,最后替换引用。
    • 使用方式与 ArrayList 无异。
    package com.kingtl.Lock;
    
    import java.util.Random;
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 使用多线程操作 CopyOnWriteArrayList
     */
    public class Demo02 {
        public static void main(String[] args) {
            // 1、创建集合
            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
            // 2、使用多线程
            ExecutorService es = Executors.newFixedThreadPool(5);
            // 3、提交任务
            for (int i = 0; i < 5; i++) {
                es.submit(new Runnable() {
                    @Override
                    public void run() {
                        for (int j = 0; j < 10; j++) {
                            list.add(Thread.currentThread().getName()+"...."+new Random().nextInt(1000));
                        }     
                    }
                });
            }
            // 4、关闭线程池
            es.shutdown();
            while (!es.isTerminated()) {}
            // 5、打印结果
            System.out.println("元素个数:" + list.size());
            for (String s : list) {
                System.out.println(s);
            }
        }
    }
    
    

    CopyOnWriteArraySet

    • 线程安全的 Set,底层使用 CopyOnWriteArrayList 实现。
    • 唯一不同在于,使用 addIfAbsent() 添加元素,会遍历数组。
    • 如 存在元素,则不添加(扔掉副本)。

    19、Queue 接口(队列)

    • Collection 的子接口,表示队列 FIFO(First In First Out) 先进先出

    • 常用方法:

      • 抛出异常:

        // 顺序添加一个元素 (到达上限后,再添加则会抛出异常)
        boolean add(E e)
            
        // 获得第一个元素并移除 (如果队列没有元素时,则抛出异常)
        E remove()
            
        // 获得第一个元素但不移除 (如果队列没有元素时,则抛出异常)
        E element()
        
      • 返回特殊值:推荐使用

        // 顺序添加一个元素(到达上限后,再添加则会返回 false)
        boolean offer(E e)
            
        // 获得第一个元素并移除(如果队列没有元素时,则返回null)
        E poll()
            
        // 获得第一个元素但不移除(如果队列没有元素时,则返回 null)
        E peek()
        
        package com.kingtl.Queue;
        
        import java.util.LinkedList;
        import java.util.Queue;
        
        public class Demo01 {
            public static void main(String[] args) {
                // 1、创建队列  LinkedList只能单线程
                Queue<String> queue = new LinkedList<>();
                // 2、入队
                queue.offer("苹果");
                queue.offer("橘子");
                queue.offer("葡萄");
                queue.offer("西瓜");
                queue.offer("榴莲");
                // 3、出队
                System.out.println(queue.peek());
                System.out.println("==================");
                System.out.println("元素个数:" + queue.size());
                int size = queue.size();
                for (int i = 0; i < size; i++) {
                    System.out.println(queue.poll());
                }
                System.out.println("出队结束!"+ queue.size());
            }
        }
        
        

    ConcurrentLinkedQueue

    • 线程安全、可高效读写的队列,高并发下性能最好的队列。
    • 无锁、CAS比较交换算法,修改的方法包含是三个核心参数(V,E,N)
    • V:要更新的变量 、E:预期值、N:新值
    • 只要当 V==E时,V=N;否则表示已被更新过,则取消当前操作。
    package com.kingtl.Queue;
    
    import java.util.concurrent.ConcurrentLinkedQueue;
    
    /**
     * ConcurrentLinkedQueue
     */
    public class Demo02 {
        public static void main(String[] args) throws Exception {
            // 1、创建一个安全队列
            ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
            // 2、入队
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 1; i <= 5; i++) {
                        queue.offer(i);
                    }
                }
            });
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 6; i <= 10; i++) {
                        queue.offer(i);
                    }
                }
            });
            // 3、启动线程
            t1.start();
            t2.start();
    
            t1.join();
            t2.join();
    
            System.out.println("--------出队---------");
            // 4、出队操作
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                System.out.println(queue.poll());
            }
    
        }
    }
    
    

    BlockingQueue接口(阻塞队列)

    • Queue 的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。

    • 方法:

      // 将指定的元素插入此队列中,如果没有可用空间,则等待。
      void put(E e)
          
      // 获取并移除 此队列头部元素,如果没有可用元素,则等待
      E take()
      
    • 可用于解决生产者、消费者问题。

    阻塞队列

    • ArrayBlockingQueue
      • 数组结构实现,有界队列。(手动固定上限)
    • LinkedBlockingQueue
      • 链表结构实现,有界队列。(默认上限Integer.MAX_VALUE)
    package com.kingtl.Queue;
    
    import java.util.concurrent.ArrayBlockingQueue;
    
    /**
     * 阻塞队列的使用
     * 案例1:创建一个有界队列,添加数据
     * 案例2:使用阻塞队列实现生产者和消费者
     */
    public class Demo03 {
        public static void main(String[] args) throws Exception {
            // 创建一个有界队列,添加数据
            ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(5);
            // 添加元素
            queue.put("aaa");
            queue.put("bbb");
            queue.put("ccc");
            queue.put("ddd");
            queue.put("eee");
            // 删除元素
            queue.take();
            System.out.println("已经添加了 5个元素");
            queue.put("xyz");
            System.out.println("已经添加了6个元素");
            System.out.println(queue.toString());
    
        }
    }
    
    
    package com.kingtl.Queue;
    
    import java.util.concurrent.ArrayBlockingQueue;
    
    /**
     * 案例2:使用阻塞队列实现生产者和消费者
     */
    public class Demo04 {
        public static void main(String[] args) {
            // 1、创建队列
            ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(6);
            // 2、创建两个线程
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 30; i++) {
                        try {
                            queue.put(i);
                            System.out.println(Thread.currentThread().getName()+ "生产了第" + i +"号面包");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            },"king");
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 30; i++) {
                        try {
                            Integer num = queue.take();
                            System.out.println(Thread.currentThread().getName()+ "消费了第" + i +"号面包");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            },"geek");
            // 启动线程
            t1.start();
            t2.start();
        }
    }
    
    

    ConcurrentHashMap

    JDK1.8之前:

    • 初始容量默认为16段(Segment),使用分段锁设计
    • 不对整个Map加锁,而是为每个Segment 加锁。
    • 当多个对象存入同一个 Segment 时,才需要互斥。
    • 最理想状态为16个对象分别存入16个Segment,并行数量16.
    • 使用方式与 HashMap无异。

    JDK1.8之后使用 CAS

    package com.kingtl.Queue;
    
    import java.util.concurrent.ConcurrentHashMap;
    
    public class Demo05 {
        public static void main(String[] args) {
            // 1、创建集合
            ConcurrentHashMap<String, String> hashmap = new ConcurrentHashMap<>();
            // 2、使用多线程添加数据
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int j = 0; j < 10; j++) {
                            hashmap.put(Thread.currentThread().getName()+"---"+j,j+"");
                            System.out.println(hashmap);
                        }
                    }
                }).start();
            }
        }
    }
    
    

    20、总结

    • ExecutorService 线程池接口、Executors工厂。
    • Callable 线程任务、Future 异步返回值。
    • LockReentrantLock重入锁、ReentrantReadWriteLock读写锁。
    • CopyOnWriteArrayList 线程安全的ArrayList。
    • CopyOnWriteArraySet 线程安全的Set。
    • ConcurrentLinkedQueue 线程安全的Queue。
    • ArrayBlockingQueue 线程安全的阻塞Queue。(生产者、消费者)
    • ConcurrentHashMap 线程安全的 HashMap。
  • 相关阅读:
    聪明人 & 普通人
    13种模型及方法论
    面向大规模商业系统的数据库设计和实践
    分治算法
    软件架构
    隐含前提思维模型
    Git回滚代码到某个commit
    使用arthas排查 test 问题
    Arthas
    docker 操作入门
  • 原文地址:https://www.cnblogs.com/KingTL/p/13511222.html
Copyright © 2011-2022 走看看