JavaSE(15)——多线程起源

多线程起源

1. 为什么使用多线程?

首先需要明确的一点,为什么要使用多线程?

在一般人看来,使用多线程是为了加快程序运行的速度。

其实不然,使用多线程最主要的原因是提高系统的资源利用率

现在的CPU基本都是多核的,如果只用单线程的话,就只用到了一个核心,其他的核心都在闲置。

CPU的核心有五个,如果只使用一个核心,那工作效率如何?比如现在有5个程序需要执行。

在单线程的时候,一个程序的执行需要10分钟,但是后面的程序都得等一个核心,那总共花费的时间就是50分钟。

而在多线程情况下,一个程序开始执行,后面的程序发现还有别的核心,就去别的核心执行,而不是傻瓜式地等同一个核心。

事实上,可以把CPU调度看做IO操作,IO操作相对于CPU而言是非常慢的,CPU等待IO那段时间是空闲的。如果需要做类似IO这种慢的操作,可以开多个线程来,尽量不要让CPU空闲下来,这样才能提高CPU的资源利用率。

说白了,其实多线程就是在压榨CPU的资源。将CPU原有的资源,充分的利用起来。

多线程不是万金油,并不是说线程越多,CPU的资源利用效率就越好。执行IO操作时线程可以适当的多一些,因为很多时候CPU是相对空闲的。如果是计算型的操作,本来CPU就不空闲,还开很多线程就更忙了。(有多线程就会有线程切换的问题,线程切换是需要耗费资源的)

2. 多线程的使用场景

多线程使用的地方其实很多,只是平时意识不到它的存在。

比如使用的最多的Web容器,Tomcat,它就是以多线程去响应请求的,我们可以在server.xml中配置连接池的配置。

<Connector port="8080" maxThreads="350" maxHttpHeaderSize="8192" minSpareThreads="45" maxPostSize="512000" protocol="HTTP/1.1" enableLookups="false" redirectPort="8443" acceptCount="200" keepAliveTimeout="15000" maxKeepAliveRequests="-1" maxConnections="25000" connectionTimeout="15000" disableUploadTimeout="false" useBodyEncodingForURI="true" URIEncoding="UTF-8" />

Tomcat处理每一个请求都会从线程连接池里面用一个线程去处理,这显然是多线程操作。然后这个请求线程找到Servlet,执行对应的service方法。

而service方法是无状态的,多个线程请求service方法,往往没有操作共享变量,不操作共享变量就不会有线程安全问题。

平时常用的SpringMVC其实也是一样的,底层也是Servlet

还有在连接数据库的时候,也会用对应的连接池 (Druid,C3P0等),比如场景的Druid配置:

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 
     <property name="url" value="${jdbc_url}" />
     <property name="username" value="${jdbc_user}" />
     <property name="password" value="${jdbc_password}" />

     <property name="filters" value="stat" />

     <property name="maxActive" value="20" />
     <property name="initialSize" value="1" />
     <property name="maxWait" value="60000" />
     <property name="minIdle" value="1" />

     <property name="timeBetweenEvictionRunsMillis" value="60000" />
     <property name="minEvictableIdleTimeMillis" value="300000" />

     <property name="testWhileIdle" value="true" />
     <property name="testOnBorrow" value="false" />
     <property name="testOnReturn" value="false" />

     <property name="poolPreparedStatements" value="true" />
     <property name="maxOpenPreparedStatements" value="20" />

     <property name="asyncInit" value="true" />
 </bean>

3. 多线程知识点

  • 线程和进程的区别
  • Thread类的常见方法
  • 如何解决线程安全性问题
  • synchronized和Lock锁的区别
  • 什么是AQS、ReentrantLock和ReentrantReadWriteLock锁
  • JDK自带的线程池有哪几个,线程池的构造方法重要的参数
  • 什么是死锁,如何避免死锁?
  • CountDownLatch、CyclicBarrier、Semaphore是什么?
  • Atomic包下的常见子类
  • 什么是CAS,CAS会有什么问题?
  • ThreadLocal是什么?

常见的问题

  1. 多线程了解多少啊?使用多线程会有什么问题?你是怎么理解“线程安全”的?
  2. 如果我现在想要某个操作等待线程结束之后才执行,有什么方法可以实现?为什么要用CountDownLatch?CountDownLatch的底层是什么?(引出AQS)
  3. synchronized关键字来说一下,它的用途是什么?synchronized底层的原理是什么?
  4. 线程安全的容器有哪些?(着重于ConcurrentHashMap、CopyWriteOnArrayList与其他非线程安全容器的区别以及它们的具体实现)
  5. ThreadLocal你了解过吗?主要是用来干什么的?具体的源码实现原理来说一下吧
  6. 产生死锁的条件是什么?我们可以如何避免死锁?(可延伸到操作系统层面上的死锁)
  7. synchronized锁和ReentrantLock锁有什么区别呀?
  8. 线程池你应该也看过吧,来说说为什么要用线程池。JDK默认实现了几个线程池,分别有xxx(自然地ThreadPoolExecutor构造函数的常用几个参数你也得一起说出来)

4. 线程池的作用

控制器触发了以后,我们直接将这个任务交给一个线程池去处理,交由线程池后就直接返回SUCCESS

这样做的好处是什么?如果多个任务同时触发,那可能某些任务执行时间过长,请求可能会被阻塞住,而我们如果放在线程池中可以提高系统的吞吐量。

使用线程池的时候,往往我们的调用方都不需要考虑请求是否立马处理成功。假设线程池在处理任务的时候因为某些原因失败了,我们可以走报警机制(用邮件/短信等渠道去提醒请求方即可)。

在使用线程池的时候,很多时候我们也是把他当做异步来使用,只要我们的系统之间交互不是强一致性的,又希望提高系统的吞吐量,我们就可以考虑使用线程池。

5. 借助工具类实现线程安全

  • 如果是集合,我们可以考虑一下juc包下的集合类。
  • 如果是数值/对象,我们可以考虑一下atomic包下的类。
  • 如果是涉及到线程的重复利用,我们可以考虑一下是否要用线程池。
  • 如果涉及到对线程的控制(比如一次能使用多少个线程,当前线程触发的条件是否依赖其他线程的结果),我们可以考虑CountDownLatch/Semaphore等等
  • 如果synchronized无法满足,我们可以考虑lock包下的类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值