同步和异步概念
-
同步
-
形容一次方法调用,同步一旦执行,调用者必须等待该方法返回,才能继续
-
-
异步
-
形容一次方法调用,异步一点开始,像是一次信息传递,调用者告知之后立刻返回,二者竞争时间片,并发执行
-
线程池概念
-
现有问题
-
线程是宝贵的内存资源,单个线程约占1MB空间,过多分配容易造成内存溢出
-
频繁的创建和销毁线程会增加虚拟机回收频率、资源开销,造成性能下降
-
-
线程池
-
线程容器,可以设定线程分配的数量上限
-
将预先创建的线程对象存入线程池中,并重用线程池中的对象
-
避免频繁的创建和销毁
-
-
线程池原理
-
将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程
-
获取线程池
-
常用的线程池接口和类(所在包:java.util.concurrent)
-
Executor:线程池的顶级接口
-
ExecutorService:线程池接口,可以通过submit(Runnable task)提交任务代码
-
Executors工厂类:通过此类可以获得一个线程池
-
通过newFixedThreadPool(int nThread)获取固定数量的线程池。参数:指定线程池中线程的数量
-
通过newCachedThreadPool()获取动态数量的线程池,如果不够则创建新的线程,无上限
-
Executors.newSingleThreadExecutor();//创建单个线程池
-
Executors.newScheduledThreadPool();//调度线程池,实现延迟、周期执行任务。
-
public static void main(String[] args) { var es = Executors.newScheduledThreadPool(1); //延迟执行,十秒后执行 es.schedule(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"zhixingle........"); // } },10000, TimeUnit.MILLISECONDS); es.shutdown(); }
public static void main(String[] args) { //创建调度线程池 ScheduledExecutorService es = Executors.newScheduledThreadPool(1); //分配任务 //2.1固定频率执行 参数: 执行的任务 初始延迟时间 周期时间 时间单位 es.scheduleAtFixedRate(new Runnable() { @Override public void run() { Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss"); System.out.println(sdf.format(date)); count++; num++; if(num ==11){ System.out.println("开始休息5秒"); //但是休息的5秒,后面直接执行五次 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } if(count==20){ es.shutdown(); //关闭线程池 } } },0,1000,TimeUnit.MILLISECONDS); //2.2固定延迟执行 }
-
public static void main(String[] args) { //1创建固定线程个数的线程池 ExecutorService es = Executors.newFixedThreadPool(4); //创建一个缓存线程池,线程个数有任务个数来决定 ExecutorService es1 = Executors.newCachedThreadPool(); //创建单个线程的线程池 ExecutorService es2 = Executors.newSingleThreadExecutor(); //创建调度线程池 调度:周期、定期执行 ExecutorService es3 = Executors.newScheduledThreadPool(4); //创建任务 Runnable ticket =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<4;i++){ es.submit(ticket); } //关闭线程 es.shutdown(); //等待任务执行完毕在关闭线程池,执行以前提交的任务,但是不接收新的任务 es.shutdownNow();//直接关闭线程池,不等待任务结束,暂停处理正在等待的任务,并且返回等待执行的任务列表 }
线程池的七个参数
-
核心线程数
-
最大线程数
-
非核心线程存活时间
-
时间单位
-
工作队列
-
线程工厂
-
拒绝策略(当进入的线程超过工作队列线程数+最大线程数)
-
抛弃,并抛出异常AbsortPolicy
-
直接抛弃,不抛出异常DiacardPolicy
-
抛弃旧的,Discard OldestPolicy
-
由线程的创建者执行CallerRunPolicy(比如主线程)
-
-
当线程进入线程池时,首先由核心线程执行,当核心线程全部被占用时,将后来进入的线程存入到线程工作队列,当线程工作队列也满的时候,创建非核心线程。如果拒绝原则使用的是抛弃旧的,会将工作队列中的抛弃
-
public static void main(String[] args) { ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
Callable接口(创建线程)
-
public interface Callable<V>{
public V call() throws Exception;
}
-
JDK5加入,与Runnable接口类似,实现之后代表一个线程任务
-
Callable具有泛型返回值、可以声明异常
-
Callable和Runnable接口的区别
-
Callable接口中的call方法有返回值,Runnable接口中run方法没有返回值
-
Callable接口中call方法有声明异常,Runnable接口中run方法没有异常
-
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 { int sum = 0; for(int i =0;i<=100;i++){ sum =sum+i; } return sum; } }; //2将Callable对象转换为可执行任务 FutureTask<Integer> task = new FutureTask<>(callable); //3创建线程 Thread thread = new Thread(task); //4启动线程 thread.start(); //5.获取结果 Integer sum = task.get(); System.out.println("结果是:"+sum); }
-
Future和Callable配合使用
-
public static void main(String[] args) throws ExecutionException, InterruptedException { //创建线程池 ExecutorService es = Executors.newFixedThreadPool(1); //提交任务给线程池 Future:表示将要执行完任务的结果 Future<Integer> future = es.submit(new Callable<Integer>() { public Integer call() throws Exception { int sum = 0; for(int i =0;i<=100;i++){ sum =sum+i; } return sum; } }); //获取任务结果 System.out.println(future.get()); //关闭线程池 es.shutdown(); }
-
Future接口
-
概念:异步接收ExecutorService.submit()返回的状态结果,当中包含了call()的返回值
-
Future:表示将要执行完任务的结果
-
方法: V get()以阻塞形式等待Future中的异步处理结果(call()的返回值
-
案例
-
使用Future 使用两个线程,一个计算1-50,一个计算51-100,然后进行汇总统计
-
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 =sum+i; } System.out.println("计算1-50的和"+sum); 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 =sum+i; } System.out.println("计算51-100的和"+sum); return sum; } }); //获取结果 Integer sum = future1.get()+future2.get(); System.out.println("1-100的和:"+sum); //关闭线程池 es.shutdown(); } }
-
Lock接口
-
J打开加入,与synchronized比较,显示定义,结构更加灵活
-
提供更多实用性方法,功能更加强大/性能更加优越
-
常用方法
-
void lock () // 获取锁,如果锁被占用,则等待
-
Boolean trylock() //尝试获取锁(成功返回true,失败返回false,不阻塞)
-
void unlock() //释放锁
-
重入锁(Reentrant Lock)
-
ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能
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();
}
}
}
}
public static void main(String[] args) {
Ticket ticket = new Ticket();
ExecutorService es = Executors.newFixedThreadPool(4);
for(int o = 0;o<4;o++){
es.submit(ticket);
}
es.shutdown();
}
读写锁(ReentrantReadWriteLock)
-
一种支持一写多读的同步锁,读写分离,分别分配读锁和写锁
-
支持多次分配读锁,使每个读操作可以并发执行
-
互斥原则
-
写-写:互斥,阻塞
-
读-写:互斥,读阻塞写,写堵塞读
-
读-读:不互斥,不阻塞
-
在读操作远远高于写操作的环境中,可以在保证线程安全的情况下,提高运行效率
-
public class ReadWriteDemo { //创建读写锁 private ReentrantReadWriteLock rw = new ReentrantReadWriteLock(); //获取写锁 ReentrantReadWriteLock.WriteLock writeLock = rw.writeLock(); //获取读锁 ReentrantReadWriteLock.ReadLock readLock = rw.readLock(); //设置访问值 private String string; //获取属性 public String getString(){ //对属性上读锁 readLock.lock(); try { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("读取数据"+this.string); return this.string; }finally { readLock.unlock(); } } //修饰属性 public void setString(String s){ //对属性上写锁 writeLock.lock(); try{ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("写入:"+s); this.string = s; }finally { writeLock.unlock(); } } }
public class TestWriteReadLock { public static void main(String[] args) { //创建操作对象 ReadWriteDemo readWriteDemo = new ReadWriteDemo(); //创建线程池 ExecutorService es = Executors.newFixedThreadPool(20); //创建写线程 Runnable write = new Runnable() { @Override public void run() { readWriteDemo.setString("小翠"+(int)(Math.random()*100)); } }; //创建读线程 Runnable read = new Runnable() { @Override public void run() { readWriteDemo.getString(); } }; //添加写线程 for(int i = 0 ;i<2;i++){ es.submit(write); } //添加读线程 for(int i = 0 ;i<18;i++){ es.submit(read); } es.shutdown(); while (!es.isTerminated()){// 等待线程池里的线程运行结束 } } }
Condition
-
Condition接口提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式
-
Condition可以通俗的理解位条件队列。当一个线程在调用了wait方法以后,直到线程等待的某个条件为真的时候才会被唤醒
-
方法名 描述 await() 当前线程进入等待状态 signal() 唤醒一个等待线程 signalAll() 唤醒所有等待线程
使用Lock和Condition实现20次输出“ABC”
public class PrintDemo { private int num = 1;//1表示输出A 2表示输出B 3表示输出C private Lock lock = new ReentrantLock(); //创建锁 private Condition conditionA = lock.newCondition(); //创建ConditionA,存放A线程等待 private Condition conditionB = lock.newCondition(); //创建ConditionB,存放A线程等待 private Condition conditionC = lock.newCondition(); //创建ConditionA,存放A线程等待 public void printA(){ lock.lock(); try { if (num != 1) { try { conditionA.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("A"); num = 2;// x下一个输出B conditionB.signal(); }finally { lock.unlock(); } } public void printB(){ lock.lock(); try { if (num != 2) { try { conditionB.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("B"); num = 3; conditionC.signal(); }finally { lock.unlock(); } } public void printC(){ lock.lock(); try { if (num != 3) { try { conditionC.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("C"); num = 1;// x下一个输出B conditionA.signal(); }finally { lock.unlock(); } } }
public class TestPrint { public static void main(String[] args) { PrintDemo pd = new PrintDemo(); ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3,3,60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); poolExecutor.submit(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { pd.printA(); } } }); poolExecutor.submit(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { pd.printB(); } } }); poolExecutor.submit(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { pd.printC(); } } }); poolExecutor.shutdown(); } }
synchronized和Lock的区别
类别 | synchronized | Lock |
---|---|---|
存在层次 | Java的关键字,在jvm层面上 | 是一个类或接口 |
锁释放 | 1、获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
锁的获取 | 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 | Lock可以尝试获得锁,线程可以不用一直等待 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可重入、不可中断、非公平 | 可重入,可中断、可非公平或公平 |
性能 | 少量同步 | 大量同步 |
CAS算法
-
CAS:Compare And Swap(比较交换算法)
-
其实现方式时是基于硬件平台的汇编指令,是靠硬件来实现的,效率高
-
并且比较和交换时同步的
-
CAS是一种乐观锁
-
-
CSA比较交换算法,修改的方法包含三个核心参数(V,E,N)
-
V:需要更新的变量、E:预期值 N:新值
-
只有当V==E时,V=N;否则表示已经被更新过,则取消当前操作
public class TestCAS { static int e = 0; public static void main(String[] args) { CAS cas = new CAS(); ExecutorService es = Executors.newFixedThreadPool(100); for (int i = 0; i < 100; i++) { es.submit(new Runnable() { @Override public void run() { e++; int n = cas.getNum(); boolean flag = cas.compareAndSwap(e,n); System.out.println(Thread.currentThread().getName()+":"+flag+":"+cas.getNum()); } }); } es.shutdown(); } }
/** * 测试CAS算法 * V:需要更新的变量、E:预期值 N:新值 * 只有当V==E时,V=N;否则表示已经被更新过,则取消当前操作 */ public class CAS { private int num ; public synchronized int getNum() { //获取需要更新的值 return num; } public synchronized boolean compareAndSwap(int e,int n){ //n:表示旧值 e:表示新值 if(this.num == n){ this.num = e; return true; } return false; } }
Queue接口(队列)
-
Collection的子接口,表示队列FiFO(First In FirstOut)
-
常用方法:
-
抛出异常:
-
Boolean add(E e)//顺序添加一个元素(到达上限后,在添加会抛出异常)
-
E remove()//获得第一个元素并移除(如果没有元素,则抛出异常)
-
E element() //获得第一个元素但不移除(如果队列没有元素,则抛出异常)
-
-
返回特殊值(推荐使用)
-
Boolean offer(E e)//顺序添加一个元素(到达上线后,再添加则会返回false)
-
E poll()//获得第一个元素并移除(如果队列没有元素时,返回null)
-
E peek()//获得第一个元素但不移除(如果队列没有元素时,返回null)
-
-
Concurrent LinkedQueue
-
线程安全、可高效读写的队列,高并发下性能最好的队列
-
无锁、CAS比较交换算法,修改的方法包好三个核心参数(V、E、N)
-
V:需要更新的变量 E:预期值 N:新值
-
只有当V==E时,v=N;否则表示已被更新过,则取消当前操作
-
public static void main(String[] args) { ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>(); //入队 queue.offer("小翠"); queue.offer("小莲花"); queue.offer("小蜘蛛"); //出队 int size =queue.size(); for (int i = 0; i <size ; i++) { System.out.println(queue.poll()); } }
Blocking Queue接口(阻塞队列)
-
Queue的子结构,阻塞的队列,增加了两个线程状态为无限等待的方法
-
方法:
-
void put(E e )//将指定元素插入队列中,如果没有可用空间,则等待
-
E take() //获取并移除此队列头部元素,如果没有可用元素,则等待
-
-
可用于解决生产者、消费者问题。
/** * 使用阻塞队列实现生产者消费者 * Blocking Queue * ArrayBlockingQueue :数组实现阻塞队列 * LinkedBlockingQueue :链表实现阻塞队列 */ public class Demo2 { public static void main(String[] args) { //创建队列 BlockingQueue<String> blockingQueue= new LinkedBlockingDeque<>(6); //生产功能 Runnable produce = new Runnable() { @Override public void run() { for (int i = 0; i < 30; i++) { try { blockingQueue.put("面包:"+i); System.out.println(Thread.currentThread().getName()+"生产了"+i+"号面包"); } catch (InterruptedException e) { e.printStackTrace(); } } } }; //消费功能 Runnable consume = new Runnable() { @Override public void run() { for (int i = 0; i <30 ; i++) { try { String b = blockingQueue.take(); System.out.println(Thread.currentThread().getName()+"消费了"+b); } catch (InterruptedException e) { e.printStackTrace(); } } } }; ExecutorService es = Executors.newFixedThreadPool(2); es.submit(produce); es.submit(consume); es.shutdown(); while(!es.isTerminated()){} } }
多线程的三个特性
-
synchronized可以保证原子性和可见性.但不能保证有序性
-
volatile可保证可见性和禁止重排,但不能保证原子性。Lock接口简介借助了volatile关键字间接的实现了可见性和有序性
原子性
-
一个或多个操作不能被分割,要么全部执行,要么都不执行
可见性
-
多个线程访问同一个变量,一个线程修改了这个变量,别的线程能立即看到修改的值。
-
volatile关键字保证内存可见性
有序性
-
程序执行的顺序按照代码的先后顺序执行。但是处理器为了提高程序运行效率,可能会对输入的代码进行优化,他不保证程序每个语句的执行顺序和编写顺序一致,但是最终结果是一致的。使用volatile可以保证代码不被优化
总结
-
ExecutorService线程池接口,Executors工厂
-
Callable线程任务、Future异步返回值
-
Lock、ReentrantLock重入锁。ReentrantRead WriteLock读写锁
-
CopyOnWriteArrayList线程安全的Array L ist
-
CopyOnWriteSet线程安全的Set
-
ConcurrentHashMap线程安全的HashMap
-
ConcurrentLinkedQueue线程安全的Queue
-
ArrayBlockingQueue线程安全的阻塞Queue(生产者、消费者)