线程概念
1 轻量级进程,共享空间,切换开销小
2 thread.sleep(n) 静态sleep方法,用于暂停当前线程的活动n 毫秒, 该方法 可以抛出一个 InterruptedException
3 简单创建一个新线程并运行的流程
通过一个Runnable接口对象 创建一个Thread,通过Start方法启动
runnable r = ()->{ do something;};
Thread t = new Thread(r);
t.start()
4 也可以构造Thread的子类 来新建线程, 需要覆写run() 方法,并调用start
这个方法不推荐了,不然一个任务就要创建一个类,应该将要并行运行的任务与运行机制解耦合
而且多次创建线程开销大,应该采用线程池的方法来实现
class MyThread extends Thread{
public void run(){};
}
5 不要直接调用runable或者thread的run方法,不然不会创建新线程
中断线程
1 当任务运行结束return ,或者 出现了未捕获的异常,线程会被终止
2 没有强制终止线程的方法,只能通过interrupt方法请求终止线程,当对一个线程调用interrupt方法,该线程的中断状态被置位,线程时不时检测该状态
3 可以通过 Thread.getCurrentThread() 获得当前线程,随后调用 isInterrupted()方法 判断自身是否被中断置位
4 当线程被阻塞的时候,无法检测中断状态,这时候调用interrupt方法会产生 InterruptedException中断
线程状态
1 线程有六种状态
New ,Runable, Blocked, Waiting, Timed wating, terminated
new状态 在New Thread(r) 但还没start的时候,这时候还需要做一些准备工作
runable 状态 , start后 线程进入该状态 (可能正在运行,也可能时间片用完了在等待)
Blocked状态,当线程试图获取 对象的内部锁(Synchronized),如果已被其他线程持有,则进入阻塞状态 ,当其他线程释放的时候,该线程可以持有锁,就退出阻塞态
Waiting状态, 当线程等待 另外一个线程通知 线程调度器一个条件的时候, 进入等待状态, 如 Object.wait() Thread.join() 或者并发包的Lock 或者 Condition时
timed wating状态, 计时等待, 等待waiting的条件 或者 计时结束, 就是限时的Waiting 状态,通过指定上述方法的 超时参数 实现
terminated状态, run方法结束 或者 线程程序出现未捕获异常的时候进入 终止状态,等待收拾现场
2 join 方法 等待 指定的线程终止, 或者带时间参数,计时等待
3 getState()方法 可以 得到该线程的 状态 返回 Thread.State
线程属性
1 每个线程具有一个优先级 从MIN_PRIORITY (0),NORM_PRIORITY(5)到 MAX_PRIORITY(10), 子线程继承父线程的优先级,setPriority() 设定优先级
2 优先级依赖于操作系统,在Oracle为Linux提供的Java虚拟机中,线程的优先级被忽略——所有线程具有相同的优先级,不要依赖线程优先级实现正确的逻辑
3 静态的 yield 方法,可以导致当前执行线程处于让步状态
4 可以通过 t.setDaemon(true) 将该线程设为守护线程,只剩下守护线程 依旧会 退出程序
5 UncaughtExceptionHandler接口 可以实现 线程因受查异常终止后,处理该异常
setUncaughtExceptionHandler方法为线程设置 异常处理器
静态的 setDefaultUncaughtExceptionHandler 方法 为所有线程安装默认处理器
线程同步
1 Java语言提供一个synchronized关键字 以及 ReentrantLock类 防止出现 竞态
2 ReentrantLock类是可重入的,线程可以重复获得已经获得的锁,通过维持一个持有计数实现
3 把解锁操作括在finally子句之内是至关重要的。如果在临界区的代码抛出异常,锁必须被释放。否则,其他线程将永远阻塞
4 线程进入临界区,却发现在某一条件满足之后它才能执行(如队列不为空)
但是其他线程此时有没有操作队列的权限
这种情况可以 通过条件对象 来管理
5 一个锁对象可以有多个条件对象,通过锁的 newCondition() 方法 创建新的条件对象
6 当线程获得锁后,发现条件对象不满足,调用条件对象的 await()方法 阻塞线程 锁就被释放了
7 一旦一个线程调用await方法,它进入该条件的等待集,直到另一个线程调用同一条件变量上的signalAll方法时为止
ReentratLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
condition.await();
condition.signalAll(); //这一调用重新激活因为这一条件而等待的所有线程,这些争取锁,一旦争取到,就从await那继续执行
condition.signal(); //随机唤醒一个 等待该条件的线程 (如果随机的这个线程没能满足条件,容易死锁)
8 通常而言在对象的状态有利于等待线程的方向改变时调用signal
9 Java中的每一个对象都有一个内部锁。如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法
10 内部对象锁只有一个相关条件,在synchronized 方法中 直接调用 wait() 等价于 对内部锁的 await, 调用 notify 和 notifyall 则等价于 signal 和signalAll
11 静态方法声明为synchronized 也合法 获得的是 class对象的内部锁
12 内部锁有一些缺点: 1 试图获取锁的线程不能被中断 2 获得锁不能设定超时时间 3 每个锁只有一个条件
13 不推荐使用synchronized 和 Lock /Condition , 推荐使用并发包的内容
14 synchronized 还可以对 一个对象 加锁
synchronized(obj){
获得obj的内部锁 而非类自己的内部锁
}
15 指令重排序 是 编译器和处理器 做的优化
16 volatile关键字有两个作用, 一是保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的, 二是禁止了指令重排序
volatile只能解决线程可见性问题,不能解决线程原子性问题
17 多个线程安全读取一个域 就要使用 volatile 或者 锁 , 或者将该对象声明为final,其他线程在这个对象构造完成后才会见到该对象,不然有可能读取到 null
18 atomic包中有很多类使用了很高效的机器级指令(而不是使用锁)来保证其他操作的原子性,如 AtomicInteger 提供了方法incrementAndGet和 decrementAndGet ,用以完成原子性的自增和自减
19 如果要实现更复杂的更新,可以使用 Atomic对象的 compareAndSet方法
do{ }while(!a.compareAndSet(oldvalue,newvalue))
compareAndSet 如果发现oldvalue变了 就更新失败,返回false
看起来麻烦,但比锁更快
可以使用简单形式,包括了上述的 do while 操作
a.updateAndGet(x->Math.max(x,observed));
20 如果有大量线程要访问相同的原子值,性能会大幅下降,因为乐观更新需要太多次重试
21 如果认为可能存在大量竞争,只需要使用LongAdder和LongAccumulator 解决,该类会将所有线程的加数 记住 并最后更新
调用 increment自增(不会返回原值), add 增加, sum再求和
22 使用ThreadLocal辅助类为各个线程 提供 线程局部变量
public static final threadLocal<...> data = ThreadLocal.withInitial(..);
// data.get()
在一个给定线程中首次调用get时,会调用initialValue方法。在此之后,get方法会返回属于当前线程的那个实例。
23 调用lock方法来获得另一个线程所持有的锁的时候,很可能发生阻塞, 可以使用trylock 方法, 如果获取不成功 就返回false
if (mylock.trylock){
try{...}finally{mylock.unlock();}
}
24 lock方法 无法被中断, 但是加超时参数的trylock方法可以,如果被中断 会抛出异常,await()也可以带时间参数
25 读写锁 对于 大量读 少量写 的场合十分有用
ReentrantReadWriteLock rwl = new ..;
Lock readLock = rw1.readLock(); 获取读锁
Lock writeLock = rw1.writeLock(); 获得写锁
对于读的方法 加 读锁, readLock.lock()
对于写的方法 加 写锁, writeLock.lock()
阻塞队列
1 上述是底层结构,对于实际编程来说,应该尽可能远离底层结构。使用由并发处理的专业人士实现的较高层次的结构要方便得多、要安全得多
2 对于许多线程问题,可以通过使用一个或多个队列以优雅且安全的方式将其形式化
转账指令 插入 队列, 一个专门处理转账的线程 获取指令,处理账户 不需要同步
队列满了 或者为空 可能导致 线程阻塞
3 多线程情况下 BlockingQueue 要用 put 和 take 方法 如果队列满 或者空 就会阻塞
如果不需要阻塞, 则使用 offer poll 和peek , 失败就返回false
4 concurrent包提供了阻塞队列的几个变种,LinkedBlockingQueue的容量默认是没有上边界,LinkedBlockingDeque是一个双端的版本
ArrayBlockingQueue在构造时需要指定容量,PriorityBlockingQueue是一个带优先级的队列,DelayQueue包含实现Delayed接口的对象:
线程安全的集合
1 concurrent包提供了高效的 映射,有序集,和队列
ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet和ConcurrentLinkedQueue。
这些集合使用复杂的算法,通过允许并发地访问数据结构的不同部分来使竞争极小化。
2 ConcurrentHashMap,可高效地支持大量的读者和一定数量的写者
3 原先的ConcurrentHashMap虽然允许多个线程同时操作,但还是无法直接 提供 原子 操作,用ConcurrentHashMap
java8 提供了compute方法 更加方便的 完成原子操作,该方法提供一个键和一个计算新值的函数,如果key不存在 会返回null
提供的函数不能做太多工作。这个函数运行时,可能会阻塞对映射的其他更新
map.computr(word,(k,v)-> v==null ? 1 : v+1);
computeIfPresent和computeIfAbsent 类似, 不过一个只在key存在更新,一个只在不存在的时候更新<br />4 merge 函数 能够容易实现 加一个键的操作
map.merge(word,1L,(exitstingvalue,newValue)->existingvalue+ newValue);
5 java 还提供了 安全的 并发散列映射的批操作
批操作会遍历映射,处理遍历过程中找到的元素,并把处理结果看作是映射状态的一个近似
search(查找) reduce(归约) forEach(挨个操作)
6 CopyOnWriteArrayList和CopyOnWriteArraySet是线程安全的集合
7 Arrays类提供了大量并行化操作
8 Vector和Hashtable类就提供了线程安全的动态数组和散列表的实现
9 任何集合类都可以通过使用同步包装器(synchronization wrapper)变成线程安全的 ,但不推荐使用
callable 和 future
1 Callable与Runnable类似,但是有返回值
2 Future保存异步计算的结果。可以启动一个计算,将Future对象交给某个线程,然后忘掉它。Future对象的所有者在结果计算好之后就可以获得它。
通过get方法获取future的计算结果,如果该计算未完成 就会进行 阻塞
3 FutureTask包装器是一种非常便利的机制,可将Callable转换成Future和Runnable
该类继承了 runable 所以可以传入Thread
Callable<T> myComputation = ...;
Futuretask<T> task = new FutureTask<T>(myComputation);
Thread t = new Thread(task);
t.start();
task.get();
执行器
1 构建一个新的线程是有一定代价的,因为涉及与操作系统的交互。如果程序中创建了大量的生命期很短的线程,应该使用线程池(thread pool)
2 将Runnable对象交给线程池,就会有一个线程调用run方法。当run方法退出时,线程不会死亡,而是在池中准备为下一个请求提供服务。
3 执行器(Executors)类有许多静态工厂方法用来构建线程池
ExecutorService pool = Executors....
newCachedThreadPool; // 必要时创建新线程,空闲线程保留 60秒
//如果有空闲线程可用,立即让它执行任务,如果没有可用的空闲线程,则创建一个新线程
newFixedThreadPool; //包含固定数量线程,空闲线程一直保留
//如果提交的任务数多于空闲的线程数,那么把得不到服务的任务放置到队列中
..
4 通过submit方法 可以将 callable对象和runnable对象提交给线程池 并返回Future对象
传入runnable对象 返回的是 Future<?> 可以调用 isDone cancel isCancel等方法
5 当用完一个线程池的时候,调用shutdown,该线程池 不再接收新任务
如果调用shudownNow,取消所有任务,并试图中断所有运行的线程
6 ScheduledExecutorService接口具有为预定执行(Scheduled Execution)或重复执行任务而设计的方法
7 使用执行器有更有实际意义的原因,控制一组相关任务:
invokeAll方法提交所有对象到一个Callable对象的集合中,并返回一个Future对象的列表
8 fork-join框架 适合于 处理任务可以轻易分解为子任务的场合
需要提供一个扩展于 RecursiveTask