1、什么是JUC
回顾:Runnable接口与Callable接口 :
callable的核心是call方法,允许返回值,runnable的核心是run方法,没有返回值
callable和runnable都可以应用于executors。而thread类只支持runnable
2、进程和线程 一个进程可以包含多个线程,Java默认线程数 :2个, main 和 GC 。
对于Java而言,开启线程的方式为:Thread,Runnable,Callable
否,我们来看一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public synchronized void start () { if (threadStatus != 0 ) throw new IllegalThreadStateException (); group.add(this ); boolean started = false ; try { start0(); started = true ; } finally { try { if (!started) { group.threadStartFailed(this ); } } catch (Throwable ignore) { } } } private native void start0 () ;
并发,并行
并发:多线程操作同一资源
并行:
1 2 3 4 5 6 public class test { public static void main (String[] args) { System.out.println(Runtime.getRuntime().availableProcessors()); } }
线程有哪几个状态?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
wait/sleep区别
来自不同类
wait => Object
sleep = > Thread
锁的释放
wait释放,wait不释放
使用范围
sleep可以在任何地方使用
而wait只能用于同步代码快
是否捕获异常
wait不需要(==但是所有线程都会捕获中断异常==),而sleep必须捕获
3、Lock锁
传统synchronized
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package juc;public class ticketDemo { public static void main (String[] args) { Ticket ticket = new Ticket (); new Thread (()->{ for (int i = 0 ; i < 20 ; i++) { ticket.sale(); try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } } },"A" ).start(); new Thread (()->{ for (int i = 0 ; i < 20 ; i++) { ticket.sale(); } },"B" ).start(); } } class Ticket { private int num=20 ; public synchronized void sale () { if (num>0 ) System.out.println("剩余票数为" + (num--)+"线程为" + Thread.currentThread().getName()); } }
Lock锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package juc;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class ticketDemo2 { public static void main (String[] args) { Ticket2 ticket = new Ticket2 (); new Thread (()->{ for (int i = 0 ; i < 20 ; i++) { ticket.sale(); try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } } },"A" ).start(); new Thread (()->{ for (int i = 0 ; i < 20 ; i++) { ticket.sale(); } },"B" ).start(); } } class Ticket2 { private int num=20 ; Lock lock = new ReentrantLock (); public void sale () { lock.lock(); try { if (num > 0 ) System.out.println("剩余票数为" + (num--) + "线程为" + Thread.currentThread().getName()); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } }
==synchronized与lock锁的区别==
synchronized 内置的Java关键字,Lock是一个Java类
synchronized 无法判断锁的状态,lock可以判断是否获得锁
前者自动释放锁,lock必须手动释放!如果不释放,将导致死锁 !
synchroniezd无法获得锁将阻塞等待,lock则不会一直等待。
synchronized 可重入锁,不可中断,非公平,而Lock是可重入锁,可以判断锁,默认非公平(可修改)。
可重入锁:可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
synchronized适合锁少量的代码同步问题,lock适合锁大量的同步代码。
==锁是什么?如何判断锁是谁?==
4、生产者消费者问题 三个步骤:等待 -> 操作 -> 通知
synchronized实现方案
lock实现方案
两者的区别是什么?
synchronized通过wait方法使线程阻塞,notify方法唤醒线程
而lock则是通过await方法阻塞等待,signal唤醒,相比lock可支持定向唤醒某个线程(condition)
1 2 3 Condition con1 = lock.newCondition()Condition con2 = lock.newCondition()con1.signal();
注意: 为防止虚假唤醒,在判断时,应当使用==while==
5、八锁现象 涉及到锁的八个问题。(注意同步关系)
注意: 在所有的锁中,如果是锁的对象中的某个方法或者属性,则表示对该对象加锁。
而如果是对静态方法加锁,则表示对Class(模板)加锁,所有对静态属性或者静态方法加锁的行为都是同一把锁。
6、CopyOnWriteArrayList(C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.util.ArrayList;public class Listdemo { public static void main (String[] args) { ArrayList arrayList = new ArrayList (); for (int i = 0 ; i < 100 ; i++) { new Thread (()->{ arrayList.add("hello" ); }).start(); } System.out.println(arrayList); } }
Exception in thread “main” java.util.ConcurrentModificationException
==并发异常的解决方案:==
使用线程安全类Vector
1 List arrayList = new Vector ();
使用工具类Collections
1 List arrayList = Collections.synchronizedList(new ArrayList ());
==CopyOnWriteArrayList(写入时复制)==(读写分离)
多个线程写入 的时候,避免覆盖
1 List arrayList = new CopyOnWriteArrayList ();
看底层代码:
1 2 3 4 5 6 7 8 9 10 public boolean add (E e) { synchronized (lock) { Object[] es = getArray(); int len = es.length; es = Arrays.copyOf(es, len + 1 ); es[len] = e; setArray(es); return true ; } }
相比Vector不需要在读的时候加锁,写入时复制大大提高读取效率。(即读操作不需要进行同步)l(在开发中读比写频繁)
看一下Vector的get方法如下
1 2 3 4 5 6 public synchronized E get (int index) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException (index); return elementData(index); }
7、CopyOnWriteArraySet 1 Set arraySet = new CopyOnWriteArraySet ();
8、ConcurrentHashMap HashMap在并发下put产生异常!
1 2 3 4 5 6 7 8 9 10 11 Map map = new HashMap (); Map map = new ConcurrentHashMap (); for (int i = 0 ; i < 50 ; i++) { int finalI = i; new Thread (()->{ map.put(String.valueOf(finalI),"world" ); }).start(); } System.out.println(map); }
9、JUC常用辅助类 1. CountDownLatch 资源–
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package juc;import java.util.concurrent.CountDownLatch;public class CountDownDemo { public static void main (String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch (6 ); for (int i = 0 ; i < 6 ; i++) { int finalI = i; new Thread (()->{ countDownLatch.countDown(); }).start(); } countDownLatch.await(); System.out.println(countDownLatch.getCount()); System.out.println("close door" ); } }
2. CyclicBarrier 资源++
线程计数器(集齐7颗龙珠触发操作):指定个数线程执行完毕之后再执行操作
3. Semaphere 信号量同一时间只能有指定个线程执行(限流 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package juc;import java.util.concurrent.Semaphore;public class SemaphereDemo { public static void main (String[] args) { Semaphore semaphore = new Semaphore (3 ); for (int i = 0 ; i < 5 ; i++) { new Thread (()->{ try { semaphore.acquire(); System.out.println(Thread.currentThread().getName()+" running" ); Thread.sleep(2000 ); System.out.println(Thread.currentThread().getName()+" closed" ); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release(); } }).start(); } } }
原理: semaphore.acquire(); 获得,如果已满则等待
semaphore.release(); 释放,会将当前信号量+1,然后唤醒等待线程
作用 :多个共享资源互斥使用,并发限流,控制最大线程数。
10、读写锁ReadWriteLock
11、BlockingQueue阻塞队列(接口) 用处:多线程并发处理,线程池 。
如何使用队列 :
四组API
方式
抛出异常
有返回值,不抛出异常
阻塞等待
超时等待
添加
add
offer
put
offer
移除
remove
poll
take
poll
检测队首
element
peek
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package juc;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;public class BlockDemo { public static void main (String[] args) { test1(); } public static void test1 () { BlockingQueue blockingQueue = new ArrayBlockingQueue (2 ); System.out.println(blockingQueue.add("a" )); System.out.println(blockingQueue.add("b" )); blockingQueue.remove(); blockingQueue.remove(); blockingQueue.remove(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static void test2 () { BlockingQueue blockingQueue = new ArrayBlockingQueue (2 ); System.out.println(blockingQueue.offer("a" )); System.out.println(blockingQueue.offer("b" )); System.out.println(blockingQueue.offer("c" )); System.out.println(blockingQueue.peek()); blockingQueue.poll(); blockingQueue.poll(); System.out.println(blockingQueue.poll()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static void test3 () throws InterruptedException { BlockingQueue blockingQueue = new ArrayBlockingQueue (2 ); blockingQueue.put("a" ); blockingQueue.put("a" ); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); blockingQueue.take(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void test4 () throws InterruptedException { BlockingQueue blockingQueue = new ArrayBlockingQueue (2 ); blockingQueue.offer("a" ); blockingQueue.offer("b" ); blockingQueue.offer("c" ,2 , TimeUnit.SECONDS); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); blockingQueue.poll(2 ,TimeUnit.SECONDS); }
12、同步队列SynchronousQueue(类) 同步队列不存储元素,put了一个元素后,必须先take出来,否则无法put。
13、==线程池(重点)== 三大方法,7大参数,4种拒绝策略
池化技术:事先准备好一些资源,有需要则来拿,用完还回去。
程序运行的本质:占用系统资源!优化资源的使用!=>池化技术
线程池、连接池、内存池….
默认大小:2
线程池好处:
降低资源的消耗
提高响应速度(不需要创建和销毁等浪费时间的操作)
方便管理
==线程复用,控制最大并发数,管理线程==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package juc;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class PoolDemo { public static void main (String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); ExecutorService executorService1 = Executors.newFixedThreadPool(5 ); ExecutorService executorService2 = Executors.newCachedThreadPool(); try { for (int i = 0 ; i < 100 ; i++) { executorService1.execute(()->{ System.out.println(Thread.currentThread().getName()+"已启动!" ); }); } }catch (Exception e){ e.printStackTrace(); }finally { executorService1.shutdown(); } } }
先来看看三大方法的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static ExecutorService newSingleThreadExecutor () { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor (1 , 1 , 0L , TimeUnit.MILLISECONDS, new LinkedBlockingQueue <Runnable>())); } public static ExecutorService newFixedThreadPool (int nThreads) { return new ThreadPoolExecutor (nThreads, nThreads, 0L , TimeUnit.MILLISECONDS, new LinkedBlockingQueue <Runnable>()); } public static ExecutorService newCachedThreadPool () { return new ThreadPoolExecutor (0 , Integer.MAX_VALUE, 60L , TimeUnit.SECONDS, new SynchronousQueue <Runnable>()); }
本质上:上述三种方法底层都是调用==ThreadPoolExecutor==方法 !
因此,我们来看看ThreadPoolExecutor方法:
1 2 3 4 5 6 7 8 public ThreadPoolExecutor (int corePoolSize, //核心线程池大小(当前可用线程) int maximumPoolSize, //最大线程池大小 long keepAliveTime, //存活时间(线程最大等待时间) TimeUnit unit, //存活时间单位 BlockingQueue<Runnable> workQueue) { this (corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
阿里开发守则之并发:
接下来我们看一下==四大拒绝策略==:
1 2 3 4 new ThreadPoolExecutor .AbortPolicy());new ThreadPoolExecutor ().DiscardPolicy()); new ThreadPoolExecutor ().DiscardOldestPolicy()); new ThreadPoolExecutor ().CallerRunsPolicy());
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package juc;import java.util.concurrent.*;public class PoolDemo { public static void main (String[] args) { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor ( 2 , 5 , 3 , TimeUnit.SECONDS, new ArrayBlockingQueue (3 ), Executors.defaultThreadFactory(), new ThreadPoolExecutor .AbortPolicy()); try { for (int i = 0 ; i < 9 ; i++) { threadPoolExecutor.execute(()->{ System.out.println(Thread.currentThread().getName()+"已启动!" ); }); } }catch (Exception e){ e.printStackTrace(); }finally { threadPoolExecutor.shutdown(); } } }
==最大的线程池大小maximumPoolSize如何设置?==
CPU密集型与IO密集型
1 2 3 4 5 maximumPoolSize 1. CPU密集型,=CPU核数 2. IO密集型,>IO任务线程数 Runtime.getRuntime().availableProcessors();
14、四大函数式接口(必须掌握)(java.util.function) 新时代程序员必会:lambda表达式、链式编程、函数式接口、Stream流式计算
(旧时代程序员:泛型、枚举、反射 )
函数式接口:只有一个方法的接口
作用:简化编程模型,在新版本的框架底层大量应用
1 2 3 4 @FunctionalInterface public interface Runnable { public abstract void run () ; }
==函数型接口==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package juc;import java.util.function.Function;public class InterfaceDemo { public static void main (String[] args) { Function function = new Function <String,String>(){ @Override public String apply (String o) { return o; } }; Function function1 = str->{ return str; }; System.out.println(function.apply("hello" )); System.out.println(function1.apply("world" )); } }
==断定型接口==
1 2 3 @FunctionalInterface public interface Predicate <T> { boolean test (T t) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package juc;import java.util.function.Predicate;public class PredictedDemo { public static void main (String[] args) { Predicate<String> predicate = new Predicate <>() { @Override public boolean test (String s) { return s.isEmpty(); } }; Predicate<String> predicate1 = str->{ return str.isEmpty(); }; System.out.println(predicate.test("hello" )); System.out.println(predicate1.test("hello" )); } }
==消费型接口==
1 2 3 @FunctionalInterface public interface Consumer <T> { void accept (T t) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package juc;import java.util.function.Consumer;public class ConsumerDemo { public static void main (String[] args) { Consumer<String> consumer = new Consumer <String>() { @Override public void accept (String s) { System.out.println(s); } }; Consumer<String> consumer1 = str->{ System.out.println(str); }; consumer.accept("hello" ); consumer1.accept("world" ); } }
==供给型接口==
1 2 3 4 @FunctionalInterface public interface Supplier <T> { T get () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package juc;import java.util.function.Supplier;public class SupplierDemo { public static void main (String[] args) { Supplier<String> stringSupplier = new Supplier <String>(){ @Override public String get () { return "hello" ; } }; Supplier<String> supplier = ()->{ return "world" ; }; System.out.println(stringSupplier.get()); System.out.println(supplier.get()); } }
15、JMM Java内存模型,是不存在的东西,只是概念和约定!
例如:
线程解锁前,必须把共享变量立刻 刷回主存。
线程加锁前,必须读取主存中的最新值到工内存。
加锁和解锁是同一把锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package juc;public class JMMDemo { private static int num=0 ; public static void main (String[] args) { new Thread (()->{ while (num ==0 ){ } }).start(); num=1 ; System.out.println(num); System.out.println(Thread.activeCount()); } }
16、Volatile 三大特性 :
17、单例模式
1 2 3 4 5 6 7 8 9 10 public class Hungry { private Hungry () { } private final static Hungry HUNGRY = new Hungry (); public static Hungry getInstance () { return HUNGRY; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package juc.singleDemo; public class LazyMan { private LazyMan () { } private volatile static LazyMan lazyMan; public static LazyMan getInstance () { if (lazyMan==null ){ synchronized (LazyMan.class){ if (lazyMan==null ){ lazyMan= new LazyMan (); } } } return lazyMan; } }
18、CAS(与乐观锁原理相同) compareAndSet:比较当前工作内存中的值和主内存中的值,如果是期望值那么执行操作否则一直循环。
缺点:
1、循环耗时
2、一次性只能保证一个共享变量的原子性
3、ABA问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package juc.singleDemo;import java.util.concurrent.atomic.AtomicInteger;public class CASDemmo { public static void main (String[] args) { AtomicInteger atomicInteger = new AtomicInteger (2022 ); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.getAndIncrement()); System.out.println(atomicInteger.compareAndSet(2022 , 2023 )); System.out.println(atomicInteger.get()); } }
**==CAS的ABA问题==**:如果改变的是基本类型则没影响,但是如果改变的是引用类型,由于Java是值传递,虽然传递的值不变,但可能引用的对象已经发生改变。
如何解决?原子引用!(带版本号的原子操作)
19、java的Integer比较
**==注意==**:==所有相同类型的包装类对象之间的值比较,应该使用equals方法比较。==–来自阿里巴巴java开发手册。
例:
1 2 3 4 5 6 7 8 9 10 11 public static void main (String[] args) throws Exception{ Integer a = -121 ; Integer b = -121 ; Integer c = 345 ; Integer d = 345 ; System.out.println(a.equals(b)); System.out.println(a == b); System.out.println(c.equals(d)); System.out.println(c == d); }
打印结果为:
c和d的值都是345,为什么用==和equals比较结果不一样呢?
我们看下对象信息,注意对象地址:
Integer值的比较有个坑:对于Integer var = ?,==在-128至127范围内的赋值, Integer 对象是在IntegerCache.cache 产生,会复用已有对象==,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生 ,并不会复用已有对象;所以,在上面,我们的c和d两个,虽然值是一样的,但是地址不一样。
这是一个大坑,很多人会在项目中使用==来比较Integer!强烈建议,必须使用equals来比较!
20、可重入锁(递归锁)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package juc.Lock;public class Demo01 { public static void main (String[] args) { Phone phone = new Phone (); new Thread (()->{ phone.call(); },"A" ).start(); new Thread (()->{ phone.call(); },"B" ).start(); } } class Phone { public synchronized void call () { System.out.println(Thread.currentThread().getName()+"call" ); send(); } public synchronized void send () { System.out.println(Thread.currentThread().getName()+"send" ); } } 输出: Acall Asend Bcall Bsend