撸一撸多线程的来龙去脉你就知道,为什么面试官折磨喜欢问这种问题了!

Posted 四原色

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了撸一撸多线程的来龙去脉你就知道,为什么面试官折磨喜欢问这种问题了!相关的知识,希望对你有一定的参考价值。

撸一撸多线程的来龙去脉你就知道,为什么面试官折磨喜欢问这种问题了!

有一天张三去面试,被面试官问了一个问题:

什么叫多线程?

张三多线程就是多个线程在cpu上执行,抢占Cpu资源,提高CPU执行效率


面试官你在说什么你懂什么?你到底学了什么回去吧你!!!

张三很伤心,出来的时候在路上直接一把抢了大妈的**《java从入门到放弃》**,一回去就把java多线程复习了一遍。

一、认识多线程

1.多任务
2.多线程
3.程序、进程、线程
  • 程序:指令和数据的有序集合,其本身没有任何运行含义,是一个静态概念。
  • 进程:是程序的一次执行过程,是动态概念。是系统分配的基本单位。
  • 线程:一个进程通常包含若干个线程,是cpu调度和执行的基本单位。

多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。

二、创建线程

1.继承Thread类
  • 自定义线程类继承Thread类

  • 重写run()方法,编写线程执行体

  • 创建线程对象,调用star()方法启动线程

    public class MyThread extends Thread
        private int key = 100;
        public synchronized int getKey()
            return key;
        
        public synchronized void setKey()
            this.key -= 1;
        
        @Override
        public void run()
            while(getKey()>0)
                System.out.println("MyThread");
                setKey();
            
        
        
        //主线程
        public static void main(String []args)
            MyThread myThread = new Mythread();//创建线程
    
            myThread.start();//启动线程
            while(myThread.getKey()>0)
                System.out.println("主线程");
                myThread.setKey();
            
        
    
    
2.实现Runnable接口
  • 定义Runnable接口实现类

  • 实现run()方法,编写线程执行体

  • 创建线程对象,调用start()方法启动线程

  • 解决了单继承的局限性

    public class MyRunnable implements Runnable
        private int key = 100;
        public synchronized int getKey()
            return key;
        
        public synchronized void setKey()
            this.key -= 1;
        
        @Override
        public void run()
            while(getKey()>0)
                System.out.println("MyThread");
                setKey();
            
        
        //主线程
        public static void main(String []args)
            MyRunnable myRunnable = new MyRunnable();
    		Thread myThread = new Thread(myRunnable);//创建线程
            myThread.start();//启动线程
            while(myRunnable.getKey()>0)
                System.out.println("主线程");
                myRunnable.setKey();
            
        
    
    
3.实现Callable接口
public class MyCallable implements Callable<Boolean>
    @Override
    public Boolean call()
        return true;
    
    public static void main(String[]args)
        MyCallable c1 = new MyCallable();
        MyCallable c2 = new MyCallable();
        
        ExecutorService service = Executors.newFixedThreadPool(2);
        
        Future<Boolean> result1 = service.submit(c1);
        Future<Boolean> result2 = service.submit(c1);
        
        Boolean b1 = result1.get();
        Boolean b2 = result2.get();
        
        service.shutdownNow();
    

  • 实现Callable接口需要返回值类型

  • 重写call方法需要抛出异常

  • 创建目标方法

  • 创建执行服务:ExecutorSevice service = Executors.newFixedThreadPool(1);//创建1个服务

  • 提交执行:Future< Boolean> result = service.submit(thread);//添加服务thread

  • 获取结果:Boolean b = result.get();

  • 关闭服务: service.shutdownNow();

三、线程状态

1.线程停止

stop()方法;//不建议使用,已被jdk弃用

2.线程休眠
  • sleep(int time);
  • 存在InterruptedException异常;
  • 休眠时间完成后,线程进入就绪状态等待CPU调度;
  • 调用sleep()方法的线程不会释放锁,锁依然被调用者占用;
3.线程暂停
  • yield();
  • 线程进入就绪状态,不阻塞;
  • 线程重新进入CPU就绪队列,等待CPU调度;
  • 调用者会释放锁。
