Java必会之多线程
一、线程的基本知识
1.1 线程知识
进程和线程的关系和区别
线程:
线程是进程的基本执行单元,进程想要执行任务,必须要有线程。程序启动默认开启一条线程,这个线程被称为主线程。
进程:
进程是指在系统中正在运行的一个应用程序。每个进程之间是独立的,每个进程均运行在其专用且受保护的内存里。
线程的六个状态:
NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
Thread流程图:

Thread的方法:
| 方法 | 说明 |
|---|---|
| void join() | t.join() 当前线程调用其他线程的t.join()方法,当前线程进入等待状态,当前线程不会释放已经持有的锁。线程t执行完毕后,当前线程进入就绪状态。 |
| static native void sleep() | 静态方法,线程睡眠,并让出CPU时间片 |
| void wait() | 当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒。 |
| native void notify() | 唤醒在此对象监视器上等待的单个线程,选择是任意性的。 |
| native void notifyAll() | 发送信号通知所有等待线程 |
1.2 线程安全
并发的相关性质:
原子性:原子操作。对基本数据类型的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
可见性:对于可见性,java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,他会保证修改的值会立即被更新到主存,当有其他线程需要读取时,他会去主存中读取新值。
volatile 不保证原子性。
有序性:Java允许编译器和处理器对指令重排序,但是重排序不会影响到单线程的执行,却会影响到多线程并发执行的正确性。
synchronized
使用对象头标记字实现
使用场景:
- 修饰方法:
一个对象中的加锁方法只允许一个线程访问。 - 修饰静态方法:
由于静态方法是类方法,所以多个线程不同对象访问这个静态方法,也是可以保证同步的。 - 修饰代码块:
如:synchronized(obj){...} 这里的obj可以是类中的一个属性,也可以是对象,这时他跟修饰普通方法一样,如果obj是Object.class这样的,那么效果跟修饰静态方法类似。


volatile
- 每次读取都强制从主内存刷数据
- 适用场景:单个线程写,多个线程读
- 原则:能不用就不用,不确定的时候也不用
- 语义
- 可见性
- 禁止指令重排序(不完全保证有序性)
- 不能保证原子性。
为什么不保证有序性呢?举个例子说明

上述代码,语句1和2,不会被重排到3的后面,4和5也不会到3的前面。但是1和2的顺序、4和5的顺序无法保证。
final
| final定义类型 | 说明 |
|---|---|
| final class XXX | 不允许继承 |
| final 方法 | 不允许Override |
| final 局部变量 | 不允许修改 |
| final 实例属性 | 构造函数、初始化块后不能变更。只能赋值一次。构造函数结束返回时,final域最新的值保证对其他线程可见。 |
| final static 属性 | 静态块执行后不允许变更,只能赋值一次 |
二、线程池
- Executor :执行者 -顶层接口
- ExecutorService :继承于Executor,线程池的接口API
- ThreadFactory:线程工才
- Executors:工具类
submit方法和execute方法的区别:
- submit方法:有返回值,用Future封装,执行的方法异常了可以在主线程里catch到。
- execute方法:无返回值,方法执行异常是捕捉不到的
如下图:

ExecutorService主要方法:

构造线程池的参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize :核心线程数
maximumPoolSize:最大线程数
keepAliveTime:当线程数超过了核心线程数,空闲线程需要等待多久等待不到新任务就终止。
workQueue:任务队列
threadFactory:线程工厂
handler:拒绝策略
提交任务逻辑:
- 判断核心线程数
- 加入workQueue
- 判断最大线程数,没达到就创建
- 执行拒绝策略(默认是抛异常)
缓冲队列
- ArrayBlockingQueue:规定大小的BlockingQueue,构造时必须指定大小
- LinkedBlockingQueue:大小不固定的BlockingQueue,如果构造时指定大小,则有大小限制,不指定大小,则用Integer.MAX_VALUE来决定
- PriorityBlockingQueue:类似于LinkedBlockingQueue,不同在它根据对象的自然顺序或者构造函数的Comparator进行排序,不是FIFO
- SynchronizedQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成。
拒绝策略:
- AbortPolicy:丢弃任务并抛异常
- DiscardPolicy:丢弃任务,不抛异常
- DiscardOldestPolicy:丢弃队列最前面的任务,重新提交被拒绝的任务
- CallerRunsPolicy:由提交任务的线程处理该任务。
线程工厂(ThreadFactory):
自定义示例:
public class CustomThreadFactory implements ThreadFactory {
private AtomicInteger count=new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
Thread thread=new Thread(r);
thread.setDaemon(false);
thread.setName("customThread-"+count.getAndIncrement());
return thread;
}
}
线程工具类:
newSingleThreadExecutor
创建一个单线程的线程池。如果这个线程因为异常结束,那么会有一个新的线程替代它。此线程池保证所有的任务的执行顺序按任务提交顺序执行。
newFixedThreadPool
创建固定大小的线程池。缺点:队列使用的LinkedBlockingQueue,且没有限制大小。
newCachedThreadPool
创建一个可缓存的队列,如果线程池大小超过了处理任务需要的线程,那么就会回收部分空闲线程。缺点:此线程池不会对线程池大小做限制。
newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
创建固定线程池的经验:
假设服务器核心数为N
- 如果是CPU密集型应用,则线程池大小设置为N或N+1
- 如果是IO密集型应用,则线程池大小设置为2N或2N+2
Java必会之多线程的更多相关文章
- java必背面试题
JAVA必背面试题和项目面试通关要点 一 数据库 1.常问数据库查询.修改(SQL查询包含筛选查询.聚合查询和链接查询和优化问题,手写SQL语句,例如四个球队比赛,用SQL显示所有比赛组合:举例2:选 ...
- 学习Java必看书籍和步骤(转载)
原地址:http://blog.csdn.net/yongjian1092/article/details/7372678 Java语言基础 谈到Java语言基础学习的书籍,大家肯定会推荐Bruce ...
- 学习Java必看书籍和步骤
Java语言基础 谈到Java语言基础学习的书籍,大家肯定会推荐Bruce Eckel的<ThinkinginJava>.它是一本写的相当深刻的技术书籍,Java语言基础部分基本没有其它 ...
- Java学习手记2——多线程
一.线程的概念 CPU执行程序,就好比一个人在干事情一样,同一个时间你只能做一件事情,但是这样的效率实在是太低了,在你用电脑的时候,听歌就不能浏览网页,看电影就不能下载视频,你想想是不是很蛋疼. 所以 ...
- java 并发性和多线程 -- 读感 (一 线程的基本概念部分)
1.目录略览 线程的基本概念:介绍线程的优点,代价,并发编程的模型.如何创建运行java 线程. 线程间通讯的机制:竞态条件与临界区,线程安全和共享资源与不可变性.java内存模型 ...
- Java 并发性和多线程
一.介绍 在过去单 CPU 时代,单任务在一个时间点只能执行单一程序.之后发展到多任务阶段,计算机能在同一时间点并行执行多任务或多进程.虽然并不是真正意义上的“同一时间点”,而是多个任务或进程共享一个 ...
- Java 并发和多线程(一) Java并发性和多线程介绍[转]
作者:Jakob Jenkov 译者:Simon-SZ 校对:方腾飞 http://tutorials.jenkov.com/java-concurrency/index.html 在过去单CPU时 ...
- Java核心知识点学习----多线程中的阻塞队列,ArrayBlockingQueue介绍
1.什么是阻塞队列? 所谓队列,遵循的是先进先出原则(FIFO),阻塞队列,即是数据共享时,A在写数据时,B想读同一数据,那么就将发生阻塞了. 看一下线程的四种状态,首先是新创建一个线程,然后,通过s ...
- Java并发性和多线程
Java并发性和多线程介绍 java并发性和多线程介绍: 单个程序内运行多个线程,多任务并发运行 多线程优点: 高效运行,多组件并行.读->操作->写: 程序设计的简单性,遇到多问题, ...
随机推荐
- Day14_81_反射机制获取Class属性
反射机制获取Class属性 获取属性 方法一: Class对象 . getFields();只能用来获取公开的属性,不能获取有私有的或者受保护的属性 获取属性 方法二: Class对象 . getDe ...
- 前后端分离中的无痛刷新token机制
今天我们来说一说前后端分离中的无痛刷新token机制 博主先来分享一波福利,最近挖到的宝藏,刚开始学Java的同学看 https://www.bilibili.com/video/BV1Rx41187 ...
- hdu1824 基础2sat
题意: Let's go home Time Limit: 10000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) ...
- Linux下磁盘分区、卸载和磁盘配额
目录 一:查看磁盘信息 二:Linux磁盘分区 三:Linux分区的卸载 四:Linux磁盘配额 一:查看磁盘信息 fdisk : 这个命令是磁盘分区表操作工具,fdisk能将磁盘分区,同时也能为每个 ...
- Win64 驱动内核编程-7.内核里操作进程
在内核里操作进程 在内核里操作进程,相信是很多对 WINDOWS 内核编程感兴趣的朋友第一个学习的知识点.但在这里,我要让大家失望了,在内核里操作进程没什么特别的,就标准方法而言,还是调用那几个和进程 ...
- Win64 驱动内核编程-24.64位驱动里内嵌汇编
64位驱动里内嵌汇编 讲道理64位驱动是不能直接内链汇编的,遇到这种问题,可以考虑直接把机器码拷贝到内存里,然后直接执行. 获得机器码的方式,可以写好代码之后,直接通过vs看反汇编,然后根据地址在看内 ...
- .NET之默认依赖注入
介绍 不要依赖于具体的实现,应该依赖于抽象,高层模块不应该依赖于底层模块,二者应该依赖于抽象.简单的说就是为了更好的解耦.而控制反转(Ioc)就是这样的原则的其中一个实现思路, 这个思路的其中一种实现 ...
- 解决docker镜像无法删除的问题
发现问题 来自守护进程的错误响应:冲突:无法删除050f26b6caca(必须强制) - 映像在多个存储库中被引用 Error response from daemon: conflict: unab ...
- 矩阵旋转-Eigen应用(QTCreator编辑器)
* { font-family: "Tibetan Machine Uni", "sans-serif", STFangSong; outline: none ...
- 04.06 UCF Local Programming Contest 2017
A.Electric Bill 题意:简单计算题,超过1000部分额外算 1 #include<stdio.h> 2 int main(){ 3 int money1,money2; 4 ...