线程,由线程ID,当前指令指针,寄存器集合,以及堆栈组成。一个进程内可以包含多个线程,线程之间可以共享资源
创建线程
实现 Runnable 接口
1 | public class Main { |
注意: 任务中的 run()
方法描述了如何完成这个任务,通过 thread.start()
Java 虚拟机会自动调用这个方法。如果直接调用 thread.run()
,这仅仅只是在同一个线程中执行该方法,并没有产生新线程
继承 Thread 类
1 | public class Main { |
建议: 采用 Runnable
接口的方式,因为 Thread
在继承上存在局限性
线程池
1 | import java.util.concurrent.ExecutorService; |
executorService.shutdown()
关闭执行器,但允许完成执行器中的任务executorService.shutdownNow()
关闭执行器,返回未完成任务列表,return List<Runnable>
进程同步
synchronized 关键字
- 实例方法加锁,给调用该方法的对象加锁
- 静态方法加锁,给类加锁
- 语句加锁,用于任何对象
语句加锁,表达式 expr 必须给出对象的引用。允许加锁部分代码,不必整个方法。
1 | synchronized (expr) { |
实例方法加锁可以转换语句加锁
1 | public synchronized void xMethod() { |
Lock 显示加锁
1 | public class Account { |
ReentrantLock()
等价于 ReentrantLock(false)
,公平策略锁,没有特定的获得顺序ReentrantLock(true)
,等待时间最长的线程将获得锁
线程间协作
1 | import java.util.LinkedList; |
通过 Lock
对象的 newCondition()
,实现进程通信:
await()
,当前线程等待,直到被唤醒signal()
,唤醒一个线程signalAll()
,唤醒所有线程
阻塞队列
试图向一个满队列添加元素或者从空队列中删除元素时会导致线程阻塞
ArrayBlockingQueue
,使用数组实现阻塞队列,必须指定容量LinkedBlockingDeque
,使用链表实现阻塞队列,可以创造不受限或者受限的队列PriorityBlockingQueue
,优先队列属性,可以创造不受限或者受限的队列
创造不受限的阻塞队列,put
方法永远不会堵塞
1 | import java.util.LinkedList; |
信号量
1 | public class Account { |
Semaphore(num)
等价于 Semaphore(num,false)
,公平策略为 false
,许可总数为 num
Semaphore(num,true)
,公平策略为 true
acquire
,获取信号量许可,许可总数减一,当无许可可用时,线程锁住release
,释放信号量许可,许可总数加一
死锁
每个线程已经锁定一个对象,而且正在等待锁定另一个对象
1 | //线程1 |
使用资源排序可以避免死锁发生,即给每一个需要锁的对象指定一个顺序,确保按照顺序来获得锁
线程状态
线程可以是以下五种状态之一:新建,就绪(可运行),运行,阻塞,结束(死亡)
同步集合
Java 集合框架中的类不是线程安全的,如果同时被多个线程访问和更新,内容可能被破坏
Collections
类提供多种静态方法来将集合转化为同步版本,例如:
synchronizedList(List<Object>)
,返回同步线性表synchronizedMap(Map<Object, Object>)
,返回同步图
内部实现:
1 | public boolean add(E o) { |
注意:Vector
,Stack
,Hashtable
属于旧类,应使用 ArrayList
,LinkedList
,Map
代替
同步集合是线程安全的,但是迭代器有快速失败的特性,意味着当下层集合被另一个线程修改时,如果在整个集合使用一个迭代器,迭代器会抛出异常。使用以下代码防止:
1 | Set hashSet = Collections.synchronizedSet(new HashSet<>()); |