4.合并线程
  • join();
  • 插入线程后,其他线程进入阻塞队列,待插入线程执行完成后释放CPU资源后再进入就绪队列等待处理机调度。
5.线程状态

Thread.State:

  • NEW:线程创建成功但未启动;
  • RUNNABLE:线程正处于JVM中执行;
  • BLOCK:线程阻塞,等待监视器锁定;
  • WAITING:等待CPU释放资源,此时资源被其他线程占用;
  • TIMED_WAITING:等待其他线程执行特定动作达到特定时间的线程;
  • TERMINATED:线程执行已完成,线程死亡。此时线程失去star()机会。
6.线程优先级

Thread.Priority:

  • MIN_PRIORITY = 1;//最小优先级
  • MAX_PRIORITY = 10;//最大优先级
  • NORM_PRIORITY = 5;//默认优先级

优先级高低意味着获得CPU调度的概率高低,高优先级线程等待低优先级线程释放资源会造成性能倒置问题;

设置优先级:setPriority(int num);

获取优先级:getPriority();

7.守护线程

Thread.daemon:

  • 用户线程

  • 守护线程

    1. JVM会确保用户线程执行完毕
    2. JVM不需要等待守护线程的执行完成
    3. 守护线程一般有:操作日志、监控内存、GC垃圾回收等。

四、线程同步

多个线程操作同一资源

1.并发和并行
  1. 并发:一个时间段的线程代码运行时,其它线程处于挂起状;
  2. 并行:当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行.
2.队列和锁

当一个线程获得对象排它锁,独占资源时,如果此时其他线程需要获得此对象,则必须等待该线程释放这个锁才能拥有获得此锁的机会。

  • 一个线程持有锁会导致其他竞争此锁的进程挂起;
  • 多线程场景下,加锁与释放锁会导致进程上下文切换以及CPU调度延时,引起性能问题;
  • 同样,性能倒置问题的发生会引起性能问题。
3.同步方法

synchronized同步方法控制资源的访问,即每个资源都使用了一把锁,每个synchronized方法必须获得使用该资源的锁才能访问,否则需要访问此资源的线程进入阻塞队列等待资源的释放,同步方法一旦执行,执行线程则独占该锁,直到执行结束后才释放此锁。

public synchronized void fun()
    /*
    *执行代码块
    */

4.同步块
public void fun(Object obj)
    synchronized(obj)
        /*
        *执行代码块
        */
    

  • obj:同步监视器,可以是任意对象,一般使用共享资源作为同步监视器。
  • 同步方法中无需指定同步监视器,同步方法中的监视器为this,是对象本身;
  • 同步监视器执行流程:
    1. 线程1访问共享资源,锁定同步监视器,开始执行同步代码块;
    2. 此时线程2也要访问该同步资源,发现同步监视器已经被锁定,则访问不能进行,开始等待同步监视器被释放;
    3. 当线程1执行完成同步代码块,线程1立即释放同步监视器,资源解锁;
    4. 线程2对共享资源进行访问,同时锁定同步监视器后进入同步代码块;
    5. 线程2对同步资源的访问完成,释放同步监视器,接触对共享资源的占用。
5.死锁
  1. 多个线程各自占用共享资源,并相互等待其他线程占有的资源才能运行;
  2. 死锁导致两个或多个线程都在等待对方释放资源;
  3. 产生死锁的四个必要条件:
    • 互斥条件:一个资源每次只允许一个进程使用;
    • 请求与保持:一个进程因请求与保持时,已获得的资源保持不变;
    • 不可剥夺条件:进程未使用完获得的资源时,不能强行剥夺;
    • 循环等待条件:若干资源之间形成一种头尾相接的循环等待资源关系。
6.Lock锁
  1. 加锁与释放锁

    ReentrantLock lock = new ReentrantLock();
    public void fun()
        lock.lock();//显式加锁
        /*
        *同步代码块
        */
        lock.unlock();//显式解锁
    
    
  2. Lock锁通过显式定义锁对象实现同步;

  3. Lock锁提供了对共享资源的独占访问,即每次只有一个线程对Lock锁对象加锁,线程访问共享资源前需要获得锁对象;

  4. ReentrantLock实现了Lock接口,拥有与synchronized相同的并发性内存语义,在实现线程安全的控制中被常用;

  5. synchronized与Lock的对比:

    • Lock是显示锁(手动开关锁),而synchronized是隐式锁,离开作用域锁自动释放;
    • Lock只有代码块锁,而synchronized拥有方法锁和代码块锁;
    • 使用Lock锁,JVM调度线程时花费的时间较少;
    • 锁的优先使用顺序:Lock > 同步代码块 > 同步方法。

