JAVA多线程(二)
- 线程是宝贵的内存资源,单个线程约占1MB空间,过多的分配易造成内存溢出
- 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销、造成程序性能下降
线程池
- 线程的容器,可设定线程分配的数量上限
- 将预先创建的线程对象存入池中,并重用线程池中的线程对象
- 避免频繁的创建或销毁
线程池原理
- 将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程
创建线程池
- Executor:线程池的顶级接口
- ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码
- Executors工厂类:通过此类可以获得一个线程池
- 通过newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量
- 通过newCachedThreadPool()获取动态数量的线程池,没有上线
Executors:创建线程池的工具类
-
创建固定大小的线程池
-
创建缓存线程池,由任务的多少来决定
-
创建单线程池
-
创建调度线程池 调度(周期,定时执行)
-
package com.wly.Executor; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; //Executor:线程池的根接口,execute() //ExecutorService:包含管理线程的一些方法submit //TheardPoolExecuter //ScheduledTheardPoolExecutor //Ex public class Demo { public static void main(String[] args) { //创建固定线程个数的线程池 // ExecutorService es= Executors.newFixedThreadPool(4); //创建缓存线程池,线程个数由任务个数决定 ExecutorService es=Executors.newCachedThreadPool(); //单线程池 // Executors.newSingleThreadExecutor(); //调度线程池 // Executors.newScheduledThreadPool(4); //提交任务 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--; } } }; //提交任务 for (int i = 0; i <5; i++) { es.submit(runnable); } //关闭线程池 es.shutdown();//等待所有任务执行完毕,关闭线程池 } }
Callable接口
-
public interface Callable{
public V call() throws Exception;
}
-
JDK5加入,与Runnable接口类似,实现之后代表一个线程任务
-
Callable具有泛型返回值,可以声明异常
-
Callable接口中的call方法有返回值,Runnable接口里的run()方法没有返回值
-
Callable接口中的call方法有声明异常,Runable接口中的run()方法没有异常
-
package com.wly.Executor; import java.util.concurrent.*; //使用线程池 public class DemoCallable2 { public static void main(String[] args) throws ExecutionException, InterruptedException { //创建线程池 ExecutorService executorService=Executors.newFixedThreadPool(1); //提交方法 Future<Integer> future=executorService.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; } }); //获取任务的结果 System.out.println(future.get()); //关闭线程池 executorService.shutdown(); } }
Future接口
-
Future:表示将要完成任务的结果
-
使用两个线程,并发计算1-50,50-100,再进行汇总
-
package com.wly.Executor; import java.awt.*; import java.util.concurrent.*; //使用两个线程,并发进行1~50,50~100,再进行汇总统计 public class DemoCallable3 { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService es=Executors.newFixedThreadPool(2); //添加任务 Future<Integer>future1= es.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { int sum=0; for (int i = 0; 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; } }); int sum=future1.get()+future2.get(); System.out.println("结果是"+sum); //关闭线程池 es.shutdown(); } }
-
表示ExecutorService.submit()所返回的状态,就是call()的返回值
-
方法**V.get()**以阻塞形式等待Future中的异步处理结果(call()的返回值)
线程的同步
- 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回
线程的异步
- 形容一次方法调用,异步一旦开始,像是一次消息传递,调用在告知之后立刻返回,两者竞争时间片
Lock()接口
- JDK5,与synchronized比较,显示定义,结构更灵活。
- 提供更多实用性方法,功能更强大,性能更优越
- 常用方法
- void lock()//获得锁,如果锁被占用,则等待
- boolean tryLock()//尝试获取锁(成功返回ture,失败返回false,不阻塞)
- void unlock()//释放锁
重入锁
-
ReentrantLock:Lock接口实现类,与synchronized一样具有互斥锁功能
-
package com.wly.Executor; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MylistLock { //创建锁 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++; System.out.println(Thread.currentThread().getName()+"添加了"+value); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } finally { lock.unlock(); } } public String[] getStr(){ return str; } }
-
package com.wly.Executor; import com.wly.Executor.MylistLock; import java.util.Arrays; public class TestMylist { public static void main(String[] args) throws InterruptedException { MylistLock list = new MylistLock(); Runnable runnable = new Runnable() { @Override public void run() { list.add("hello"); } }; Runnable runnable2 = new Runnable() { @Override public void run() { list.add("word"); } }; //创建线程 Thread t1 = new Thread(runnable); Thread t2 = new Thread(runnable2); t1.start(); t2.start(); // t1.join(); t2.join(); System.out.println(Arrays.toString(list.getStr())); } }
重入锁使用
-
package com.wly.Executor; 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--; } finally { lock.unlock(); } } } }
-
package com.wly.Executor; 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:
-
一种支持一种多读的同步锁,读写分离,可以分别分配读锁,写锁
-
支持多次分配读锁,使用多个读操作可以并发执行
-
互斥规则:
- 写——写:互斥,阻塞
- 读——写:读阻塞写、写阻塞读
- 读——读:不互斥、不阻塞
-
在读操作远远高于写操作环境下,可以保障线程安全的情况下,提高运行效率。
-
package com.wly; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.*; public class ReadWriteDemo { //创建读写锁 private ReentrantReadWriteLock rr1=new ReentrantReadWriteLock(); //获取读锁 private ReadLock readLock=rr1.readLock(); //获取写锁 private WriteLock writeLock=rr1.writeLock(); private String value; //读取方法 public String getValue(){ //使用读锁上锁 readLock.lock(); try { try{ Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("读取=========="+value); return this.value; }finally { readLock.unlock(); } } //写入方法 public void setValue(String value){ writeLock.lock(); try{ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("写入"+value); this.value=value; }finally { writeLock.unlock(); } } }
-
package com.wly; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestReadWrite { public static void main(String[] args) { ReadWriteDemo readWriteDemo = new ReadWriteDemo(); //创建线程池 ExecutorService es = Executors.newFixedThreadPool(20); Runnable read = new Runnable() { @Override public void run() { readWriteDemo.getValue(); } }; Runnable write = new Runnable() { @Override public void run() { readWriteDemo.setValue("张三" + new Random().nextInt(100)); } }; long start = System.currentTimeMillis(); //2个写任务 for (int i = 0; i < 2; i++) { es.submit(write); } //18个读取任务 for (int i = 0; i < 18; i++) { es.submit(read); } es.shutdown(); //isTerminated()为true表示线程池任务执行完毕 while (!es.isTerminated()) { } long end=System.currentTimeMillis(); System.out.println("用时:"+(end-start)); } }
线程安全的集合
-
Collection体系中,除Vector以外的线程安全集合
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MBIEo186-1672213466536)(D:\笔记\JAVA多线程\IMG_1291(20220401-185857)].PNG)
-
Map体系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jDUTp7tL-1672213466537)(D:\笔记\JAVA多线程\IMG_1292(20220401-191351)].PNG)
Collections中的工具方法
- Collections工具类中提供了多个可以获得线程安全集合的方法
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-txTCH1l1-1672213466537)(D:\笔记\JAVA多线程\IMG_1293(20220401-192443)].PNG)
- JDK1.2提供,接口统一,维护性高,但性能没有提升,均以synchonized实现
CopyOnWriteArrayList
-
线程安全的ArrayList,加强版读写分离
-
写有锁,读无锁,读写之间不阻塞,优于读写锁
-
写入时,先copy一个容器副本,再添加新元素,最后替换引用
-
使用方式与ArrayList无异
-
package com.wly.Collections; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class Demo1 { public static void main(String[] args) { //方法一: // ArrayList<String> arrayList=new ArrayList<>(); // List<String> synlist= Collections.synchronizedList(arrayList); //方法二: CopyOnWriteArrayList<String> arrayList=new CopyOnWriteArrayList<>(); //创建线程 for (int i = 0; i <5 ; i++) { int temp=i; new Thread(new Runnable() { @Override public void run() { for (int j = 0; j <10 ; j++) { arrayList.add(Thread.currentThread().getName()+"======"+temp+"===="); System.out.println(arrayList.toString()); } } }).start(); } } }
-
package com.wly.Collections; import java.util.Random; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Demo2 { public static void main(String[] args) { CopyOnWriteArrayList list=new CopyOnWriteArrayList(); //使用多线程操作 ExecutorService es=Executors.newFixedThreadPool(5); 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)); } } }); } //关闭线程池 es.shutdown(); while (!es.isTerminated()){ } System.out.println("元素个数"+list.size()); for (Object string:list ) { System.out.println(string); } } }
-
package com.wly.Collections; import java.util.Random; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Demo2 { public static void main(String[] args) { CopyOnWriteArrayList list=new CopyOnWriteArrayList(); //使用多线程操作 ExecutorService es=Executors.newFixedThreadPool(5); 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)); } } }); } //关闭线程池 es.shutdown(); while (!es.isTerminated()){ } System.out.println("元素个数"+list.size()); for (Object string:list ) { System.out.println(string); } } }
CopyOnEriteArraySet
- 线程安全的Set,底层使用CopyOnWriteArrayList实现
- 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组
- 如果元素存在,则不添加(不重复)
Queue接口队列
-
Collection的子接口,表示队列EIFO(First In First Out)先进先出
-
常用方法:
-
抛出异常
-
方法 说明 boolean add(E e) 顺序添加一个元素(达到上限后,再添加则会抛出异常) E emove() 获得第一个元素并移除(如果对列没有元素,则抛出异常) E element() 获得第一个元素但不移除(如果对列没有元素,则抛出异常) -
返回特殊值
方法 说明 boolean offer(E e) 顺序添加一个元素(到达上限后,再添加则会返回false) E poll() 获得第一个元素并移除(如果队列没有元素时,则返回null) E peek() 获得第一个元素但不移除(如果队列没有元素时,则返回null) -
ConcurrentLinkedQueue
-
线程安全、可高效读写队列,高并发下性能最好的队列
-
无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
-
V:要更新的变量、E:预期值、N:新值
-
只有当V=E时,V=N;否则表示已被更新过,则取消当前操作
-
package com.wly.Collections; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; public class Demo3 { public static void main(String[] args) throws InterruptedException { ConcurrentLinkedQueue<Integer> queue=new ConcurrentLinkedQueue<>(); //入队操作 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); } } }); //启动线程 t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("==================="); //出队操作 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_VAULE)
-
package com.wly.Collections; import java.util.concurrent.ArrayBlockingQueue; //阻塞队列 public class Demo4 { public static void main(String[] args) throws InterruptedException { //创建一个有界队列,添加元素 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("xxx"); System.out.println("加入第6个元素"); System.out.println(queue.toString()); } }
生产者、消费者问题
-
package com.wly.Collections; import java.util.concurrent.ArrayBlockingQueue; //阻塞对列的使用 //使用阻塞对列实现生产者,消费者 public class Demo5 { public static void main(String[] args) throws InterruptedException { ArrayBlockingQueue<Integer> queue=new ArrayBlockingQueue<>(6); //创建两个线程 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 take = queue.take(); System.out.println(Thread.currentThread().getName()+"消费了"+i); } catch (InterruptedException e) { e.printStackTrace(); } } } },"明明"); t1.start(); t2.start(); } }
ConcurrentHashMap
-
初始容量默认增加16段(Segment),使用分段锁设计
-
不对整个Map加锁,而是为每个Segment加锁
-
当多个对象存入同一个Segment时,才需要互斥
-
使用方式与HashMap无异
-
package com.wly.Collections; import java.util.concurrent.ConcurrentHashMap; public class Demo6 { public static void main(String[] args) { ConcurrentHashMap<String,String> hashMap=new ConcurrentHashMap<>(); //使用多线程添加数据 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(); } } }
-
当多个对象存入同一个Segment时,才需要互斥
-
使用方式与HashMap无异
-
package com.wly.Collections; import java.util.concurrent.ConcurrentHashMap; public class Demo6 { public static void main(String[] args) { ConcurrentHashMap<String,String> hashMap=new ConcurrentHashMap<>(); //使用多线程添加数据 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(); } } }