JUC
概念
JUC并发编程就是多线程的进阶版,主要包含三个类java.util.concurrent、 java.util.concurrent.automic、 java.util.concurrent.locks
回顾
进程和线程
进程就是应用程序在内存中分配的空间,也就是正在运行的程序 ,各个进程之间互不干扰。同时进程保存着程序每一个时刻运行的状态。
**一个线程执行一个子任务,这样一个进程就包含了多个线程,每个线程负责一个单独的子任务。**总之,进程和线程的提出极大的提高了操作系统的性能。进程让操作系统的并发性成为了可能,而线程让进程的内部并发成为了可能。
当然区别还是有的,他们本质的区别就是是否单独占有内存地址空间及其它系统资源(比如I/O) ,另外一个重要区别是,进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位 ,即CPU分配时间的单位 。
线程的几个状态
new 新建
runnable 运行
blocked 阻塞
waiting 等待
timed_waiting 超时等待
terminated终止
wait/sleep区别
Synchronzied 和Lock区别
Synchronized 内置的Java关键字 Lock是一个Java类
S无法判断锁的状态 Lock可以判断是否获取到了锁
S会自动释放锁 Lock必须手动释放 如果不释放就会死锁
S 线程1 (获得锁)线程2 (等待),lock锁下的线程就不一定一直等
S可重入锁 不可以中断,非公平,Lock 可重入锁
S适合锁少量的代码同步问题 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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class A { public static void main (String[] args) { data data = new data(); new Thread(()->{ for (int i = 0 ; i < 10 ; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A" ).start(); new Thread(()->{ for (int i = 0 ; i < 10 ; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B" ).start(); } }class data { private int number = 0 ; public synchronized void increment () throws InterruptedException { if (number!=0 ){ this .wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>" +number); this .notifyAll(); } public synchronized void decrement () throws InterruptedException { if (number==0 ){ this .wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>" +number); this .notifyAll(); } }
运行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0
若不是两个线程,而是多个线程,就会出现以下的问题
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 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 C=>1 A=>2 C=>3 B=>2 B=>1 B=>0 C=>1 A=>2 C=>3 A=>4 C=>5 D=>4 D=>3 C=>4 D=>3 D=>2 D=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0
问题分析
结果的出现原因是资源类的if判断,可能导致两个加的方法同时进来,造成的虚假唤醒问题
1 2 3 4 5 6 7 8 9 10 11 12 public synchronized void increment () throws InterruptedException { while (number!=0 ){ this .wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>" +number); this .notifyAll(); }
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 package PC;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class B { public static void main (String[] args) { data2 data = new data2(); new Thread(()->{ for (int i = 0 ; i < 10 ; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A" ).start(); new Thread(()->{ for (int i = 0 ; i < 10 ; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B" ).start(); new Thread(()->{ for (int i = 0 ; i < 10 ; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C" ).start(); new Thread(()->{ for (int i = 0 ; i < 10 ; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"D" ).start(); } }class data2 { private int number = 0 ; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void increment () throws InterruptedException { lock.lock(); try { while (number!=0 ){ condition.await(); } number++; System.out.println(Thread.currentThread().getName()+"=>" +number); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void decrement () throws InterruptedException { lock.lock(); try { while (number==0 ){ condition.await(); } number--; System.out.println(Thread.currentThread().getName()+"=>" +number); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
八锁问题
资源类phone可以发短信和打电话
1.创建一个Phone实例多线程调用两个方法,问哪一个先执行?
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 public class Demo1 { public static void main (String[] args) { phone phone = new phone(); new Thread(()->{ phone.sendMessage(); }).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone.call(); }).start(); } }class phone { public synchronized void sendMessage () { System.out.println("发短信" ); } public synchronized void call () { System.out.println("打电话" ); } } 发短信 打电话
分析:synchronized关键字是对对象上锁,谁先拿到锁,谁就先执行
2.创建一个Phone实例多线程调用两个方法,其中第一个线程调用的方法中加延迟,问哪一个先执行?
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 public class Demo2 { public static void main (String[] args) { phone phone = new phone(); new Thread(()->{ phone.sendMessage(); }).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone.call(); }).start(); } }class phone { public synchronized void sendMessage () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信" ); } public synchronized void call () { System.out.println("打电话" ); } } 发短信 打电话
分析:原理同上,谁先拿到锁,谁先执行
3.创建一个Phone实例多线程调用两个方法,其中一个是普通方法,而且该线程位置靠后,问哪一个先执行?
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 public class Demo3 { public static void main (String[] args) { phone phone = new phone(); new Thread(()->{ phone.sendMessage(); }).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone.watchvideo(); }).start(); } }class phone { public synchronized void sendMessage () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信" ); } public synchronized void call () { System.out.println("打电话" ); } public void watchvideo () { System.out.println("看视频" ); } } 看视频 发短信
分析:watchvideo()
为普通方法,当然是不受锁的影响,因为 sendMessage()
方法体 中有延迟语句,因此会后输出,其实 抛去延迟来说,两个输出结果为不一定
4.创建两个Phone实例多线程调用两个方法,问哪一个先执行?
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 public class Demo4 { public static void main (String[] args) { phone phone1 = new phone(); phone phone2 = new phone(); new Thread(()->{ phone1.sendMessage(); }).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); }).start(); } }class phone { public synchronized void sendMessage () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信" ); } public synchronized void call () { System.out.println("打电话" ); } } 打电话 发短信
分析:由于是两个对象,因此锁对象之间没有干扰,因为延时,所以打电话先输出,而且可以证明锁住的是实例对象,多个之间并不干扰
5.创建一个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?
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 public class Demo5 { public static void main (String[] args) { phone phone1 = new phone(); new Thread(()->{ phone1.sendMessage(); }).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone1.call(); }).start(); } }class phone { public synchronized static void sendMessage () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信" ); } public synchronized static void call () { System.out.println("打电话" ); } } 发短信 打电话
分析:发短信 在前面的原因是 synchronized 加 静态方法 锁的是 Class ,Phone.Class
只有单个。因此第二个线程需要 等待第一个线程释放Class锁才能执行。
6.创建两个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?
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 public class Demo6 { public static void main (String[] args) { phone phone1 = new phone(); phone phone2 = new phone(); new Thread(()->{ phone1.sendMessage(); }).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); }).start(); } }class phone { public synchronized static void sendMessage () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信" ); } public synchronized static void call () { System.out.println("打电话" ); } } 发短信 打电话
分析:虽然是两个实例对象,但是锁住的是同一个phone.class,原理和5是一样的
7.创建一个Phone实例多线程调用两个方法,其中一个有static修饰,而且调用线程在前面,问哪一个先执行?
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 public class Demo7 { public static void main (String[] args) { phone phone1 = new phone(); new Thread(()->{ phone1.sendMessage(); }).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone1.call(); }).start(); } }class phone { public synchronized static void sendMessage () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信" ); } public synchronized void call () { System.out.println("打电话" ); } } 打电话 发短信
分析:打电话 先输出的原因是 Phone7 实例 和 Phone7.Class 分别被锁,两个线程之间并无影响,因为线程延迟的原因。再次 证明 synchronized 锁的是 类实例即对象 、synchronized 加 静态方法 锁的是 Class
8.创建两个Phone实例多线程调用两个方法,其中一个有static修饰,而且调用线程在前面,问哪一个先执行?
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 public class Demo8 { public static void main (String[] args) { phone phone1 = new phone(); phone phone2 = new phone(); new Thread(()->{ phone1.sendMessage(); }).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); }).start(); } }class phone { public synchronized static void sendMessage () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信" ); } public synchronized void call () { System.out.println("打电话" ); } } 打电话 发短信
分析:原理同7两个线程分别锁的是 Phone8.Class 和 Phone8实例,因为线程延迟,打电话 才会优先输出。
集合的线程安全
概述
线程安全集合:多线程并发的基础上修改一个集合,不会发生ConcurrentModificationException并发修改异常
CopyOnWriteArrayList是线程安全的集合,ArrayList是线程不安全的集合,Vector是线程安全的集合
ArrayList的线程安全
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 public class demo1_arraylist { public static void main (String[] args) { ArrayList<String> list = new ArrayList<>(); for (int i = 0 ; i < 10 ; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0 ,5 )); System.out.println(list); },String.valueOf(i)).start(); } } } [null , 0c293, 7509b] [null , 0c293, 7509b, 50105 , a4b9f, cd3b1, 4d7bb, 89969 , dd07a, de64e] [null , 0c293, 7509b, 50105 , a4b9f, cd3b1, 4d7bb, 89969 ] [null , 0c293, 7509b, 50105 , a4b9f, cd3b1] [null , 0c293, 7509b, 50105 , a4b9f, cd3b1, 4d7bb] [null , 0c293, 7509b, 50105 , a4b9f] [null , 0c293, 7509b, 50105 ] [null , 0c293, 7509b] [null , 0c293, 7509b] Exception in thread "8" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911 ) at java.util.ArrayList$Itr.next(ArrayList.java:861 ) at java.util.AbstractCollection.toString(AbstractCollection.java:461 ) at java.lang.String.valueOf(String.java:2994 ) at java.io.PrintStream.println(PrintStream.java:821 ) at listsecurity.demo1_arraylist.lambda$main$0 (demo1_arraylist.java:20 ) at java.lang.Thread.run(Thread.java:748 )
如何解决呢?
使用Vector:查看Vector的add()方法,发现相比于ArrayList的add()方法前面加了synchronized修饰,因此是线程安全的
使用Collectiobns.synchronizedlist(new ArrayList):Collections集合工具类提供一些列线程安全的集合构造方法。
使用 JUC包下的 CopyOnWriteArrayList集合:一种线程安全的集合,CopyOnWrite的意思是写入时复制,是一种计算机程序设计优化策略,解决多线程写入覆盖问题
1 2 3 4 List<String> list = new Vector<>(); List<String> list = Collections.synchronizedList(new ArrayList<>()); List<String> list = new CopyOnWriteArrayList<>();
Set,Map集合的线程安全
对比ArrayList,Set同样线程并不安全,可以通过上面类似的解决
1 2 Set<String> set = Collections.synchronizedSet(new HashSet<>()); Set<String> set = new CopyOnWriteArraySet<>();
实际上HashSet的底层是HashMap方法
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 public HashSet () { map = new HashMap<>(); } public boolean add (E e) { return map.put(e, PRESENT)==null ; }
因此Map也是不安全的,解决方法同样有两种
1 2 Map<String,String> map = Collections.synchronizedMap(new HashMap<>()) Map<String,String> map = new ConcurrentHashMap<>();
读写锁
ReadWriteLock维护一对关联的locks ,一个用于只读操作,一个用于写入。 read lock可以由多个阅读器线程同时进行,只要没有作者。 write 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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 public class demo1_rwl { public static void main (String[] args) { MycacheReadWriteLock cache = new MycacheReadWriteLock(); for (int i = 0 ; i < 4 ; i++) { final int temp = i; new Thread(()->{ cache.save(temp + "" ,temp + "" ); },String.valueOf(i)).start(); } for (int i = 0 ; i < 4 ; i++) { final int temp = i; new Thread(()->{ cache.get(temp + "" ); },String.valueOf(i)).start(); } } }class MycacheReadWriteLock { private volatile Map<String,String> cache = new HashMap<>(); ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); public void save (String key,String value) { reentrantReadWriteLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + "===>开始写入数据" ); cache.put(key,value); System.out.println(Thread.currentThread().getName() + "===>写入成功了" ); } catch (Exception e) { e.printStackTrace(); } finally { reentrantReadWriteLock.writeLock().unlock(); } } public void get (String key) { reentrantReadWriteLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + "===>开始获取数据" ); cache.get(key); System.out.println(Thread.currentThread().getName() + "===>获取成功" ); } catch (Exception e) { e.printStackTrace(); } finally { reentrantReadWriteLock.readLock().unlock(); } } }0 ===>开始写入数据0 ===>写入成功了2 ===>开始写入数据2 ===>写入成功了3 ===>开始写入数据3 ===>写入成功了1 ===>开始写入数据1 ===>写入成功了1 ===>开始获取数据1 ===>获取成功0 ===>开始获取数据2 ===>开始获取数据3 ===>开始获取数据3 ===>获取成功2 ===>获取成功0 ===>获取成功
阻塞队列
BlockingQueue是Java util.concurrent包下重要的数据结构,区别于普通的队列,BlockingQueue提供了线程安全的队列访问方式 ,并发包下很多高级同步类的实现都是基于BlockingQueue实现的。阻塞队列简单的来说就是你只管往里面存、取就行,而不用担心多线程环境下存、取共享变量的线程安全问题。
阻塞指两种状态
入队:如果队列此时是满的,需要阻塞等待
取出:如果队列是空的,需要阻塞等待生产
阻塞队列的操作方法
提供了四组不同的方法用于插入,移除,检查元素
方法\处理方式
抛出异常
返回特殊值
一直阻塞
超时退出
插入方法
add(e)
offer(e)
put(e)
offer(e,time,unit)
移除方法
remove()
poll()
take()
poll(time,unit)
检查方法
element()
peek()
-
-
抛出异常:如果试图的操作无法立即执行,抛异常。当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException(“Queue full”)异常。当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常 。
返回特殊值:如果试图的操作无法立即执行,返回一个特殊值,通常是true / false。
一直阻塞:如果试图的操作无法立即执行,则一直阻塞或者响应中断。
超时退出:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功,通常是 true / false。
注意之处
不能往阻塞队列中插入null,会抛出空指针异常。
可以访问阻塞队列中的任意元素,调用remove(o)可以将队列之中的特定对象移除,但并不高效,尽量避免使用。
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 public class demo1_blockingqueue { public static void main (String[] args) throws InterruptedException { test4(); } public static void test1 () { ArrayBlockingQueue queue = new ArrayBlockingQueue(3 ); queue.add("william" ); queue.add("will" ); queue.add("iam" ); System.out.println(queue); System.out.println(queue.remove()); System.out.println(queue.element()); System.out.println(queue.remove()); System.out.println(queue.remove()); System.out.println(queue); } public static void test2 () { ArrayBlockingQueue queue = new ArrayBlockingQueue(3 ); queue.offer("william" ); queue.offer("will" ); queue.offer("iam" ); System.out.println(queue.offer("队列已满,入队返回异常" )); System.out.println(queue.poll()); System.out.println(queue.poll()); System.out.println(queue.poll()); System.out.println(queue.poll()); } public static void test3 () throws InterruptedException { ArrayBlockingQueue queue = new ArrayBlockingQueue(3 ); queue.put("william" ); queue.put("will" ); queue.put("iam" ); queue.put("队列已满,一直阻塞等待" ); System.out.println(queue.take()); System.out.println(queue.take()); System.out.println(queue.take()); System.out.println(queue.take()); } public static void test4 () throws InterruptedException { ArrayBlockingQueue queue = new ArrayBlockingQueue(3 ); queue.put("william" ); queue.put("will" ); queue.put("iam" ); queue.offer("队列已满,入队会超时等待" ,3 , TimeUnit.SECONDS); System.out.println(queue.take()); System.out.println(queue.take()); System.out.println(queue.take()); System.out.println(queue.poll(1 , TimeUnit.SECONDS)); } } [william, will, iam] william will will iam []false william will iamnull william will iamnull
同步队列
SynchronousQueue 这个队列比较特殊,没有任何内部容量 ,甚至连一个队列的容量都没有。并且每个 put 必须等待一个 take,反之亦然。
需要区别容量为1的ArrayBlockingQueue、LinkedBlockingQueue。
以下方法的返回值,可以帮助理解这个队列:
iterator() 永远返回空,因为里面没有东西
peek() 永远返回null
put() 往queue放进去一个element以后就一直wait直到有其他thread进来把这个element取走。
offer() 往queue里放一个element后立即返回,如果碰巧这个element被另一个thread取走了,offer方法返回true,认为offer成功;否则返回false。
take() 取出并且remove掉queue里的element,取不到东西他会一直等。
poll() 取出并且remove掉queue里的element,只有到碰巧另外一个线程正在往queue里offer数据或者put数据的时候,该方法才会取到东西。否则立即返回null。
isEmpty() 永远返回true
remove()&removeAll() 永远返回false
线程池
池化技术就是事先准备好一些资源,有人需要用 久去那里拿,用完再还回去,
线程池的好处
线程复用,可以控制最大并发数,管理线程
降低资源的消耗
提高响应的速度
方便管理
使用Executors创建线程池
Executors创建的线程池实例常用的三个方法
newSingleThreadExecutor()创建一个使用从无界队列运行的单个工作线程的执行程序。
newFixedThreadPool(int nThreads)创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
newCachedThreadPool()创建一个根据需要创建新线程的线程池核心线程数为0,全部线程可回收,在可用时将重新使用以前构造的线程。
newScheduThreadPool()创建一个定时任务线程池
线程池关闭的方法pool.shutdown()
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 public class Demo1_ThreadPool { public static void main (String[] args) { ExecutorService pool1 = Executors.newSingleThreadExecutor(); ExecutorService pool2 = Executors.newFixedThreadPool(6 ); ExecutorService pool3 = Executors.newCachedThreadPool(); try { for (int i = 0 ; i < 10 ; i++) { pool1.execute(()->{ System.out.println(Thread.currentThread().getName()); }); } } finally { pool1.shutdown(); } try { for (int i = 0 ; i < 10 ; i++) { pool2.execute(()->{ System.out.println(Thread.currentThread().getName()); }); } } finally { pool1.shutdown(); } try { for (int i = 0 ; i < 10 ; i++) { pool3.execute(()->{ System.out.println(Thread.currentThread().getName()); }); } } finally { pool1.shutdown(); } } } pool-1 -thread-1 pool-1 -thread-1 pool-1 -thread-1 pool-1 -thread-1 pool-1 -thread-1 pool-1 -thread-1 pool-1 -thread-1 pool-1 -thread-1 pool-1 -thread-1 pool-1 -thread-1 pool-2 -thread-1 pool-2 -thread-2 pool-2 -thread-3 pool-2 -thread-4 pool-2 -thread-3 pool-2 -thread-5 pool-2 -thread-6 pool-2 -thread-1 pool-2 -thread-2 pool-2 -thread-4 pool-3 -thread-1 pool-3 -thread-3 pool-3 -thread-2 pool-3 -thread-1 pool-3 -thread-4 pool-3 -thread-5 pool-3 -thread-6 pool-3 -thread-7 pool-3 -thread-8 pool-3 -thread-3
查看源码发现其实他们底层都是ThreadPoolExecutor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static ExecutorService newSingleThreadExecutor (ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1 , 1 , 0L , TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
我们再来看一下ThreadPoolExecutor
1 2 3 4 5 6 7 8 9 10 public ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this (corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); }
七大参数
核心线程数 :@param corePoolSize 池中要保留的线程数,即使它们处于空闲状态,除非设置了 {@code allowCoreThreadTimeOut}
最大线程数:@param maximumPoolSize 池中允许的最大线程数
空闲存活时间:@param keepAliveTime 当线程数较大时与核心相比,这是多余空闲线程在终止之前等待新任务的最长时间。
超时单位:@param unit {@code keepAliveTime} 参数的时间单位
阻塞队列: @param workQueue 用于在执行任务之前保存任务的队列。该队列将仅保存由 {@code execute} 方法提交的 {@code Runnable} 任务。
线程工厂 :@param threadFactory 执行程序创建新线程时使用的工厂
拒接策略:@param handler 执行被阻塞时使用的处理程序,因为达到了线程边界和队列容量 @throws IllegalArgumentException 如果以下情况之一成立:
{@code corePoolSize < 0}
{@code keepAliveTime < 0}
{@code maximumPoolSize <= 0}
{@code maximumPoolSize < corePoolSize}
@throws NullPointerException if {@code workQueue} 或 {@code threadFactory}或 {@code handler} 为空
四种拒绝策略
RejectedExecutionHandler 是拒绝策略的接口,有四个实现类
AbortPolicy : 默认拒绝策略,抛出异常
DiscardPolicy:不会抛出异常,会丢掉任务
DiscardOldestPolicy:不会抛出异常,会和最早的线程尝试竞争资源,竞争不一定会成功,失败就丢调任务
CallerRunsPolicy:从哪个线程来的,哪个线程处理,即main线程处理
最大线程数可以通过两种方式设置
四大函数式接口
都可以使用lamda表达式简化,函数式接口 只有一个方法的接口
java.util.Function
包下的 有基本的四大函数式接口
Function 函数式接口
Predicate 断定型接口
Consumer 消费型接口 只有输入没有返回值
Supplier 供给型接口 只有输出没有参数
Function接口
apply方法接受一个参数t,返回一个参数R
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 @FunctionalInterface public interface Function <T , R > { R apply (T t) ;
例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class demo1_function { public static void main (String[] args) { Function<String, String> function = new Function<String, String>() { @Override public String apply (String s) { return s; } }; Function function1 = (str)->{return str;}; Function function2 = str->{return str;}; System.out.println(function.apply("niubi" )); System.out.println(function1.apply("niubi" )); System.out.println(function2.apply("niubi" )); } }
Predicate接口
断定型接口,根据传入的数据,只返回boolean值
can can 源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @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 21 22 23 public class Demo2_predicate { public static void main (String[] args) { Predicate<String> predicate = new Predicate<String>() { @Override public boolean test (String s) { if (!s.isEmpty()){ return true ; } return false ; } }; Predicate<String> predicate1 = s -> { if (!s.isEmpty()){ return true ; } return false ; }; System.out.println(predicate.test("william" )); System.out.println(predicate1.test("william" )); } }
Consumer接口
消费者型接口,只有输入参数,没有返回值
can can 源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @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 public class Demo3_consumer { 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 = (s)->{ System.out.println("一共消费了" + s + "元" ); }; consumer.accept("20" ); consumer.accept("30" ); } }
Supplier接口
没有输入参数,只有 返回值
can can 源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @FunctionalInterface public interface Supplier <T > { T get () ; }
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Demo4_supplier { public static void main (String[] args) { Supplier<String> supplier = new Supplier<String>() { @Override public String get () { return "niuBi" ; } }; Supplier<String> supplier1 = ()->{return "niuBi" ;}; System.out.println(supplier.get()); System.out.println(supplier1.get()); } }
Stream流式计算
什么是Stream流式计算
常用的集合是为了存储数集,而对于集合数据的一些处理(像筛选集合数据等)可以使用Stream流来处理
java.util.tream包下的Stream接口 支持顺序和并行聚合操作的一系列元素
Stream流可以结合四大函数式接口进行数据处理(方法的参数支持函数式接口)
使用
集合.stream() 可以将集合对象 转为 流对象,调用流对象的一些方法进行数据操作
常用方法
filter(Predicate<? super T> predicate) 返回由与此给定谓词匹配的此流的元素组成的流。
count() 返回此流中的元素数。
forEach(Consumer<? super T> action) 对此流的每个元素执行操作。
sorted(Comparator<? super T> comparator) 返回由该流的元素组成的流,根据提供的 Comparator进行排序。
map(Function<? super T,? extends R> mapper)返回由给定函数应用于此流的元素的结果组成的流。
sorted(Comparator<? super T> comparator)返回由该流的元素组成的流,根据提供的 Comparator进行排序。
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 public class Demo_stream { public static void main (String[] args) { User user1 = new User("william1" , 18 , 10000 ); User user2 = new User("william2" , 19 , 11000 ); User user3 = new User("william3" , 20 , 12000 ); User user4 = new User("william4" , 21 , 13000 ); User user5 = new User("william5" , 22 , 14000 ); User user6 = new User("william6" , 23 , 15000 ); List<User> users = Arrays.asList(user1, user2, user3, user4, user5, user6); users.stream().forEach((user)->{ System.out.println(user); }); users.stream().close(); System.out.println("---------我是分割线--------" ); users.stream() .filter((user) -> {return user.getSalary() > 12000 ;}) .filter((user) -> {return user.getAge() > 20 ;}) .forEach((user)->{ System.out.println(user); }); users.stream().close(); users.stream() .filter((user)-> user.getSalary() < 20000 ) .map((user)-> user.getSalary() + 2000 ) .sorted((u1,u2)->{return u1.compareTo(u2);}) .forEach((user)->{ System.out.println(user); }); users.stream().close(); } }class User { private String name; private int age; private int salary; public User (String name, int age, int salary) { this .name = name; this .age = age; this .salary = salary; } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } public int getSalary () { return salary; } public void setSalary (int salary) { this .salary = salary; } @Override public String toString () { return "User{" + "name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}' ; } } User{name='william1' , age=18 , salary=10000 } User{name='william2' , age=19 , salary=11000 } User{name='william3' , age=20 , salary=12000 } User{name='william4' , age=21 , salary=13000 } User{name='william5' , age=22 , salary=14000 } User{name='william6' , age=23 , salary=15000 } ---------我是分割线-------- User{name='william4' , age=21 , salary=13000 } User{name='william5' , age=22 , salary=14000 } User{name='william6' , age=23 , salary=15000 }12000 13000 14000 15000 16000 17000 进程已结束,退出代码为 0
Fork/Join分支合并
ork/Join框架是一个实现了ExecutorService接口的多线程处理器,它专为那些可以通过递归分解成更细小的任务而设计,最大化的利用多核处理器来提高应用程序的性能。
与其他ExecutorService相关的实现相同的是,Fork/Join框架会将任务分配给线程池中的线程。而与之不同的是,Fork/Join框架在执行任务时使用了工作窃取算法 。
fork 在英文里有分叉的意思,join 在英文里连接、结合的意思。顾名思义,fork就是要使一个大任务分解成若干个小任务,而join就是最后将各个小任务的结果结合起来得到大任务的结果。
感觉就是分治算法的思想
工作窃取算法指的是在多线程执行不同任务队列的过程中,某个线程执行完自己队列的任务后从其他线程的任务队列里窃取任务来执行。
工作窃取流程如下图所示:
异步回调
有返回结果
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 60 61 public class Demo1 { public static void main (String[] args) throws ExecutionException, InterruptedException { test2(); } static void test1 () throws ExecutionException, InterruptedException { CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{ try { TimeUnit.SECONDS.sleep(3 ); System.out.println("异步任务执行了!!" ); } catch (Exception e) { e.printStackTrace(); } finally { } }); System.out.println("willaim1" ); completableFuture.get(); } static void test2 () throws ExecutionException, InterruptedException { CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{ int num = 10 / 0 ; try { TimeUnit.SECONDS.sleep(3 ); return 1 ; } catch (Exception e) { e.printStackTrace(); return -1 ; } }); System.out.println("william2" ); completableFuture.whenCompleteAsync((t,u)->{ System.out.println(t+"=====> " + t); System.out.println(u+"=====> " + u); System.out.println("有返回值的异步执行了!" ); }).exceptionally((e)->{ e.printStackTrace(); return 404 ; }).get(); } }
JMM
JMM:Java内存模型,是一个概念,不是实际存在的东西
线程解锁前,必须把 共享变量 立刻 刷新为主存(每个线程都有自己一块内存称为 工作内存,操作的变量是自己内存块的变量,但是实际存在的位置是主存,因此每次操作完之后需要 更新 主存)
线程加锁前:必须读取主存中的最新值搭配线程工作内存中
JMM的八种操作
read、load
use、assign
write、store
lock、unlock
lock(锁定),作用于主内存中的变量,把变量标识为线程独占的状态。
read(读取),作用于主内存的变量,把变量的值从主内存传输到线程的工作内存中,以便下一步的load操作使用。
load(加载),作用于工作内存的变量,把read操作主存的变量放入到工作内存的变量副本中。
use(使用),作用于工作内存的变量,把工作内存中的变量传输到执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
assign(赋值),作用于工作内存的变量,它把一个从执行引擎中接受到的值赋值给工作内存的变量副本中,每当虚拟机遇到一个给变量赋值的字节码指令时将会执行这个操作。
store(存储),作用于工作内存的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用。
write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
volatile
保证可见性
内存可见性,指的是线程之间的可见性,当一个线程修改了共享变量时,另一个线程可以读取到这个修改后的值 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Demo1_volatile { private static Boolean flag = true ; public static void main (String[] args) { new Thread(()->{ while (flag){ } },"其他线程" ).start(); try { TimeUnit.SECONDS.sleep(3 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("主线程修改了flag,但是另外一个线程工作空间的flag仍然是true." ); flag = true ; } }
若不加volatile 该程序会一直死循环,因为内存不可见,可以通过修改flag为private static volatile Boolean flag = true;
即加上volatile保证内存可见性解决问题
不保证原子性
原子性即是不可分割性,通俗的讲就是线程A在执行的时候不可打扰 要么成功 要么失败
可以使用原子类来解决原子性的问题 比如原子性的integer类
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 public class VolatileDemo2 { private static volatile int num = 0 ; public static void main (String[] args) { for (int i = 0 ; i < 10 ; i++) { new Thread(()->{ for (int i1 = 0 ; i1 < 1000 ; i1++) { add(); } }).start(); } while (Thread.activeCount() > 2 ){ Thread.yield(); } System.out.println("正常结果为100,输出结果为===》" + num); } public static void add () { num++; } }
可以通过加上synchronzied关键字或者Lock锁来保证原子性
或者另外一种方式 使用原子类来解决
创建 可原子更新的int 值对象
private static volatile AtomicInteger num = new AtomicInteger(0);
该对象有方法+1,原理是调用Unsafe类的方法,底层使用的CAS
num.getAndIncrement();
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 public class AtomicDemo3 { private static volatile AtomicInteger num = new AtomicInteger(0 ); public static void main (String[] args) { for (int i = 0 ; i < 10 ; i++) { new Thread(()->{ for (int i1 = 0 ; i1 < 1000 ; i1++) { num.getAndIncrement(); } }).start(); } while (Thread.activeCount() > 2 ){ Thread.yield(); } System.out.println("正常结果为10000,输出结果为===》" + num); } }
禁止指令重排
指令重排 ,简单的来说就是计算机将你的指令优化了一下,以最高效的顺序执行
源代码 ==> 编译器优化的重排 ==> 指令并行也可能重排 ==> 内存系统也会重排 执行
指令重排有个前提,他会考虑数据之间的依赖性,如果没有影响才会进行指令重排
加入volatile会避免指令重排,内存中有屏障,作用
单例模式
单例模式是Java中最简单的设计模式之一,这种模式涉及一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
注意
单例类只能有一个实例
单例类必须自己创建自己的唯一实例
单例类必须给所有其他的对象提供这一实例
饿汉式单例
多线程下特点
这种方式比较常用,但容易产生垃圾对象。私有构造器,开始直接创建实例被加载进内存。 容易造成内存空间的浪费
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Demo1_hungry { private byte [] data1 = new byte [1024 * 1024 ]; private byte [] data2 = new byte [1024 * 1024 ]; private byte [] data3 = new byte [1024 * 1024 ]; private byte [] data4 = new byte [1024 * 1024 ]; private Demo1_hungry () { } private static final Demo1_hungry hungryMan = new Demo1_hungry(); public static Demo1_hungry getInstance () { return hungryMan; } }
懒汉式单例
一般懒汉式
特点
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 public class Demo2_lazy { private static Demo2_lazy lazyMan; private Demo2_lazy () { System.out.println(Thread.currentThread().getName() + "线程拿到了实例!" ); } public static Demo2_lazy getInstance () { if (lazyMan == null ){ return new Demo2_lazy(); } return lazyMan; } public static void main (String[] args) { for (int i = 0 ; i < 10 ; i++) { new Thread(()->{ Demo2_lazy.getInstance(); },String.valueOf(i)).start(); } } }0 线程拿到了实例!4 线程拿到了实例!5 线程拿到了实例!3 线程拿到了实例!2 线程拿到了实例!1 线程拿到了实例!7 线程拿到了实例!6 线程拿到了实例!8 线程拿到了实例!9 线程拿到了实例!
加锁懒汉式
特点
相比一般的懒汉式,在获取实例的静态方法前加synchronized关键字,可以保证多线程之间的同步问题,保证单例模式
这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)
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 public class Demo3_sychronziedLazyMan { private static Demo3_sychronziedLazyMan lazyMan; private Demo3_sychronziedLazyMan () { System.out.println(Thread.currentThread().getName() + "线程拿到了实例!" ); } public static synchronized Demo3_sychronziedLazyMan getInstance () { if (lazyMan == null ){ lazyMan = new Demo3_sychronziedLazyMan(); } return lazyMan; } public static void main (String[] args) { for (int i = 0 ; i < 10 ; i++) { new Thread(()->{ Demo3_sychronziedLazyMan.getInstance(); },String.valueOf(i)).start(); } } }0 线程拿到了实例!
DCL双重校验锁
特点
DCL即 double-checked locking
相比一般懒汉式,又加了一层 if(实例还不存在) { synchronized (单例类) { if(实力还不存在) 初始化单例} }, volatile关键字 保证线程之间的同步问题
相比加锁的懒汉式,不是在方法前面加synchronized从而影响效率,这种方法效率更高。
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。 getInstance() 的性能对应用程序很关键。
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 public class Demo4_DCLLazyMan { private static Demo4_DCLLazyMan lazyMan; private Demo4_DCLLazyMan () { System.out.println(Thread.currentThread().getName() + " 线程拿到了实例!" ); } public static Demo4_DCLLazyMan getInstance () { if (lazyMan == null ){ synchronized (Demo4_DCLLazyMan.class){ if (lazyMan == null ){ lazyMan = new Demo4_DCLLazyMan(); } } } return lazyMan; } public static void main (String[] args) { for (int i = 0 ; i < 10 ; i++) { new Thread(()->{ Demo3_sychronziedLazyMan.getInstance(); },String.valueOf(i)).start(); } } }0 线程拿到了实例!
但是上面的lazyMan = new Demo4_DCLLazyMan();
依然有一定的缺陷,在JMM中并不是原子操作,创建实例的过程大致分为三个步骤
分配内存空间
执行构造方法
将实例变量指向内存空间
而在多线程执行时候,最后两步是可以变的,而这就可能导致多线程同步异常,获取单例失败,因此可以通过使用volatile关键字禁止指令重排解决常见操作非原子性的问题
1 2 private static volatile Demo4_DCLLazyMan lazyMan;
CAS
概念
CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术。
CAS也是现在面试经常问的问题
之前学习的java.util.concurrent.automics下的AtomicsInteger等类提供简单的cas操作
1 2 3 4 5 6 7 8 9 10 public class Demo1_CAS { public static void main (String[] args) { AtomicInteger num = new AtomicInteger(100 ); num.compareAndSet(100 ,101 ); System.out.println(num.compareAndSet(101 , 120 )); System.out.println(num.compareAndSet(150 , 130 )); } }
而Java实现CAS的功能使用的依然是我们熟悉的Unsafe类
ABA问题
ABA问题即是狸猫换太子,也就是一个线程速度过快,在正常线程1,2之间穿插了一个捣乱的线程,但是捣乱线程的操作会使 数据恢复为未执行之前的状态,故不会影响正常线程执行结果,一般情况下ABA并不会出现什么问题,但是设计引用的时候就会出现问题。
通过原子引用来解决ABA问题
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 60 61 62 63 64 65 66 67 68 public class ABA { private static AtomicStampedReference<Integer> num = new AtomicStampedReference<>(1 ,1 ); public static void main (String[] args) { int currentStamp = num.getStamp(); new Thread(()->{ System.out.println("=============正常线程1===========" ); System.out.println("正常版本号1 拿到版本号" + num.getStamp()); },"正常线程" ).start(); try { TimeUnit.SECONDS.sleep(2 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ System.out.println("=========I AM 捣乱线程==========" ); System.out.println(num.compareAndSet(1 ,2 ,num.getStamp(), num.getStamp() + 1 )); System.out.println("捣乱线程第一次修改值后的 版本号===》" + num.getStamp()); System.out.println(num.compareAndSet(2 , 1 , num.getStamp(), num.getStamp() + 1 )); System.out.println("捣乱线程第二次修改值后的 版本号===》" + num.getStamp()); },"捣乱线程" ).start(); try { TimeUnit.SECONDS.sleep(2 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ System.out.println("=============正常线程2===========" ); System.out.println("正常版本号2执行前 当前版本号" + num.getStamp()); System.out.println("正常版本号2执行前 期望版本号" + currentStamp); System.out.println(num.compareAndSet(1000 , 3000 , currentStamp, num.getStamp() + 1 )); System.out.println("正常线程2执行后 版本号===》" + num.getStamp()); },"正常线程" ).start(); } } =============正常线程1 =========== 正常版本号1 拿到版本号1 =========I AM 捣乱线程==========true 捣乱线程第一次修改值后的 版本号===》2 true 捣乱线程第二次修改值后的 版本号===》3 =============正常线程2 =========== 正常版本号2 执行前 当前版本号3 正常版本号2 执行前 期望版本号1 false 正常线程2 执行后 版本号===》3 进程已结束,退出代码为 0
各种锁
公平,非公平锁
概念
公平锁:非常公平,线程之间不可以插队
非公平锁:非常不公平,线程之间可以插队(默认)
这里的“公平”,其实通俗意义来说就是“先来后到”,也就是FIFO。如果对一个锁来说,先对锁获取请求的线程一定会先被满足,后对锁获取请求的线程后被满足,那这个锁就是公平的。反之,那就是不公平的。
一般情况下,非公平锁能提升一定的效率。但是非公平锁可能会发生线程饥饿(有一些线程长时间得不到锁)的情况 。所以要根据实际的需求来选择非公平锁和公平锁。
ReentrantLock支持非公平锁和公平锁两种。
乐观锁与悲观锁
悲观锁:
悲观锁就是我们常说的锁。对于悲观锁来说,它总是认为每次访问共享资源时会发生冲突,所以必须对每次数据操作加上锁,以保证临界区的程序同一时间只能有一个线程在执行。
乐观锁:
乐观锁又称为“无锁”,顾名思义,它是乐观派。乐观锁总是假设对共享资源的访问没有冲突,线程可以不停地执行,无需加锁也无需等待。而一旦多个线程发生冲突,乐观锁通常是使用一种称为CAS的技术来保证线程执行的安全性。
由于无锁操作中没有锁的存在,因此不可能出现死锁的情况,也就是说乐观锁天生免疫死锁 。
乐观锁多用于“读多写少“的环境,避免频繁加锁影响性能;而悲观锁多用于”写多读少“的环境,避免频繁失败和重试影响性能。
可重入锁
所谓重入锁,顾名思义。就是支持重新进入的锁,也就是说这个锁支持一个线程对资源重复加锁 。
synchronized关键字就是使用的重入锁。比如说,你在一个synchronized实例方法里面调用另一个本实例的synchronized实例方法,它可以重新进入这个锁,不会出现任何异常。
如果我们自己在继承AQS实现同步器的时候,没有考虑到占有锁的线程再次获取锁的场景,可能就会导致线程阻塞,那这个就是一个“非可重入锁”。
ReentrantLock
的中文意思就是可重入锁。
自旋锁
概述
AtomicXxx类下的CAS方法底层就是 自旋锁,不断循环直到成功为止
自旋锁主要应用CAS操作,直到手动释放锁才停止
主要就是while啥的,不成功就一直循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public final int getAndUpdate (IntUnaryOperator updateFunction) { int prev, next; do { prev = get(); next = updateFunction.applyAsInt(prev); } while (!compareAndSet(prev, next)); return prev; }
死锁
概念
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。 此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
两个线程互相抢夺资源,互相争用还未释放的锁
可以使用下面的方法排查死锁
使用命令 jps -1
定位进程号
使用jstack 进程号
查看堆栈信息