前言:函数式编程

函数式接口Function interface: 任何接口,如果只包含唯一 一个抽象方法,那么他就是一个函数式接口

如:Runnable接口 当中只有一个抽象方法,他就是函数式接口 如果有两个及以上的抽象方法,它就不是函数式接口
lambda 表达式:

  1. //Lambda 表达式
  2. class T{
  3. public void run(){
  4. new Thread(()->System.out.println()).start();
  5. new Thread(new Runnable(){
  6. @Override
  7. public void run() {
  8. System.out.println();
  9. }
  10. }).start(); ;
  11. }
  12. }

一.多线程基础:

1.1线程与进程

1.1.1进程:

进程是对应操作系统而言的,一个应用程序或者服务,操作系统主要的任务就是进程调度。目前的操作系统都支持多进程的任务调度。

1.1.2线程:

线程是进程的细分,一个进程可以包含多条线程,线程也叫轻量级进程。CPU调度的最小单位是线程

1.2 线程两种创建方式:

1.2.1 继承Thread类

1.定义类来继承Thread根据自己的要求
2.重写run()
3.创建并启动线程

1.2.2实现Runnable接口:

  1. 实现Runnable然后使用Thread去用start方法 实现的是静态代理<br /> 创建Runnable实现类( Task任务 ):<br />
  1. //线程任务
  2. class WashFeet implements Runnable{
  3. @Override
  4. public void run() {
  5. while (true)
  6. System.out.println("洗脚.....");
  7. }
  8. }
  9. 创建线程并启动:
  10. public class SkyAndEarth {
  11. public static void main(String[] args) {
  12. Thread son = new Thread( new WashFeet() );
  13. son.start();
  14. while (true){
  15. System.out.println("正在营业....");
  16. }
  17. }
  1. <br />**还有一种实现Callable接口: 在第六章(了解)**

1.2.3 两种方式对比

  1. 方式一,线程对象和线程任务是耦合的,方式二,线程对象和线程任务是分离的<br /> 方法一,扩展性不好,类单继承的。 方式二,扩展性更好,接口是多实现。<br /> 方法一,调用线程方法直接 ,方式二,不可直接调用线程方法,需要先调用 Thread.currentThread(),<br /> 这个方法的作用是拿到与当前线程任务绑定的线程对象<br />

1.3 线程常见方法:

  1. <br />Thread.currentThread() //拿到线程本身<br />.getState() 获得线程状态<br />start() 启动线程。<br />.isAlive(); 查看是否还活着<br /> Thread.sleep( long ms ) 睡眠 暂停执行,直到睡眠时间到。<br /> setName()/getName() 取名字/获得名字,如果没有设置名字,默认名字为Thread-(0-n)<br />Thread.yield()线程礼让. 让出cpu执行时间片,自己进入就绪状态,再次等待调度。<br />join() 同步,加入线程线程中<br /> setDeamon(boolean xx)/isDeamon()设置守护线程/判断守护线程<br />守护线程:c()垃圾回收 虚拟机必须确保用户线程执行完毕 <br /> 虚拟机不用等待守护线程执行完毕 如 后台记录操作日志、监控内存、垃圾回收用户线程:执行完就结束了<br /> setPriority(int xx)/getPriority() 设置/获取优先级 ,<br /> 默认三个 MIN_PRIORITY = 1;NORM_PRIORITY = 5;MAX_PRIORITY = 10;<br /> ava提供一个线程调度器(控制cpu) 用来监控程序启动后进入就绪状态的所有线程;<br /> 线程调度按照优先级决定调度哪个线程来执行<br /> 线程优先级1~10 线程优先级越高cpu执行时不一定会调它 只是他的权重会变大;<br />

1.4 理解同步与异步

  1. 同步:一个线程全部做完以后,另一个线程才开始执行,也就是一个线程的开始总是接着另一个线程的结尾。<br /> 异步:一个线程不需要等待,另一个线程执行完毕后,才执行,从宏观上看线程是同时执行,但是微观上是一个线程执行一点点后,另外一个线程又执行一点点,这样交替执行。线程默认是异步的,同步需要自己控制<br /> 多线程好处: 可以充分利用CPU,尤其是在处理阻塞问题的时候,使用多线程可以提高效率<br />

二.线程同步:

2.1 为什么要线程同步

  1. 就是为了数据一致,数据的安全性。<br />

2.2.1 同步代码块

  1. class Window implements Runnable{
  2. //模拟100张票
  3. static int total = 100;
  4. @Override
  5. public void run() {
  6. while (true){
  7. synchronized( "" ){ // 小括号()里需要一个对象,这个对对象必须满足,不同线程进入时是同一个对象。这样才有互斥性。
  8. if(total>0) {
  9. total--;
  10. //没时间了
  11. System.out.println(Thread.currentThread().getName() + "卖出1张,还剩" + total);
  12. }else{
  13. break;
  14. }
  15. }
  16. }
  17. }
  18. }

2.2.2 同步方法

  1. 和同步代码块原理基本一致也是使用synchronized 只是把同步范围扩大至整个方法,也就是锁的粒度更粗。<br />
  1. //卖票软件
  2. class SoftWare{
  3. private static int total = 100 ;
  4. public static synchronized void sales(){
  5. fwefw
  6. wfewfwef
  7. wfewf
  8. if( total>0 ){
  9. total--;
  10. System.out.println( Thread.currentThread().getName() +"销售1张,还剩余"+total);
  11. }
  12. }
  13. public static int size(){
  14. return total;
  15. }
  16. }

2.2.3 同步不安全的集合

  1. static <T> List<T> synchronizedList(List<T> list) <br /> 返回指定列表支持的同步(线程安全的)列表。
  2. static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)<br /> 返回由指定映射支持的同步(线程安全的)映射。
  3. static <T> Set<T> synchronizedSet(Set<T> s)<br /> 返回指定 set 支持的同步(线程安全的)set。<br /> <br /> synchronizedList(List<T> list) 为例, 实现原理就是使用 同步代码块,这方法返回值实际上是 SynchronizedList 的实例,<br /> 这个实例套娃使用了 出入参数不安全的集合 ,在不安全外围包裹了一个 同步代码块<br />

