一文搞懂Java的多线程底层逻辑,再也不怕多线程了
1、线程是什么
线程是操作系统调度的最小单元,也叫轻量级进程。它被包含在进程之中,是进程中的实际运作单位。同一进程可以创建多个线程,每个进程都有自己独立的一块内存空间。并且能够访问共享的内存变量。
总之:线程就是一个电脑的工作单位,可以直接类比现实生活中的一个"劳动力"(一个人)
举个例子,开了一家餐厅,餐厅这个实体就是进程,餐厅里的服务员就是线程,餐厅里的座位就是资源(游戏内的数据),所有的服务员都可以安排客人就座,多个服务员安排座位就是多线程竞争,锁也就是去排号。线程池就是有多个服务员一直站在那里等着被呼叫。
进程和线程的区别可以通俗理解为进程是一个公司,而线程是公司里的工作人员,真正干活的还是个人
2、启动线程
java创建线程的三种方式:
*继承Thread类创建线程类*,无法继承其他类。
*实现Runnable接口*
*通过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 要尽量控制范围,不能范围太大,否则会损失系统性能。
- 点赞
- 收藏
- 关注作者
评论(0)