多线程笔记

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。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值