2.2.4 死锁问题[高频面试]

  1. <br />**死锁**:多个线程各自占着共享资源 并且互相等待其他线程占有的资源才可以运行,导致两者或者多个线程都在等待对象释放资源停止的这种情形 <br />**死锁产生的必要条件:**<br /> **1.互斥条件:一个资源只能被一个线程所使用**<br />** 2.请求与保持条件:一个线程因请求资源而阻塞时,以获取的资源保持不放**<br />** 3.不剥夺条件:线程获取资源,在未使用完成时,不能强行剥夺**<br />** 4.循环等待:若干线程之间形成一种头尾衔接的循环等待资源的关系**<br />** 上面四个死锁出现的必要条件,我们只要想办法破坏其中一种,就可以避免死锁**
  1. class test{
  2. public static void main(String[] args){
  3. Object o1 = new Object();
  4. Object o2 = new Object();
  5. Thread son1 = new Thread( new H(o1,o2) );
  6. son1.start();
  7. Thread son2 = new Thread( new Boo(o1,o2) );
  8. son2.start();
  9. }
  10. }
  11. //死锁
  12. class H implements Runnable{
  13. Object o1 = null;
  14. Object o2 = null;
  15. public H(Object o1,Object o2){
  16. this.o1=o1;
  17. this.o2=o2;
  18. }
  19. @Override
  20. public void run() {
  21. synchronized (o1){
  22. System.out.println( "Foo线程获得o1,想去锁o2");
  23. //可选 主要是为其他线程争取时间
  24. try {
  25. Thread.sleep(1);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. synchronized (o2){
  30. System.out.println("Foo线程获得o2");
  31. }
  32. System.out.println("Foo释放o2");
  33. }
  34. System.out.println("Foo 释放o1");
  35. }
  36. }
  37. class Boo implements Runnable{
  38. Object o1 = null;
  39. Object o2 =null;
  40. public Boo(Object o1, Object o2) {
  41. this.o1 = o1;
  42. this.o2 = o2;
  43. }
  44. @Override
  45. public void run() {
  46. synchronized (o2){
  47. System.out.println( "Boo线程获得o2,想去锁o1");
  48. synchronized (o1){
  49. System.out.println("Boo线程获得o1");
  50. }
  51. System.out.println("Boo释放o1");
  52. }
  53. System.out.println("Boo 释放o2");
  54. }
  55. }

三.线程生命周期[高频面试]

  1. 创建 就绪 -(阻塞)- 运行 结束

11.线程 - 图1

四..JUC锁[高频面试]

4.1 锁分类

  1. 锁的作用就是保证数据一致性,但是往往就会牺牲效率,所以使用锁需要自己做好平衡和取舍。<br /> 同等情况下,当然使用性能更好的锁,是最好的方案。<br />

4.1.1 悲观锁

  1. 怀疑任何情况都会出现并发问题,所以在设计的时候 就默认锁定,jdk1.5前就是 synchronized ,<br /> 早期说这个性能有问题(经过优化现在一般不去比较了)。<br /> JDK1.5新的解决方法是提供了Lock接口和他的实现类。例如 ReentrantLock ReentrantReadWriteLock等。<br />

4.1.2 乐观锁

  1. 乐观锁就是和悲观锁相反的思想,就是先不怀疑存在并发问题,如果确有存在再解决,<br /> 通常需要为并发修改的数据设定一个版本号,<br /> 修改前对一下先前获取的版本号,修改时在比较版本号如果一致才修改,<br /> 不一致说明有版本变化不可修改。CAS算法就是一种乐观锁算法,<br /> 它是硬件层面实现的,把多语句指令和为一个指令,确保了原子操作。<br /> Compare And Swap( 比较 交换 ) V (内存值) E(期望值) N(新值) 当且仅当 V==E 才将 V=N

4.1.3Lock 锁

  1. java1.5之后的更为强大的同步机制,通过显示定义同步锁对象来实现同步,同步锁使用Lock对象来<br /> Lock接口和他的实现类。例如 **ReentrantLock ReentrantReadWriteLoc**k
  2. Lock和对比 synchronized
  3. lock 是显式锁 synchroized是隐式锁 lock只能锁代码块 使用lock可以提高效率<br />synchronized 关键字 ReentrantLock 类(jdk1.5新引入)<br /> 都是重入锁(ReentrantLock 功能强大 提供了更多的方法,以及实现公平锁策略)<br /> synchronized 上锁与解锁自动完成 ReentrantLock 需要自己上锁和解锁。
  1. class L implements Runnable{
  2. public static void main(String[] args) {
  3. Thread T1 = new Thread(new L());
  4. T1.setName("黄牛");
  5. Thread T2 = new Thread(new L());
  6. T2.setName("商家");
  7. Thread T3 = new Thread(new L());
  8. T3.setName("学生");
  9. T3.setPriority(10);
  10. T1.start();
  11. T2.start();
  12. T3.start();
  13. }
  14. static int num =100;
  15. static Lock lock = new ReentrantLock();
  16. @Override
  17. public void run() {
  18. while(true){
  19. try {
  20. lock.lock();
  21. if (num>0){
  22. try {
  23. Thread.sleep(100);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. num--;
  28. System.out.println(Thread.currentThread().getName()+"抢到一张票,现在剩余"+num+"张票");
  29. }else{
  30. break;
  31. }
  32. }finally {
  33. lock.unlock();
  34. }
  35. }
  36. }
  37. }

五.线程通信;

  1. 什么是线程通信:协调多个线程有序访问某些资源。<br /> 线程通信方法:这些方法必须使用在存在 synchronized 代码块内 或者 synchronized 方法中。<br /> wait():void , 它是Object的方法,不是线程提供的方法,让执行这个wait()调用的线程等待。无限期等待,直到有被唤醒。<br /> wait( long ms ) : 等待指定的毫米数,如果没有被唤醒,则自动唤醒<br /> wait( long ms, int naos) : 更精确的等待时间<br /> notify(): 唤醒等待在该对象上的线程(只会唤醒一个)。<br /> notifyAll(): 唤醒全部等待在该对象上的全部线程。<br /> 开发协作模式”生产者/消费者”===》
  1. //线程通信方法
  2. class WaitAndNotifyCase {
  3. public static void main(String[] args) throws InterruptedException {
  4. Object obj = new Object();
  5. Thread son1 = new Thread( new Foo(obj) );
  6. son1.setName("张三");
  7. Thread son2 = new Thread( new Foo(obj) );
  8. son2.setName("李四");
  9. son1.start();
  10. son2.start();
  11. //让主线程等5s后去唤醒其他线程
  12. Thread.sleep(5000);
  13. synchronized (obj){
  14. //obj.notify();//唤醒一个
  15. obj.notifyAll();//唤醒全部
  16. }
  17. }
  18. }
  19. class Foo implements Runnable{
  20. Object obj = null;
  21. public Foo(Object obj) {
  22. this.obj = obj;
  23. }
  24. @Override
  25. public void run() {
  26. synchronized (obj){
  27. System.out.println(Thread.currentThread().getName()+ "执行开始...");
  28. try {
  29. obj.wait();
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. System.out.println(Thread.currentThread().getName()+ "执行完毕...");
  34. }
  35. }
  36. }

管程法

  1. 生产者:负责生产(可能是方法、对象、线程、进程)<br /> 消费者:负责消费(可能是方法、对象、线程、进程)<br /> 缓冲区:消费者不能直接使用生产者数据需要有一个缓冲区<br />开发协作模式"生产者/消费者”===》
  1. //生产者消费者模型 管程法
  2. //容器
  3. class Container{
  4. Object[] data = new Object[10];
  5. int size;
  6. public synchronized void add (Object obj){
  7. while(size==data.length){
  8. try {
  9. this.wait();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. data[size++]=obj;
  15. System.out.println(Thread.currentThread().getName()+"生产了"+obj);
  16. this.notifyAll();
  17. }
  18. public synchronized Object get(){
  19. while (size==0){
  20. try {
  21. this.wait();
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. Object result = data[--size];
  27. System.out.println(Thread.currentThread().getName()+"消费了"+result);
  28. this.notifyAll();
  29. return result;
  30. }
  31. }
  32. //生产者
  33. class Product implements Runnable{
  34. Container container;
  35. public Product(Container container){
  36. this.container=container;
  37. }
  38. @Override
  39. public void run() {
  40. for (int i = 1; i < 10; i++) {
  41. container.add("鸭子"+i);
  42. }
  43. }
  44. }
  45. //消费者
  46. class Consumer implements Runnable{
  47. Container container;
  48. public Consumer(Container container){
  49. this.container=container;
  50. }
  51. @Override
  52. public void run() {
  53. for (int i = 1; i < 10; i++) {
  54. container.get();
  55. }
  56. }
  57. }
  58. class test{
  59. public static void main(String[] args) {
  60. Container container = new Container();
  61. Thread xfz1 = new Thread( new Consumer(container) );
  62. xfz1.start();
  63. Thread scz1 = new Thread( new Product( container ) );
  64. scz1.start();
  65. }
  66. }

信号灯法
*通过标志位区判断:如果标志位为真 等待 标志位为假通知另一个等待

  1. //信号灯法
  2. class demo{
  3. public static void main(String[] args) {
  4. DT d = new DT();
  5. Gz g = new Gz(d);
  6. Yy y = new Yy(d);
  7. g.start();
  8. y.start();
  9. }
  10. }
  11. class Yy extends Thread{
  12. DT dt;
  13. public Yy(DT dt){
  14. this.dt=dt;
  15. }
  16. @Override
  17. public void run() {
  18. for (int i = 0; i < 20; i++) {
  19. this.dt.play("快乐大本营");
  20. }
  21. }
  22. }
  23. class Gz extends Thread{
  24. public Gz(DT dt){
  25. this.dt=dt;
  26. }
  27. DT dt;
  28. @Override
  29. public void run() {
  30. for (int i = 0; i < 20; i++) {
  31. this.dt.watch();
  32. }
  33. }
  34. }
  35. class DT{
  36. String name;
  37. boolean flag = true;
  38. public synchronized void play(String name){
  39. while(!flag){
  40. try {
  41. this.wait();
  42. } catch (InterruptedException e) {
  43. e.printStackTrace();
  44. }
  45. }
  46. System.out.println("演员表演了:"+name);
  47. this.name=name;
  48. this.notifyAll();
  49. this.flag=!flag;
  50. }
  51. public synchronized void watch(){
  52. while(flag){
  53. try {
  54. this.wait();
  55. } catch (InterruptedException e) {
  56. e.printStackTrace();
  57. }
  58. }
  59. System.out.println("观众观看了:"+name);
  60. this.notifyAll();
  61. this.flag=!flag;
  62. }
  63. }

六.线程池

  1. 先讲实现Callable接口;call 是一个有返回值的方法 Callable<> 带泛型<br />

6.1 池:

是一种容器的概念,池化技术,都是容器技术。创建多个线程对象存入一个容器中,用就去池中获取,用完再放回去
因为线程也是宝贵的资源,频繁的创建销毁线程会存在内存开销。
Executor : 线程池的顶级接口
ExecutorService: Executor 的子接口,扩展了一些管理线程任务的方法。
ScheduledExecutorService: ExecutorService 的子接口 提供了延时执行任务的方法

6.2 如何创建线程池

Executor : 线程池的顶级接口
ExecutorService: Executor 的子接口,扩展了一些管理线程任务的方法。
ScheduledExecutorService: ExecutorService 的子接口 提供了延时执行任务的方法
corePoolSize:
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会停止

  1. <br />Executors 线程池的工具类 ,可以创建出线程池实例 多个S就是 工具类 没有就是接口<br />ExecutorService: Executor 的子接口<br /> 方法:<br />void execute(Runnable command); 执行命令 无返回值 一般用于执行Runnable接口<br /><T>future<T> submit (Callable<T> task) ; 执行命令 有返回值 一般用于执行Callable接口<br />void shutdown(); 关闭连接 <br />提交任务到线程池,Runnable 类型的任务,Callable 类型的任务 <br />Future 保存异步运算的结果,它提供了一个get()用于返回真正的异步计算结果,<br /> 但是调用get()方法会造成调用线程阻塞,直到获得结果为止。<br /> <br />//1 单线程池<br /> ExecutorService es = Executors.newSingleThreadExecutor();

//2 固定线程池
ExecutorService es2 = Executors.newFixedThreadPool(10);

//3 可变数量的线程池
ExecutorService es3 = Executors.newCachedThreadPool();

//4 创建定时任务线程池
ScheduledExecutorService se4 = Executors.newScheduledThreadPool(10);

6.3.提交任务到线程池

6.3.1 Runnable 类型的任务

  1. //关注运行没有结果的任务
  2. class WashFeet implements Runnable{
  3. @Override
  4. public void run() {
  5. System.out.println(Thread.currentThread().getName()+"洗脚...");
  6. try {
  7. Thread.sleep(3000);//模拟一个延时
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. }
  12. }

方法名为 run() 没有返回值 不可抛出异常

6.3.2 Callable 类型的任务

  1. class Numcount implements Callable<Integer>{
  2. @Override
  3. public Integer call() throws Exception {
  4. int sum=0;
  5. for (int i = 0; i <= 100; i++) {
  6. sum+=i;
  7. }
  8. System.out.println(Thread.currentThread().getName() +"sum:"+sum);
  9. return sum;
  10. }
  11. }
  12. class demo2{
  13. public static void main(String[] args) {
  14. Numcount num = new Numcount();
  15. ExecutorService es = Executors.newSingleThreadExecutor();
  16. Future<Integer> receive = es.submit(num);
  17. Integer result = null;
  18. try {
  19. result = receive.get();
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. } catch (ExecutionException e) {
  23. e.printStackTrace();
  24. }
  25. System.out.println(result);
  26. }
  27. }

特点: 方法名为 call() 有返回值 可抛出异常

6.3.3 Future 异步结果

Future 保存异步运算的结果,它提供了一个get()用于返回真正的异步计算结果,但是调用get()方法会造成调用线程阻塞,直到获得结果为止。

  1. Future<Integer> result = es2.submit( new NumberCount() );
  2. Integer sum = result.get(); //阻塞调用语句的线程
  3. System.out.println(sum);

6.4 关闭线程池

  1. shutdown() 方法,调用后线程池可拒绝接收新的任务,带全部现有任务执行完毕后关闭线程池。<br />

6.5 统计多个任务运行的总时间

  1. 使用 shutdown()+isTerminated() 循环判断<br /> CountDownLatch 同步辅助工具类 构造一个任务总数,每个任务结束就减一,直到为0,把调用 await() 的线程唤醒。

七.并发集合 (JUC 集合)

image.png
CopyOnWriteArrayList : 底层使用 ReentrantLock实现锁,并且修改数据,拷贝一个新的数字操作。
CopyOnWriteArraySet : 底层套娃使用 CopyOnWriteArrayList ,添加前通过addIfAbsent(e) 判断元素是否已经存在,如果存在着不添加。
ConcurrentHashMap : 底层使用(1.8早期分段锁 后期使用CAS 无锁算法)

ConcurrentLinkedQueue : 并发安全的 队列,链表实现
ArrayBlockingQueue : 阻塞队列,基于数组实现 ,也是有有界队列
LinkedBlockingQueue: 阻塞队列,基于链表实现, 是无界队列

补充:
volatile 【面试题】
关键字 用于修饰变量 , 它作用是,可以将线程工作内存中的数据,立即同步到主存,以保证其他线程池立即可见。简单的说就是解决内存可见性问题。但是不能保证原子性问题。
JAVA (JMM)java内存模型:
image.png

JUC 中除有线程池 锁 同步工具, 还提供了一大堆 原子操作类。
AtomicInger AtomicLong……. 类它们的底层使用cas算法保证线程安全问题。

附图

JVM内存模型:

11.线程 - 图4

案例:

用sychorized 不交互打印

  1. class Windows implements Runnable{
  2. public static void main(String[] args){
  3. Thread t1 =new Thread(new Windows("a"));
  4. t1.start();
  5. Thread t2 =new Thread(new Windows("daa"));
  6. t2.start();
  7. }
  8. private String data;
  9. public Windows(String data ){
  10. this.data=data;
  11. }
  12. //不交互打印 data
  13. public void run(){
  14. synchronized (System.in) {
  15. for (int i = 0; i < 100; i++) {
  16. System.out.println(data);
  17. }
  18. }
  19. }
  20. }

作业:计算1-100000之间的素数有哪些

  1. class Prime implements Runnable{
  2. @Override
  3. public void run() {
  4. int a = 0;
  5. for (int i = 2; i < 100000; i++) {
  6. boolean b = true;
  7. for (int j =2; j < i ; j++) {
  8. if(i%j==0){
  9. b = false;
  10. break;
  11. }
  12. }
  13. if(b){
  14. a++;
  15. }
  16. }
  17. System.out.println("2~100000之间的素数有"+a+"个");
  18. }
  19. }

计算1-100000之间的素数有哪些

  1. class Prime2 implements Runnable{
  2. @Override
  3. public void run() {
  4. int n = 0;//用来计数
  5. for (int i =100000; i <= 200000; i++) {
  6. if (isPrimeNumber(i))n++;
  7. }
  8. System.out.println("100000~200000之间的素数"+n+"个");
  9. }
  10. static Boolean isPrimeNumber(long num) {//判断是否为素数
  11. //判断一个数是否为素数
  12. if (num == 2) return true;//2特殊处理
  13. if (num < 2 || num % 2 == 0) return false;//识别小于2的数和偶数
  14. for (int i = 3; i <= Math.sqrt(num); i += 2) {
  15. if (num % i == 0) {//识别被奇数整除
  16. return false;
  17. }
  18. }
  19. return true;
  20. }
  21. }