1、多线程简介
1.1线程和进程
Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。**
(进程——自我理解——一个正在执行的程序,称之为一个进程。)
(线程——本身没有资源,所有线程共享进程的资源。)
同一时间,一个CPU(一个核),只能处理一个线程!!!
你在任务管理器中查看到的几千个线程,并不不是在同一时间执行的。
1.4 线程的交互
交互的方式 包括 : 互斥 、同步。
争用条件
互斥
同步
1.5 Java对线程的支持
1.6 如何正确地停止一个线程?
不正确的线程停止方法
stop()调用后,会不知道,线程执行到了哪里,甚至,还没有执行完,就被突然停止了。会造成很多问题。比如,如果此处涉及到事务,那么,事务,可能就不会被提交或者出现异常后,不会被回滚。
不正确的停止线程的方法2:
注意:此时,线程的中断状态,就会被清除,再次调用interrupted()方法,就会返回false。
正确停止线程的方法——使用退出标志
1.7 Daemon 守护线程和 用户线程
daemon线程解释: 当线程只剩下守护线程的话,jvm会退出。但是如果还有其他的任意一个用户线程还在,jvm就不会退出。main线程是用户线程。
守护线程和主线程有着同样的生命周期,主线程不存在,子线程也就不存在了
非守护线程和主线程无关,主线程关闭,子线程继续执行
1.8 可重入锁
A:概念
最大的作用是可以多次获取该锁,而避免死锁。
电影院售票示例
B:可重入所实现原理
C:可重入锁应用场景
D:注意事项
- lock.lock() ,不要放在try语句里面,防止获取锁失败,直接释放锁。
- 获取锁的次数和释放锁的次数要一致!
E:使用方式
1. 一般在成员变量位置,定义一个锁(对象锁?)。在方法(临界区)进行上锁。finally中一定要unlock.
1.9 多线程的理解
- (1) 程序可以同时由多个线程执行多个方法(代码)
3、线程池
3.1 线程池的创建
- 阿里巴巴开发规范
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
Positive example 1:
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
Positive example 2:
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
Junit 与 Java多线程
暂时,无法正确测试
总结
1、线程创建后,调用线程的start()方法开启一个线程,线程开启后,每个线程会执行线程的核心业务方法run()方法 。并且只会执行一次。
2、创建线程类的三种方式:
继承Thread 类
实现 Runnable 接口
实现Callable 或者Futre接口
5、并发工具类
A:CountDownLatch
参考源码中的文档查看:
B:FutureTask 配合Callable接口使用
6、Java原子类以及实现原理
7、悲观锁和乐观锁
悲观锁: Synchronized
乐观锁:CAS 机制
8、 线程编写经验
- (1)run方法中,会写一个while 无限循环,在特定条件下使用return结束任务。
- (2)在构造器中启动线程将会产生问题,因为,另一个任务可能会在构造器结束之前,开始执行,这意味着,该线程可以访问不稳定状态的对象。
- (3)使用内部类隐藏线程代码
9、 线程中异常的处理
- (1) 异常不能够跨线程传播,必须在任务方法中进行捕获处理。
二、多线程编程思想
2.1 多线程的使用场景?
-
1. 一次请求很久才能响应
当主线程在处理一件事情时,发现在处理过程中,会消耗很长时间。主线程可能会等待后续的响应。此时,可以使用多条子线程来处理后续的任务。
示例:主线程一次性查询出 线程数量 * 1000 条数据从数据库。然后使用线程池,提交一定数量的线程来处理这些数据。
三、Java并发编程的艺术
3.1 上下文切换
A:Java性能调优工具
- jstack
Jstack是Jdk自带的线程跟踪工具,用于打印指定Java进程的线程堆栈信息。
配合使用Linux文本分析命令分析
grep | awk | sort | uniq -c