线程池

1.线程池

  • 问题:

    • 线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。
    • 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。
  • 线程池:

    • 线程容器,可设定线程分配的数量上限。
    • 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
    • 避免频繁的创建和销毁。
  • 线程池工作原理:将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程。

image-20210406103134700

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

    Executor//线程池的顶级接口,只有一个方法execute()
    ExecutorService//线程池接口,可通过submit(Runnable task)提交任务代码;有两个实现类
        ThreadPoolExecutor
        ScheduledThreadPoolExecutor
    Executors工具类//通过此类可以获得一个线程池。
    	//通过newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量。
    	//通过newCachedThreadPool()获得动态数量的线程池,如不够则创建新的,没有上限
        //创建单线程池
        //创建调度线程池,调度:周期,定时执行
    
    /**
     * 演示线程池的创建
     */
    public class Demo01 {
        public static void main(String[] args) {
            //1.创建线程池
            //1.1创建固定线程个数的线程池
    //        ExecutorService executorService = Executors.newFixedThreadPool(4);
            //1.2创建缓冲线程池,线程个数由任务个数确定
            ExecutorService executorService = Executors.newCachedThreadPool();
            //1.3创建单线程池
    //        Executors.newSingleThreadExecutor();
            //1.4创建调度线程池
    //        Executors.newScheduledThreadPool();
            //2.提交任务
            Runnable runnable=new Runnable() {
                private int ticket=100;
                @Override
                public void run() {
                    while (true) {
                        synchronized (this){
                        if (ticket <=0){
                            break;
                        }
                        System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
                        ticket--;
                        }
                    }
                }
            };
            for (int i=0;i<5;i++) {
                executorService.submit(runnable);
            }
            //3.关闭线程池
            executorService.shutdown();//等待所有任务执行完毕,然后关闭线程池
    
        }
    }
    

2.Callable接口

public interface Callable<V> {
	public V call() throws Exception;
}
  • JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
  • Callable具有泛型返回值、可以声明异常。
  • Callable和Runnable接口的区别:
    • (1)Callable接口中call方法有返回值, Runnable接口中run方法没有返回值
    • (2)Callable接口中call方法有声明异常,Runnable接口中run方法没有异常
/**
 * 演示Callable接口的使用
 * Callable和Runnable接口的区别
 * (1)Callable接口中call方法有返回值, Runnable接口中run方法没有返回值
 * (2)Callable接口中call方法有声明异常,Runnable接口中run方法没有异常
 */
public class Demo02 {
    public static void main(String[] args) throws Exception{
        //功能要求:使用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 = 0; i < 100; i++) {
                    sum+=i;
                    Thread.sleep(100);
                }

                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);
    }
}
/**
 * 使用线程池计算1-100的和
 */
public class Demo03 {
    public static void main(String[] args) throws Exception{
        //1.创建线程池
        ExecutorService es= Executors.newFixedThreadPool(1);
        //2.提交任务
        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 = 0; i < 100; i++) {
                    sum+=i;
                }
                return sum;
            }
        });
        //3.获取结果,等待任务执行完毕才会返回
        System.out.println(future.get());
        //4.关闭线程池
        es.shutdown();
    }
}

3.Future接口

  • 表示将要完成任务的结果
  • 表示ExecutorService.submit()所返回的状态结果,就是call()的返回值
  • 方法: V get()以阻塞形式等待Future中的异步处理结果(call()的返回值)

需求:使用两个线程,并发计算150、51100的和,再进行汇总统计

/**
 * 需求:使用两个线程,并发计算1~50、51~100的和,再进行汇总统计
 */
public class Demo04 {
    public static void main(String[] args) throws Exception{
        ExecutorService es= Executors.newFixedThreadPool(2);
        Callable<Integer> callable1=new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"开始计算1-49:");
                int sum=0;
                for (int i = 0; i < 50; i++) {
                    sum+=i;
                }
                return sum;
            }
        };
        Callable<Integer> callable2=new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"开始计算50-100:");
                int sum=0;
                for (int i = 50; i < 101; i++) {
                    sum+=i;
                }
                return sum;
            }
        };
        Future<Integer> submit1 = es.submit(callable1);
        Future<Integer> submit2 = es.submit(callable2);
        //获取结果get()
        Integer integer1 = submit1.get();
        Integer integer2 = submit2.get();
        System.out.println("结果是:"+(integer1+integer2));
        //关闭线程池
        es.shutdown();
    }
}

4.线程的同步与异步

  • 同步:形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。
  • 同步是单条执行路径。

image-20210406210646930

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

image-20210406210830230

5.Lock接口

  • JDK5加入,与synchronized比较,显示定义,结构更灵活。

  • 提供更多实用性方法,功能更强大、性能更优越。

  • 常用方法:

    void lock()//获取锁,如锁被占用,则等待。
    boolean tryLock()//尝试获取锁(成功返回true。失败返回false,不阻塞)
    void unlock()//释放锁
    