五、线程通信

1.线程通信的方法
  • wait();//线程等待
  • notify();//通知
  • notifyAll();//通知全部其他线程
2.生产者消费者模式

(1)管程法
//产品类
public class Product
    String name;
    public Product() 
    
    public Product(String name) 
        this.name = name;
    
    public String getName() 
        return name;
    
    public void setName(String name) 
        this.name = name;
    

//消费者
public class User implements Runnable
    Factory factory;
    User()
    User(Factory factory)
        this.factory = factory;
    
    @Override
    public void run() 
        for (int i = 0; i < 20; i++) 
            factory.getProduct();
    

//生产者
public class Productor implements Runnable
    Factory factory;
    Productor()
    Productor(Factory factory)
        this.factory = factory;
    
    @Override
    public void run() 
        for (int i = 0; i < 20; i++)
            factory.setProduct(new Product("产品:"+(i+1)));
    


//工厂类
public class Factory

    final int PRODUCT_MAX_NUM = 10;
    //产品集合
    Product [] products = new Product[PRODUCT_MAX_NUM];

    //产品计数器
    int proCount=0;

    //生产产品
    public synchronized void setProduct(Product item)
        if(proCount == PRODUCT_MAX_NUM-1)
            System.out.println("产品达到了库存,停止生产...");
            try 
                wait();
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
        products[proCount] = item;
        proCount++;
        System.out.println("生产者生产了产品:"+item.getName()+",通知消费者消费...");
        notifyAll();
    
    //消费产品
    public synchronized Product getProduct()
        if (proCount == 0)
            System.out.println("没有产品可以消费了...");
            try 
                wait();
             catch (InterruptedException e) 
                e.printStackTrace();
            
        

        Product item = products[proCount-1];

        System.out.println("消费者消费了产品:"+item.getName());
        notifyAll();
        proCount--;
        return item;
    
    
    public static void main(String[] args) 
        Factory factory = new Factory();

        new Thread(new Productor(factory)).start();
        new Thread(new User(factory)).start();
    

(2)信号灯法
//工厂类
public class Factory

    final int PRODUCT_MAX_NUM = 10;
    //产品集合
    Product [] products = new Product[PRODUCT_MAX_NUM];

    //产品计数器
    int proCount=0;

    //信号量,
    boolean single = false;

    //生产产品
    public synchronized void setProduct(Product item)
        if(proCount == PRODUCT_MAX_NUM)
            single = true;
            System.out.println("产品达到了库存,停止生产...");
            try 
                wait();
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
        products[proCount] = item;
        single = true;
        proCount++;
        System.out.println("生产者生产了产品:"+item.getName()+",通知消费者消费...");
        notifyAll();
    
    //消费产品
    public synchronized Product getProduct()
        if (!single)
            System.out.println("没有产品可以消费了...");
            try 
                wait();
             catch (InterruptedException e) 
                e.printStackTrace();
            
        

        Product item = products[proCount-1];

        System.out.println("消费者消费了产品:"+item.getName());
        notifyAll();
        single = --proCount != 0;
        return item;
    
    public static void main(String[] args) 
        Factory factory = new Factory();

        new Thread(new Productor(factory)).start();
        new Thread(new User(factory)).start();
    

3.线程池
//1-使用ThreadPoolExecutor()构造方法直接创建线程池实例
ExecutorService es = new ThreadPoolExecutor(
  10,//corePoolSize:线程核心池大小1
  10,//maximunPoolSize:最大线程数10
  10L,//keepAliveTime:线程没有任务后保持10毫秒后停止
  TimeUnit.MICROSECONDS,//以ms为单位
  new LinkedBlockingQueue<Runnable>()
);

