1、什么是JUC
java.util 工具包、包、分类:
2、线程和进程 进程 :一个程序,QQ.exe Music.exe 程序的集合
一个进程往往可以包含多个线程,至少包含一个!
Java默认有几个线程?
2 个:mian、GC
线程 :开了一个进程 Typora,写字,自动保存(线程负责的 )
对于Java而言:Thread、Runnable、Callable 、线程池(四种创建线程的方式)
2.1、 java无法真正开启线程,底层需要调用C++开启线程 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 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 () ;
2.2 、并发、并行 并发编程 :并发、并行
并发 (多线程操作同一个资源)
CPU 一核 ,模拟出来多条线程,天下武功,唯快不破,快速交替
并行 (多个人一起行走)
CPU 多核 ,多个线程可以同时执行; 线程池
1 2 3 4 5 6 7 public class Test1 { public static void main (String[] args) { System.out.println(Runtime.getRuntime().availableProcessors()); } }
并发编程的本质 :充分利用CPU的资源
2.3、线程有几个状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public enum State { // 新生 NEW, // 运行 RUNNABLE, // 阻塞 BLOCKED, // 等待,死死地等 WAITING, // 超时等待 TIMED_WAITING, // 终止 TERMINATED; }
2.4、wait/sleep 区别
来自不同的类 wait => Object sleep => Thread
关于锁的释放 wait 会释放锁,sleep 不会释放锁!(抱着锁睡)
使用的范围是不同的 wait 必须在同步代码块中
sleep 可以再任何地方睡
是否需要捕获异常 wait 不需要捕获异常 sleep 必须要捕获异常
3、Lock锁(重点) 3.1 传统 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 public class SaleTicketDemo01 { public static void main (String[] args) { Ticket ticket = new Ticket (); new Thread (()->{ for (int i = 1 ; i < 40 ; i++) { ticket.sale(); } },"A" ).start(); new Thread (()->{ for (int i = 1 ; i < 40 ; i++) { ticket.sale(); } },"B" ).start(); new Thread (()->{ for (int i = 1 ; i < 40 ; i++) { ticket.sale(); } },"C" ).start(); } } class Ticket { private int number = 30 ; public synchronized void sale () { if (number>0 ){ System.out.println(Thread.currentThread().getName()+"卖出了" +(number- -)+"票,剩余:" +number); } } }
3.2 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 public class SaleTicketDemo02 { public static void main (String[] args) { Ticket2 ticket = new Ticket2 (); new Thread (()->{for (int i = 1 ; i < 40 ; i++) ticket.sale();},"A" ).start(); new Thread (()->{for (int i = 1 ; i < 40 ; i++) ticket.sale();},"B" ).start(); new Thread (()->{for (int i = 1 ; i < 40 ; i++) ticket.sale();},"C" ).start(); } } class Ticket2 { private int number = 30 ; Lock lock = new ReentrantLock (); public void sale () { lock.lock(); try { if (number>0 ){ System.out.println(Thread.currentThread().getName()+"卖出了" +(number--)+"票,剩余:" +number); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
3.3 Synchronized 和 Lock 区别 1、Synchronized 内置的Java关键字 , Lock 是一个Java类
2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
4、Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去;
5、Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以自己设置);
6、Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!
4、线程通讯(生产者消费者问题) 4.1 生产者和消费者问题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 56 57 58 59 60 61 62 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(); } }
使用if会导致,A B C D 4 个线程, 虚假唤醒
当使用if判断通过进入后,若被阻塞之后再次被唤醒时,之前if判断的条件可能已经改变了,但由于此时已执行到if体内了,就会按顺序往下执行(此时条件已不满足,不应往下继续执行)。——这就是虚假唤醒
解决方法:if 改为 while 判断
因为是在while体内,唤醒之后会循环判断条件,所以可以避免线程的虚假唤醒!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public synchronized void increment () throws InterruptedException { while (number!=0 ){ this .wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>" +number); this .notifyAll(); } public synchronized void decrement () throws InterruptedException { while (number==0 ){ this .wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>" +number); this .notifyAll(); }
4.2 JUC版的生产者和消费者问题(使用Lock锁的Condition)
通过Lock 获得Condition
使用Condition 可以精准的通知和唤醒线程
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 public class C { public static void main (String[] args) { Data3 data = new Data3 (); new Thread (()->{ for (int i = 0 ; i <10 ; i++) { data.printA(); } },"A" ).start(); new Thread (()->{ for (int i = 0 ; i <10 ; i++) { data.printB(); } },"B" ).start(); new Thread (()->{ for (int i = 0 ; i <10 ; i++) { data.printC(); } },"C" ).start(); } } class Data3 { private Lock lock = new ReentrantLock (); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); private int number = 1 ; public void printA () { lock.lock(); try { while (number!=1 ){ condition1.await(); } System.out.println(Thread.currentThread().getName()+"=>AAAAAAA" ); number = 2 ; condition2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printB () { lock.lock(); try { while (number!=2 ){ condition2.await(); } System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBB" ); number = 3 ; condition3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printC () { lock.lock(); try { while (number!=3 ){ condition3.await(); } System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBB" ); number = 1 ; condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
5、八锁现象 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 public class Test1 { public static void main (String[] args) { Phone phone = new Phone (); new Thread (()->{ phone.sendSms(); },"A" ).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (()->{ phone.call(); },"B" ).start(); } } class Phone { public synchronized void sendSms () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信" ); } public synchronized void call () { System.out.println("打电话" ); } }
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 public class Test2 { public static void main (String[] args) { Phone2 phone1 = new Phone2 (); Phone2 phone2 = new Phone2 (); new Thread (()->{ phone1.sendSms(); },"A" ).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (()->{ phone2.call(); },"B" ).start(); } } class Phone2 { public synchronized void sendSms () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信" ); } public synchronized void call () { System.out.println("打电话" ); } public void hello () { System.out.println("hello" ); } }
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 public class Test3 { public static void main (String[] args) { Phone3 phone1 = new Phone3 (); Phone3 phone2 = new Phone3 (); new Thread (()->{ phone1.sendSms(); },"A" ).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (()->{ phone2.call(); },"B" ).start(); } } class Phone3 { public static synchronized void sendSms () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信" ); } public static synchronized void call () { System.out.println("打电话" ); } }
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 public class Test4 { public static void main (String[] args) { Phone4 phone1 = new Phone4 (); Phone4 phone2 = new Phone4 (); new Thread (()->{ phone1.sendSms(); },"A" ).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread (()->{ phone2.call(); },"B" ).start(); } } class Phone4 { public static synchronized void sendSms () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信" ); } public synchronized void call () { System.out.println("打电话" ); } }
6、集合类线程不安全(使用原子集合类变安全)
List
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 List<String> list = new CopyOnWriteArrayList <>(); for (int i = 1 ; i <= 10 ; i++) { new Thread (()->{ list.add(UUID.randomUUID().toString().substring(0 ,5 )); System.out.println(list); },String.valueOf(i)).start(); } } }
Set
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class SetTest { public static void main (String[] args) { Set<String> set = new CopyOnWriteArraySet <>(); for (int i = 1 ; i <=30 ; i++) { new Thread (()->{ set.add(UUID.randomUUID().toString().substring(0 ,5 )); System.out.println(set); },String.valueOf(i)).start(); } } }
Tips: HashSet的底层是HashMap,用的是Map的key,所以不会有重复元素。
1 2 3 4 5 6 7 8 9 10 public HashSet () { map = new HashMap <>(); } public boolean add (E e) { return map.put(e, PRESENT)==null ; } private static final Object PRESENT = new Object ();
Map
工作中一般不用HashMap,因为企业中一般需要高并发,用HashMap会遇到ConcurrentModificationException异常,所以一般用线程安全的ConcurrentHashMap替代。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class MapTest { public static void main (String[] args) { Map<String, String> map = new ConcurrentHashMap <>(); for (int i = 1 ; i <=30 ; i++) { new Thread (()->{ map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring( 0 ,5 )); System.out.println(map); },String.valueOf(i)).start(); } } }
7、Callable 1、可以有返回值
2、可以抛出异常
3、方法不同,run()/ call()
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 public class CallableTest { public static void main (String[] args) throws ExecutionException, InterruptedException { new Thread ().start(); MyThread thread = new MyThread (); FutureTask futureTask = new FutureTask (thread); new Thread (futureTask,"A" ).start(); new Thread (futureTask,"B" ).start(); Integer o = (Integer) futureTask.get(); 最后 System.out.println(o); } } class MyThread implements Callable <Integer> { @Override public Integer call () { System.out.println("call()" ); return 1024 ; } }
细节:
1、有缓存
2、结果可能需要等待,会阻塞!
8. 常用的辅助类 8.1 CountDownLatch (倒计时计数器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class CountDownLatchDemo { public static void main (String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch (6 ); for (int i = 1 ; i <=6 ; i++) { new Thread (()->{ System.out.println(Thread.currentThread().getName()+" Go out" ); countDownLatch.countDown(); },String.valueOf(i)).start(); } countDownLatch.await(); System.out.println("Close Door" ); } }
原理:
countDownLatch.countDown(); // 数量-1
countDownLatch.await(); // 等待计数器归零,然后再向下执行
每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒继续执行!
8.2、CyclicBarrier
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 public class CyclicBarrierDemo { public static void main (String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier (7 ,()->{ System.out.println("召唤神龙成功!" ); }); for (int i = 1 ; i <=7 ; i++) { final int temp = i; new Thread (()->{ System.out.println(Thread.currentThread().getName()+"收 集" +temp+"个龙珠" ); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } }
8.3、Semaphore Semaphore:信号量
抢车位!6车—3个停车位置
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 SemaphoreDemo { public static void main (String[] args) { Semaphore semaphore = new Semaphore (3 ); for (int i = 1 ; i <=6 ; i++) { new Thread (()->{ try { semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"抢到车 位" ); TimeUnit.SECONDS.sleep(2 ); System.out.println(Thread.currentThread().getName()+"离开车 位" ); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); } },String.valueOf(i)).start(); } } }
原理:
semaphore.acquire() 获得,假设如果已经满了,等待,等待被释放为止!
**semaphore.release()**释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!
作用: 多个共享资源互斥的使用!并发限流 ,控制最大的线程数!