5.1重入锁

ReentrantLock: Lock接口的实现类,与synchronized一样具有互斥锁功能。

/**
 * 演示ReentrantLock的使用
 */
public class MyList {
    private Lock lock=new ReentrantLock();
    private String[] str={"A","B","","",""};
    private int count=2;
    public void add(String value){
        lock.lock();
        try {
            str[count]=value;
            count++;
        }finally {
            lock.unlock();
        }
        System.out.println(Thread.currentThread().getName()+"添加了:"+value);
    }

    public String[] getStr() {
        return str;
    }
}

//测试类
public class TestMyList {
    public static void main(String[] args) throws Exception{
        MyList list=new MyList();
        Runnable runnable1=new Runnable() {
            @Override
            public void run() {
                list.add("hello");
            }
        };
        Runnable runnable2=new Runnable() {
            @Override
            public void run() {
                list.add("world");
            }
        };
        Thread thread1=new Thread(runnable1);
        Thread thread2=new Thread(runnable2);
        thread1.start();
        thread2.start();
        thread1.join();//保证在输出str之前把thread2执行完
        thread2.join();//保证在输出str之前把thread2执行完

        System.out.println(Arrays.toString(list.getStr()));
    }
}
//卖票
public class Ticket implements Runnable{
    private int ticket=100;
    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--;
            }finally {
                lock.unlock();
            }
        }
    }
}

//测试类
public class Test {
    public static void main(String[] args) {
        Ticket ticket=new Ticket();
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 4; i++) {
            executorService.submit(ticket);
        }
        executorService.shutdown();
    }
}

5.2读写锁

  • ReentrantReadWriteLock:

    • 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
    • 支持多次分配读锁,使多个读操作可以并发执行。
  • 互斥规则:

    • 写-写:互斥,阻塞。
    • 读-写:互斥,读阻塞写、写阻塞读。。读-读:不互斥、不阻塞。
    • 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
/**
 * 演示读写锁的使用
 * ReentrantReadWriteLock
 */
public class ReadWriteDemo0 {
    //创建读写锁
    private ReentrantReadWriteLock rrw=new ReentrantReadWriteLock();
    //获取读锁
    private ReentrantReadWriteLock.ReadLock readLock=rrw.readLock();
    //获取写锁
    private String valve;

    //创建互斥锁
    private ReentrantLock rl=new ReentrantLock();

    private ReentrantReadWriteLock.WriteLock writeLock=rrw.writeLock();
    //读取
    public String getValve() {
        //使用读锁上锁
        readLock.lock();
        try {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("读取"+valve);
            return valve;
        } finally {
            readLock.unlock();
        }
    }
    //写入
    public void setValve(String valve) {
        rl.lock();
        try {
            System.out.println("写入"+valve);
            this.valve = valve;
        } finally {
            rl.unlock();
        }
    }
}

//测试类
public class Test {
    public static void main(String[] args) {
        ReadWriteDemo0 rw=new ReadWriteDemo0();
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        Runnable runnableRead=new Runnable() {
            @Override
            public void run() {
                rw.getValve();
            }
        };
        Runnable runnableWrite=new Runnable() {
            @Override
            public void run() {
                rw.setValve("张三:"+new Random().nextInt(100));
            }
        };
        long start=System.currentTimeMillis();
        //分配两个写的任务
        for (int i = 0; i < 2; i++) {
            executorService.submit(runnableWrite);
        }
        //分配任务,18个读取任务
        for (int i = 0; i < 18; i++) {
            executorService.submit(runnableRead);
        }
        executorService.shutdown();
        while (!executorService.isTerminated()) {//空转
        }
        long end=System.currentTimeMillis();
        System.out.println("用时:"+(end-start));
    }
}

6.线程安全的集合

  • Collection体系集合中,除Vector以外的线程安全集合(蓝色是线程安全)

image-20210412114036904

  • Map体系集合(蓝色是线程安全)

image-20210412114120566

  • Collections中的工具方法