//2-创建一个固定容量为10的线程池
ExecutorService es1 = Executors.newFixedThreadPool(10);
/*源码
public static ExecutorService newFixedThreadPool(int nThreads) 
 return new ThreadPoolExecutor(
 		 nThreads, 
 		 nThreads,
       0L, 
       TimeUnit.MILLISECONDS,
       new LinkedBlockingQueue<Runnable>());
  
*/

//3-创建一个可缓存的线程池,不对线程池大小做现在,
//  线程池大小取决于OS或者说JVM能够创建的最大线程数
//  线程没有任务后保持60秒后停止
ExecutorService es2 = Executors.newCachedThreadPool();
/*源码
public static ExecutorService newCachedThreadPool() 
  return new ThreadPoolExecutor(
        0, 
  	  Integer.MAX_VALUE,
        60L, 
        TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>());

*/
//4-创建一个单线程化的线程池,使用唯一的工作线程来执行任务
ExecutorService es3 = Executors.newSingleThreadExecutor();
/*源码
public static ExecutorService newSingleThreadExecutor() 
  return new FinalizableDelegatedExecutorService(
      new ThreadPoolExecutor(
          1, 
          1,
          0L, 
          TimeUnit.MILLISECONDS,
          new LinkedBlockingQueue<Runnable>()));

*/

//创建一个固定大小的线程池,支持定时周期任务
ExecutorService es4 = Executors.newScheduledThreadPool(10);
/*源码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
  return new ScheduledThreadPoolExecutor(corePoolSize);

*/

  • 优点:

    • 提高响应速度
    • 降低资源消耗
    • 便于线程的管理
  • 内容:

    • ExecutorService和Executors;
    • corePoolSize:线程核心池大小;
    • maximunPoolSize:最大线程数;
    • keepAliveTime:线程没有任务后保持多久后停止
  • ThreadPoolExecutor是ExecutorService的子类

    • void executor(Runnable able):无返回值执行任务,传递参数为Runnable;

       ExecutorService es = Executors.newFixedThreadPool(10);//创建一个容量为10的线程池
       es.submit(new Runnable() 
                  @Override
                  public void run() 
                      System.out.println("Runnable接口");
                  
              );
      //es.submit(()-> System.out.println("Runnable接口"));//使用lamda表达式简写
      es.shutdown();//关闭线程池
      
    • < T> Future< T> submit(Callable < T> able):带返回值执行任务,传递参数为Callable;

              ExecutorService es = Executors.newFixedThreadPool(10);
      
              FutureTask<String> ft = new FutureTask<String>(new Callable<String>() 
                  @Override
                  public String call() throws Exception 
                      return "Callable接口";
                  
              );
      
              es.submit(ft);
      
              try 
                  System.out.println(ft.get());//获取执行结果
               catch (InterruptedException e) 
                  e.printStackTrace();
               catch (ExecutionException e) 
                  e.printStackTrace();
              
      
              es.shutdown();//关闭线程池
      
  • Executors:工具类,线程池工厂类,用于创建并返回不同类型线程池;

总结

好了,关于多线程也就到此告一段落吧!
java这条路通往何方?张三又该何去何从?
一切都没有定数!
我们只知道他拿走了那本书,
拿走的不是简简单单几张纸,
拿走的是他的青春和无穷无尽地“快乐”,
张三也许会说:"不就是java多线程吗?我分分钟肝到底!"

但是他永远不会知道,等待他的后果绝对不是他想要的结果,而那个阿姨,她将用她的一生来治愈她失去的《java从入门到放弃》,是的,张三他输了,他不是输给了面试官,也不是输给了多线程!他失败得彻彻底底,他失败得一塌糊地!

以上是关于撸一撸多线程的来龙去脉你就知道,为什么面试官折磨喜欢问这种问题了!的主要内容,如果未能解决你的问题,请参考以下文章

撸一撸多线程的来龙去脉你就知道,为什么面试官折磨喜欢问这种问题了!

撸一撸多线程的来龙去脉你就知道,为什么面试官折磨喜欢问这种问题了!

来撸一撸Dubbo之SPI机制源码,SPI到底解决了什么问题?

撸一撸那些一行代码实现的神奇功能

淘宝京东双十一撸一撸

撸一撸Spring Cloud Ribbon的原理-负载均衡策略