一文搞懂Java的多线程底层逻辑,再也不怕多线程了

Posted 香菜聊游戏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文搞懂Java的多线程底层逻辑,再也不怕多线程了相关的知识,希望对你有一定的参考价值。

目录

1、线程是什么

2、启动线程

3、线程池

4、线程池的创建

通过Executors工厂方法创建

通过构造函数创建

5、调试线程

6、synchronized关键字


没什么想说的,就是想写两次Java的,顺便送两本书,好了,开始吧

1、线程是什么

线程是操作系统调度的最小单元,也叫轻量级进程。它被包含在进程之中,是进程中的实际运作单位。同一进程可以创建多个线程,每个进程都有自己独立的一块内存空间。并且能够访问共享的内存变量。

总之:线程就是一个电脑的工作单位,可以直接类比现实生活中的一个"劳动力"(一个人)

举个例子,开了一家餐厅,餐厅这个实体就是进程,餐厅里的服务员就是线程,餐厅里的座位就是资源(游戏内的数据),所有的服务员都可以安排客人就座,多个服务员安排座位就是多线程竞争,锁也就是去排号。线程池就是有多个服务员一直站在那里等着被呼叫。

进程和线程的区别可以通俗理解为进程是一个公司,而线程是公司里的工作人员,真正干活的还是个人

2、启动线程

java创建线程的三种方式:

  1. *继承Thread类创建线程类*,无法继承其他类。

  2. *实现Runnable接口*

  3. *通过Callable和Future创建线程*

    package thread;
    /**
    * @author 香菜
    */
    public class ExtendThread extends Thread 
      @Override
      public void run() 
          System.out.println("ExtendThread");;
      
    
    package thread;
    
    import java.util.concurrent.Callable;
    /**
    * @author 香菜
    */
    public class ImpCallable implements Callable<Integer> 
      @Override
      public Integer call() throws Exception 
          System.out.println("Callable ");
          return 1;
      
    
    package thread;
    
    import java.util.concurrent.FutureTask;
    
    /**
    * @author 香菜
    */
    public class Aain 
      public static void main(String[] args) 
          new ExtendThread().start();
          new Thread(new ImpRunnable()).start();
          new Thread(new FutureTask<>(new ImpCallable())).start();
      
    

    总结:线程的概念来自于生活,理解了概念,在项目中思考的时候只要搞清楚项目的线程模型,基本上不会遇到太大的问题

3、线程池

线程池存在的原因是为了节省创建线程和销毁线程的系统损耗,这样说可能不太好理解,我们直接通俗点解释。

我们做了一个饭馆,大家都知道饭馆的营业时间是有周期性的,也就是饭点的时候客人才多,在其他的时间饭店里肯定是没有人的,我们怎么样雇人帮忙呐?

假如我们在看到客人多的时候感觉招募两个店员,然后开始让他们进行干活,饭点过了就开掉,这样的话就是开启一个线程,没事做了赶紧销毁掉。这样的处理逻辑明显不符合饭馆的操作,大家都知道每个饭馆的工作人员基本上都是固定的,为什么这样呐?首先开启线程,也就是招聘会有开销,比如发广告,面试,这些都很费时间,而且招到以后还要培训,如果用完之后直接开掉,肯定是不合适的,所以这个时候我们需要一些长期的工作人员维持在店里,也就是线程池了。

线程池的原理是同样的:保留一部分的线程在系统内,避免线程的创建和销毁的系统消耗,随取随用。

4、线程池的创建

java中创建线程池的方式一般有两种:

通过Executors工厂方法创建

通过new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)自定义创建

通过Executors工厂方法创建

Executor 提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节、调度等)分离开来的方法。

 相当于manager,老板让manager去执行一件任务,具体的是谁执行,什么时候执行,就不管了。

 介绍几个

内置的线程池基本上都在这里

newScheduledThreadPool 定时执行的线程池

newCachedThreadPool 缓存使用过的线程

newFixedThreadPool 固定数量的线程池

newWorkStealingPool 将大任务分解为小任务的线程池

通过构造函数创建

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) ;

int corePoolSize :表示线程池的数量,常态下的线程数量

int maximumPoolSize :表示线程池最大的线程池数量,极限情况下的最大数量

long keepAliveTime :表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了

TimeUnit unit :参数keepAliveTime的时间单位,

BlockingQueue<Runnable> workQueue : 一个阻塞队列,用来存储等待执行的任务

常见选择有:

ArrayBlockingQueue 少用
LinkedBlockingQueue 常用
SynchronousQueue 常用
PriorityBlockingQueue 少用

ThreadFactory threadFactory :用于设置创建线程的工厂,可以设置线程的名字和优先级等等

RejectedExecutionHandler handler:看类名也能猜到是拒绝处理任务时的策略。主要有下面几种可以选择

1、AbortPolicy:直接抛出异常。
2、CallerRunsPolicy:只用调用者所在线程来运行任务。
3、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
4、DiscardPolicy:不处理,丢弃掉。
5、也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

5、调试线程

多线程的调试可能是一些同学不太会,大概说下怎么回事

下面创建了一个10个线程的线程池,并提交了3个任务,运行的时候生成了三个线程创建了一个3个线程的线程池,提交了3个任务,运行的时候生成了三个线程

ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.submit(()-> System.out.println("1111111111111111111111"));
executorService.submit(()-> System.out.println("222222222222222222"));
executorService.submit(()-> System.out.println("3333333333333333333333"));

添加断点:

添加断点应该每个同学都会,就是在调试的时候需要注意下面两个选项

All 就是在断点发生的时候,会将整个虚拟机停住,也就是所有的线程都会暂停

Thread 就是断住当前线程,其他的线程不收影响,在调试多线程的时候一定要选择这个,测试多个线程的并行

6、synchronized关键字

每个java对象头中都有锁状态位标记。java中在使用synchronize同步的时候,肯定是涉及到某个对象的锁。因此,在考虑同步的时候,首先要想到是同步的是哪个对象的锁。

每个对象都和一个monitor对象关联,主要用来控制互斥资源的访问,如果你想要加锁必须先获得monitor的批准,如果现在正有线程访问,会把申请的线程加入到等待队列。在对临界资源的加锁的时候会调用monitor_enter,离开的时候会monitorexit 释放锁

1、 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对class对象的锁,该类所有的对象同一把锁。

2、每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。

3、实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制,避免做嵌套synchronized 的使用。

4、synchronized 要尽量控制范围,不能范围太大,否则会损失系统性能。

最后常规送书:

本书共有15章,内容涉及Java线程堆栈分析、性能瓶颈分析、内存泄漏分析和堆内存设置、并发和多线程、幽灵代码、常见的Java陷阱、数据库、字符集与编码、JVM运行参数、常用问题定位工具、计算架构与存储架构、开发语言等的选择、设计软件系统、工程实践、常见案例等内容。


京东自营购买链接:
https://item.jd.com/12711001.html

当当自营购买链接:
http://product.dangdang.com/29115495.html

大家点赞关注,三天后在留言的同学中抽取送两本书

注:如果中奖了没关注则放弃

以上是关于一文搞懂Java的多线程底层逻辑,再也不怕多线程了的主要内容,如果未能解决你的问题,请参考以下文章

一文搞懂Java的多线程底层逻辑,再也不怕多线程了

一文让你彻底搞懂多线程

一文搞懂 Java 线程中断

一文搞懂 Java 线程中断

从jvm的角度来看java的多线程

从jvm的角度来看java的多线程