//Collections工具类中提供了多个可以获得线程安全集合的方法。
public static <T> Collection<T> synchronizedCollection(Collection<T> c) public static <T> List<T> synchronizedList(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提供,接口统一、维护性高,但性能没有提升,均以synchonized实现。
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 使用多线程操作线程不安全集合
 * 把线程不安全的集合转为线程安全的集合
 */
public class Demo01 {
    public static void main(String[] args) {
        //1.创建ArrayList
//        ArrayList<String> arrayList=new ArrayList<>();
        //1.1.使用Collections钟德线程安全方法转成线程安全的集合
//        List<String> strings = Collections.synchronizedList(arrayList);
        //1.2.使用CopyOnWriteArrayList
        CopyOnWriteArrayList<String> copy=new CopyOnWriteArrayList<>();
        //2.创建线程
        for (int i = 0; i < 30; i++) {
            int temp=i;
            new Thread(new Runnable(){
                @Override
                public void run() {
                    for (int j = 0; j <2 ; j++) {
                        copy.add(Thread.currentThread().getName()+"====="+temp+"====="+j);
                        System.out.println(copy.toString());
                    }
                }
            }).start();
        }
    }
}

6.1CopyOnWriteArrayList

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

6.2CopyOnWriteArraySet

  • 线程安全的Set,底层使用CopyOnWriteArrayList实现。
  • 唯一不同在于,使用addlfAbsent()添加元素,会遍历数组,如存在元素,则不添加(扔掉副本)。
/**
 * 演示CopyOnWriteArraySet的使用
 * 有序,不会添加重复元素
 */
public class Demo03 {
    public static void main(String[] args) {
        //1.创建集合
        CopyOnWriteArraySet<String> cowas=new CopyOnWriteArraySet<>();
        //2.添加元素
        cowas.add("pingguo");
        cowas.add("huawei");
        cowas.add("xiaomi");
        cowas.add("lianxiang");
        cowas.add("lianxiang");
        //3.打印
        System.out.println("元素个数:"+cowas.size());
        System.out.println(cowas.toString());
    }
}

6.3Queue接口

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

  • 常用方法:

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

    boolean offer(E e)//顺序添加一个元素(到达上限后,再添加则会返回false)。
    E poll()//获得第一个元素并移除(如果队列没有元素时,则返回null)
    E peek()//获得第一个元素但不移除(如果队列没有元素时,则返回nul1)
    
    /**
     * Queue接口的使用
     */
    public class Demo04 {
        public static void main(String[] args) {
            //1.创建队列
            Queue<String> queue=new LinkedList<>();
            //2.入队
            queue.offer("苹果");
            queue.offer("橘子");
            queue.offer("西瓜");
            queue.offer("榴莲");
            System.out.println(queue.peek());
            //3.出队
            System.out.println("===========");
            int size=queue.size();
            for (int i = 0; i < size; i++) {
                System.out.println(queue.poll());
            }
        }
    }
    

6.4ConcurrentLinkedQueue

  • 线程安全、可高效读写的队列,高并发下性能最好的队列。
  • 无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)。
  • V:要更新的变量、E:预期值、N:新值。
  • 只有当V==E时,V=N;否则表示已被更新过,则取消当前操作。
/**
 * 演示ConcurrentLinkedQueue的使用
 */
public class Demo05 {
    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 = 0; 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());
        }
    }
}

6.5BlockingQueue接口(阻塞队列)

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

  • 方法:

     void put(E e)//将指定元素插入此队列中,如果没有可用空间,则等待。
     E take()//获取并移除此队列头部元素,如果没有可用元素,则等待。
    
  • 实现类:

//ArrayBlockingQueue:数组结构实现,有界队列。(手工固定上限)
public class TestArrayB1ockingQueue {
  public static void main(String[]args) {
  BlockingQueue<String> abq = new ArrayBlockingQueue<String>(10);
  }
}
// LinkedBlockingQueue:链表结构实现,有界队列。(默认上限Integer.MAX_VALUE)
public class TestLinkedBlockingQueue {
  public static void main(String[]args) {
  BlockingQueue<String> lbg = new LinkedBlockingQueue<String>;
  }
}
/**
* 阻塞队列的使用
* 案例1:创建一个有界队列,添加数据
* 案例2:使用阻塞队列实现生产者和消费者
* @author NG
*/
public class Demo06 {
  public static void main(String[] args) throws Exception{
      //创建一个有界队列,添加数据
      ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(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());
  }
}

/**
*  案例2:使用阻塞队列实现生产者和消费者
*/
public class Demo07 {
  public static void main(String[] args) {
      //1.创建队列
      ArrayBlockingQueue<Integer> queue=new ArrayBlockingQueue<>(6);
      //2.创建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();
                  }
              }
          }
      },"晨晨");
      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();
                  }
              }
          }
      },"冰冰");
      //3.启动线程
      t1.start();
      t2.start();
  }
}

6.6ConcurrentHashMap

  • 初始容量默认为16段(Segment),使用分段锁设计。
  • 不对整个Map加锁,而是为每个Segment加锁。
  • 当多个对象存入同一个Segment时,才需要互斥。
  • 最理想状态为16个对象分别存入16个Segment,并行数量16。使用方式与HashMap无异。
/**
 *ConcurrentHashMap的使用
 */
public class Demo08 {
    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();
        }
    }
}
posted @ 2021-06-28 21:24  underline  阅读(111)  评论(0)    收藏  举报