多线程基础
原因
- 多核+分布式架构
- 多CPU核心意味着同时操作系统有更多的并行计算资源可以使用,以线程作为基本调度单元
- 总线架构
- SMP Architecture(SymmetricalMultiProcessing 对称多处理)处理同一块内存效率低
- NUMA Architecture(NonUniformMemoryAccess 非统一内存访问)可部分绑定,可扩展
Java多线程
线程创建过程
线程与进程
- 应用基本运行单位是进程
- CPU调度的单位是线程
- 进程里面包含N个线程,系统资源共享
- 线程独享自己的资源
线程方法
```java public static void main(String[] args){ Runnable task = new Runnable(){
}; Thread thread = new Thread(task); thread.setName(“ThisIsMyThreadName”); thread.setDaemon(true); // 如果是守护线程,那JVM会在没有前台线程时退出整个进程不再执行 thread.start();@Overridepublic void run(){Thread t = Thread.currentThread();try{System.out.println("threadName is");System.out.println("threadName is"+t.getName());System.out.println("threadName is");Thread.sleep(5000);}catch(InterruptedException e){e.printStackTrace();}System.out.println("threadName is"+t.getName());}
// 线程的优先级默认是5,从1-10,越大优先级越高
System.out.println(Thread.activeCount()); // 2
Thread.currentThread().getThreadGroup().list();
// java.lang.ThreadGroup[name=main,maxpri=10]
// Thread[main,5,main]
// Thread[Monitor Ctrl-Break,5,main]
System.out.println(Thread.currentThread().getThreadGroup().getParent().activeGroupCount()); // 1
Thread.currentThread().getThreadGroup().getParent().list();
// java.lang.ThreadGroup[name=system,maxpri=10]
// Thread[Reference Handler,10,system]
// Thread[Finalizer,8,system]
// Thread[Single Dispatcher,9,system]
// Thread[Attach Listener,5,system]
// java.lang.ThreadGroup[name=main,maxpri=10]
// Thread[main,5,main]
// Thread[Monitor Ctrl-Break,5,main]
}
```java
// 接口定义
public interface Runnable{
public abstract void run();
}
// 重要实现
Thread implements Runnable...
// 辨析
Thread.start(); // 创建线程
Thread.run(); // 调用线程
| Thread类重要属性/方法 | 说明 |
|---|---|
| volatile String name; | 线程名称-诊断分析使用 |
| boolean daemon = false; | 后台守护线程标志-决定JVM优雅关闭 |
| Runnable target; | 任务(只能通过构造函数传入) |
| synchronized void start(); | [协作]启动新线程并自动执行 |
| void join(); | [协作]等待某个线程执行完毕(来汇合) |
| static native Thread currentThread(); | 静态方法:获取当前线程信息 |
| static native void sleep(long millis); | 静态方法:释放CPU,不释放锁 |
| Object#方法 | 说明 |
|---|---|
| void wait(); | 放弃锁+等待0ms+尝试获取锁(释放CPU,释放锁) |
| void wait(long timeout, int nanos) | 放弃锁+wait+到时间自动唤醒/中途唤醒(精度:nanos>0则timeout++) |
| native void wait(long timeout); | 放弃锁+wait+到时间自动唤醒/中途唤醒(唤醒之后需要自动获取锁) |
| native void notify(); | 发送信号通知1个等待线程 |
| native void notifyAll(); | 发送信号通知所有等待线程 |
- Thread.sleep(long millis)
- 一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁
- millis时间后线程自动苏醒进入就绪状态
- 作用:给其他线程执行机会的最佳方式
- Thread.yield()
- 一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态
- 不会导致阻塞,该方法与sleep类似,只是不能由用户指定暂停多长时间
- 作用:让相同优先级的线程再次被CPU选择调用
- t.join()
- 当前线程里调用其他线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态
- 当前线程不会释放已经持有的对象锁,t线程会释放锁
- 线程t执行完毕或者millis时间到,当前线程进入就绪状态
- obj.wait()
- 当前线程调用对象的wait(),当前线程释放对象锁,进入等待队列
- 依靠notify()/notifyAll()唤醒或timeout时间到自动唤醒
- obj.notify()
- 唤醒在此对象监视器上等待的单个线程,选择是任意的
- obj.nofityAll()
- 唤醒再次对象监视器上等待的所有线程
- sleep与wait
- sleep不释放锁,wait释放锁
- sleep只能按时间自动唤醒,wait可被notify唤醒 ```java // https://github.com/JavaCourse00/JavaCourseCodes
package java0.conc0301.op;
public class Join {
public static void main(String[] args) {
Object oo = new Object();
MyThread thread1 = new MyThread("thread1 -- ");
thread1.setOo(oo);
thread1.start();
// synchronized (oo) {
synchronized (thread1) {
for (int i = 0; i < 100; i++) {
if (i == 20) {
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " -- " + i);
}
}
}
}
class MyThread extends Thread {
private String name;
private Object oo;
public void setOo(Object oo) {
this.oo = oo;
}
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 100; i++) {
System.out.println(name + i);
}
}
}
}
<a name="cWzx9"></a>
## 线程中断与异常
- 线程内部自己处理异常,因为不会溢出到外层
- 如果线程被Object.wait/Thread.join/Thread.sleep三种方法之一阻塞,此时调用该线程的interrupt(),那么该线程将抛出一个InterruptedException中断异常,从而提早终结被阻塞的状态。如果线程没有被阻塞,这时调用interrupt()将不起作用,直到执行到wait/join/sleep时才会马上抛出InterruptedException
- 子线程内部调用Thread.interrupted()可以重置中断状态
- 如果是计算密集型的操作需要分段处理,每个片段检查一下状态,是不是要终止
<a name="e7qra"></a>
## 线程状态
<br />
<a name="6wwqL"></a>
# 线程安全
<a name="H9Whq"></a>
## 多线程执行问题
- 多个线程竞争同一资源时,如果对资源的访问顺序敏感,就是存在竞态条件
- 导致竞态条件发生的代码区称作临界区
- 不进行恰当的控制,会导致线程安全问题
- 恰当的控制
- 同步
- 加锁
<a name="EyyQF"></a>
## 并发
<a name="P4m0i"></a>
### 原子性
- 原子操作,注意和事务ACID里原子性的区别与联系
- 原子性操作不可被中断,要么执行,要么不执行
- 对基本数据类型的变量的读取和赋值操作是原子性操作
```java
x=10; // 原子性操作
y=x; // 非原子性操作
x++; // 非原子性操作
x=x+1;// 非原子性操作
可见性
- volatile关键字保证可见性,但不保证原子性
- 当一个共享变量被volatile修饰时,会保证修改的值立即被更新到主存,当有其他线程需要读取时,会去内存中读取新值
通过synchronized和Lock也能保证可见性,这两个关键字保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前对变量的修改刷新到主存当中
有序性
Java允许编译器和处理器对指令进行重排,重排过程不会影响到单线程的执行,但会影响到多线程的并发
- 可通过volatile/synchronized/Lock保证一定的有序性
happens-before原则(先行发生原则):
- 程序次序规则:一个线程内,按照代码先后顺序
- 锁定规则:一个unLock操作先行发生于后面对同一个锁的Lock操作
- volatile变量规则:对一个变量的写操作先行发生后面对这个变量的读操作
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出A先于C
- 线程启动规则:start方法先发生于线程的每一个动作
- 线程中断规则:对线程的interrupt()先行发生于被中断线程代码检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先于线程的终止检测,Thread.join()方法结束、Thread.isAlive()检测是否终止
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法
synchronized
实现机制
使用对象头标记字(Object monitor)
- Synchronized 方法优化
- 偏向锁:BiaseLock
三种用法
public class SyncCounter{
private int sum = 0;
// 锁方法,专有指令,影响所有调用此方法的对象
public synchronized int incAndGet(){
return ++sum;
}
public int addAndGet(){
// 锁对象,只影响当前对象
synchronized (this){
return ++sum;
}
// 锁代码块,粒度最小
synchronized {
}
}
public int getSum(){
return sum;
}
}
volatile
- 每次读取都强制从主内存刷数据
- 适用场景:单个线程写,多个线程读,也可以做指令重排屏障,阻止上下两部分的指令混排,做屏障分割
- 原则:能不用就不用,不确定的时候也不用
-
final
常量替换
- 写代码时最大化用final是个好习惯
| final定义类型 | 说明 |
| —- | —- |
| final class | 不允许继承 |
| final 方法 | 不允许Override |
| final 局部变量 | 不允许修改 |
| final 实例属性 |
- 构造函数/初始化块/之后不允许变更;
- 只能赋值一次
- 安全发布:构造函数结束返回时,final域最新的值被保证对其他线程可见
| | final static属性 |静态块执行后不允许变更;
只能赋值一次; |

