1、进程和线程 进程:把一个静态代码装载到内存中运行即可得到一个进程
线程:在进程中的执行单元,进程是最小的资源分配单元,线程是最小的执行单元
Java默认两个线程,一个Main线程,一个GC回收线程
Java实际上并不能开启线程,Thread.start方法最后调用native本地方法开启线程
并发编程:并发、并行
CPU 一核 ,模拟出来多条线程,天下武功,唯快不破,快速交替
CPU 多核 ,多个线程可以同时执行; 线程池
1 2 3 4 5 6 7 8 public class Test1 { public static void main (String[] args) { System.out.println(Runtime.getRuntime().availableProcessors()); } }
线程的几种状态(6种)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
wait和sleep区别
wait是Object类的方法,sleep是Thread类方法
wait会释放锁,sleep不会释放锁
wait必须在同步代码块使用,sleep随处可用
2、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 public class TicketSaleDemo1 { public static void main (String[] args) { Ticket ticket = new Ticket (); new Thread (()->{ for (int j = 0 ; j < 40 ; j++) { ticket.sale(); } },"线程A" ).start(); new Thread (()->{ for (int j = 0 ; j < 40 ; j++) { ticket.sale(); } },"线程B" ).start(); new Thread (()->{ for (int j = 0 ; j < 40 ; j++) { ticket.sale(); } },"线程C" ).start(); } } class Ticket { private int num = 30 ; public synchronized void sale () { if (num>0 ){ System.out.println(Thread.currentThread().getName()+"卖出了" +(num--)+"票,剩余" +num); } } }
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 public class TicketSaleDemo2 { public static void main (String[] args) { Ticket2 ticket = new Ticket2 (); new Thread (() -> { for (int i = 0 ; i < 40 ; i++) { ticket.sale(); } }, "A" ).start(); new Thread (() -> { for (int i = 0 ; i < 40 ; i++) { ticket.sale(); } }, "B" ).start(); new Thread (() -> { for (int i = 0 ; i < 40 ; i++) { ticket.sale(); } }, "C" ).start(); } } class Ticket2 { private int num = 30 ; Lock lock = new ReentrantLock (); public void sale () { lock.lock(); try { if (num > 0 ) { System.out.println(Thread.currentThread().getName() + "卖出了" + (num--) + "票,剩余" + num); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
synchronized和lock的区别
synchronized是内置Java关键字,Lock是JUC包下的一个类
synchronized无法判断锁的状态,Lock可以判断是否获取到锁
synchronized会自动释放锁,Lock需要自己手动释放锁,如果未释放会造成死锁
synchronized线程1(获得锁,阻塞),线程2(等待),Lock锁就不一定会等待下去
synchronized适合锁少量代码同步问题,Lock适合锁大量同步代码块
3、生产者消费者模式
生产者和消费者问题 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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public class Demo1 { public static void main (String[] args) { MyClass data = new MyClass (); new Thread (() -> { for (int i = 0 ; i < 10 ; i++) { try { data.increment(); } catch (InterruptedException e) { throw new RuntimeException (e); } } }, "A" ).start(); new Thread (() -> { for (int i = 0 ; i < 10 ; i++) { try { data.decrement(); } catch (InterruptedException e) { throw new RuntimeException (e); } } }, "B" ).start(); new Thread (() -> { for (int i = 0 ; i < 10 ; i++) { try { data.increment(); } catch (InterruptedException e) { throw new RuntimeException (e); } } }, "C" ).start(); new Thread (() -> { for (int i = 0 ; i < 10 ; i++) { try { data.decrement(); } catch (InterruptedException e) { throw new RuntimeException (e); } } }, "D" ).start(); } } class MyClass { private int number = 0 ; public synchronized void increment () throws InterruptedException { if (number != 0 ) { wait(); } number++; System.out.println(Thread.currentThread().getName() + "=>" + number); notifyAll(); } public synchronized void decrement () throws InterruptedException { if (number == 0 ) { wait(); } number--; System.out.println(Thread.currentThread().getName() + "=>" + number); notifyAll(); } }
存在虚假唤醒的可能,两个线程不会出问题,四个线程就有问题
虚假唤醒是指,在多线程编程中,wait方法通常用于使线程进入等待状态, 直到被其他线程唤醒。虚假唤醒是指线程在没有被notify或notifyAll方法正确唤醒的情况下, 却从wait状态恢复执行的现象。 如果使用if判断,只判断了一次,则会往下执行,应该使用while判断
底层系统实现差异:不同的操作系统和 JVM(Java 虚拟机)实现对于线程等待和唤醒的机制可能存在差异。在某些情况下,系统可能会产生一些 “伪信号”,导致线程被错误地唤醒。
信号干扰:在复杂的多线程环境中,可能存在多种并发的信号和事件。例如,当一个线程正在等待某个特定条件(如资源可用),但由于其他外部因素(如系统中断、硬件异常等),可能会错误地触发线程从等待状态中恢复。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public synchronized void increment () throws InterruptedException { while (number != 0 ) { wait(); } number++; System.out.println(Thread.currentThread().getName() + "=>" + number); notifyAll(); } public synchronized void decrement () throws InterruptedException { while (number == 0 ) { wait(); } number--; System.out.println(Thread.currentThread().getName() + "=>" + number); notifyAll(); }
JUC版本生产者消费者问题
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 public class LockDemo { public static void main (String[] args) { LockData data = new LockData (); new Thread (() -> { for (int i = 0 ; i < 10 ; i++) { data.increment(); } }, "A" ).start(); new Thread (() -> { for (int i = 0 ; i < 10 ; i++) { data.decrement(); } }, "B" ).start(); new Thread (() -> { for (int i = 0 ; i < 10 ; i++) { data.increment(); } }, "C" ).start(); new Thread (() -> { for (int i = 0 ; i < 10 ; i++) { data.decrement(); } }, "D" ).start(); } } class LockData { private int number = 0 ; final Lock lock = new ReentrantLock (); final Condition isZero = lock.newCondition(); public void increment () { lock.lock(); try { while (number != 0 ) { isZero.await(); } number++; System.out.println(Thread.currentThread().getName() + "->" + number); isZero.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void decrement () { lock.lock(); try { while (number == 0 ) { isZero.await(); } number--; System.out.println(Thread.currentThread().getName() + "->" + number); isZero.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
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 93 public class ConditionDemo { public static void main (String[] args) { ConditionData data = new ConditionData (); 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 ConditionData { private Lock lock = new ReentrantLock (); private Condition printA = lock.newCondition(); private Condition printB = lock.newCondition(); private Condition printC = lock.newCondition(); private int flag = 1 ; public void printA () { lock.lock(); try { while (flag != 1 ) { printA.await(); } System.out.println(Thread.currentThread().getName() + "====>" + "AAAAAAAAAAAAAA" ); flag++; printB.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printB () { lock.lock(); try { while (flag != 2 ) { printB.await(); } System.out.println(Thread.currentThread().getName() + "====>" + "BBBBBBBBBBBBB" ); flag++; printC.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printC () { lock.lock(); try { while (flag != 3 ) { printC.await(); } System.out.println(Thread.currentThread().getName() + "====>" + "CCCCCCCCCCCCC" ); flag = 1 ; printA.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
4、锁的八个问题
锁的对象只有两种,一种是实例锁,锁的是对象实例可以存在多个实例,一种是Class锁,锁的是字节码只有一份
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 Demo1 { public static void main (String[] args) { Phone phone = new Phone (); new Thread (()->{ phone.sendMsg(); },"A" ).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { throw new RuntimeException (e); } new Thread (()->{ phone.call(); },"B" ).start(); } } class Phone { public synchronized void sendMsg () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { throw new RuntimeException (e); } 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 public class Demo2 { public static void main (String[] args) { Phone2 phone1 = new Phone2 (); Phone2 phone2 = new Phone2 (); new Thread (()->{ phone1.sendMsg(); },"A" ).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { throw new RuntimeException (e); } new Thread (()->{ phone2.call(); },"B" ).start(); } } class Phone2 { public synchronized void sendMsg () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { throw new RuntimeException (e); } 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 49 public class Demo3 { public static void main (String[] args) { Phone3 phone1 = new Phone3 (); Phone3 phone2 = new Phone3 (); new Thread (()->{ phone1.sendMsg(); },"A" ).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { throw new RuntimeException (e); } new Thread (()->{ phone2.call(); },"B" ).start(); } } class Phone3 { public static synchronized void sendMsg () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { throw new RuntimeException (e); } System.out.println("发消息" ); } public static 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 public class Demo4 { public static void main (String[] args) { Phone4 phone1 = new Phone4 (); Phone4 phone2 = new Phone4 (); new Thread (()->{ phone1.sendMsg(); },"A" ).start(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { throw new RuntimeException (e); } new Thread (()->{ phone2.call(); },"B" ).start(); } } class Phone4 { public static synchronized void sendMsg () { try { TimeUnit.SECONDS.sleep(4 ); } catch (InterruptedException e) { throw new RuntimeException (e); } System.out.println("发消息" ); } public synchronized void call () { System.out.println("打电话" ); } }
实例锁:sync在实例方法上,sync在代码块中sync(this)
类锁:sync在静态方法上,sync在代码块中sync(xxx.class)
5、集合类不安全
List不安全
使用ArrayList在多线程环境下报错:java.util.ConcurrentModificationException 多线程修改异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class UnSafeList { public static void main (String[] args) { List list = new CopyOnWriteArrayList (); for (int i = 0 ; i < 10 ; i++) { new Thread (()->{ list.add(UUID.randomUUID().toString().substring(0 ,5 )); System.out.println(list); }).start(); } } }
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。 这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public boolean add (E e) { final ReentrantLock lock = this .lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1 ); newElements[len] = e; setArray(newElements); return true ; } finally { lock.unlock(); } }
读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为开始读的那一刻已经确定了读的对象是旧对象。CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单等场景。
Set不安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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(); } } }
HashSet底层就是HashMap
1 2 3 4 5 6 7 8 public HashSet () { map = new HashMap <>(); } public boolean add (E e) { return map.put(e, PRESENT)==null ; } private static final Object PRESENT = new Object ();