一个程序运行后被抽象为一个进程;
一个程序至少有一个进程,一个进程至少有一个线程.
线程是程序执行时的最小单位,是CPU调度和分派的基本单位;
它是进程的一个执行流,一个进程可以由很多个线程组成; 线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量;
线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行
线程由阻塞状态,只能进入到就绪状态,而不能直接变为运行状态
单核CPU,同一时间只能有一个线程运行,不同系统由不同的策略分配给各个线程执行时间;
多核CPU,同时可有多个线程并行(不是并发)
阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪,notify通知);
以下情况会发生阻塞:
sleep
调用IO方法等待返回
等待获取资源监视器monitor(synchronize)
wait等待获取notify通知
调用suspend()将线程挂起(容易死锁,避免使用)
yield(不会阻塞,直接由运行状态变为就绪状态)
new MyThread().start(); 复制代码
new Thread(new MyRunnable()).start(); 复制代码
暂停执行一段时间,进入阻塞状态;
时间过去后,进入就绪状态
把指定的线程加入到当前线程,让一个线程等待另一个线程执行完以后再执行,可以将两个交替执行的线程合并为顺序执行的线程
...
try {
threadA.start();
threadA.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//当前线程等待threadA线程执行完以后,再往下执行
...
复制代码
线程让步:暂时让出自己的时间片给通优先级线程
当前线程由执行状态,转入就绪状态,不会阻塞线程
只是转入就绪状态,让线程调度器重新调度一次:
与当前线程优先级相同或更高,且处于就绪状态的线程获取执行,进入运行状态。 也有可能yield以后调度器重新立刻执行该线程。
Daemon Thread,又称“守护线程”,在后台运行,为其它线程提供服务;
所有前台线程死亡,后台线程自动死亡;
setDaemon(true),在start()之前设置 isDaemon()判断是否为后台线程 复制代码
mThread.setPriority(Thread.MAX_PRIORITY);//10
mThread.setPriority(Thread.NORM_PRIORITY);//5
mThread.setPriority(Thread.MIN_PRIORITY);//1
可以直接写数字,但是不同操作系统优先级不同,也不能很好的对应Java的10个优先级
所以,尽量使用Thread提供的优先级静态字段。
复制代码
Thread mThread = new Thread() {
public void run() {
if (mThtread.isInterrupted()) {
//收尾
return;
}
}
};
mThread.start();
mThread.stop();//强行终止
mThread.interrupt();//标记,配合终止
复制代码
仅仅返回当前状态
返回当前状态,并重置该状态
The interrupted status of the thread is cleared by this method
mThread.interrupt();//标记,配合终止
Thread mThread = new Thread() {
public void run() {
//只返回状态
if (mThread.isInterrupted()) {
//收尾
return;
}
//返回状态,并重置状态
if (Thread.interrupted()) {
//收尾
return;
}
try {
Thread.sleep(2000);
//interrupt()时,能唤醒sleep、join、wait
//内部使用Thread.interrupted()检查、重置、抛异常
} catch (InterruptedException e) {
e.printStackTrace();
//收尾
return;
}
//被打断,不会重置状态,内部实现里又重新设置回来了
SystemClock.sleep(2000);
}
};
总结:
在mThread.isInterrupted()、Thread.interrupted()、InterruptedException中做收尾工作
复制代码
方法、代码块
保证方法或代码块内部对资源的互斥访问;
即同一时间,同一个monitor监视的代码,最多只能有一个线程访问
保证线程之间,对监视资源的数据同步;
即任何线程在获得monitor的第一时间,现将共享内存中的数据复制到自己的缓存中;
任何线程在释放monitor的第一时间,将自己缓存中的数据复制到共享内存中
同样是“加锁”机制,使用方式更灵活,但也更麻烦
Java 并发:Lock 框架详解
读写锁...
程序的最小执行单位,不能被分割执行,如 a = 1+2;
注意:i++不是原子操作 --> tmp = i+1; i = tmp
轻量级的synchronize
作用和volatile基本一致,对于操作基本类型做了封装,更方便;
AtomicInteger、AtomicBoolean...
AtomicInteger ato = new AtomicInteger(); ato.addAndGet(1); ato.getAndAdd(2); 复制代码
无论是线程安全问题,还是衍生出的锁机制问题,它们的核心都在于“保证资源的安全性”,而不是某个方法或某几行代码
数据的安全性
多个线程访问共同的资源时,在某一个线程对资源进行写操作的中途,其它线程对该资源进行读、写,导致出现了数据的错误。
对资源进行访问限制
使同一时间只有一个线程可以访问资源,保证数据的准确性。
线程释放共享资源锁,进入等待队列,直到被再次唤醒
唤醒该共享资源锁上等待的单个/全部线程,唤醒哪个和顺序:取决于优先级和JVM
private String testStr;
public void setStr(String str) {
synchronized (obj){
this.testStr = str;
obj.notify();
}
}
public String getStr() {
/**
为什么不用if而要用while?
因为wait不确定会被谁唤醒(notify、interrt)
1. wait()一般都是这样搭配的标准写法
2. wait不只会被notify唤醒,还可能会被intrupt唤醒;
此时如果是if的话,wait被唤醒,就不查是否为null直接return了
*/
synchronized (obj){
//if (testStr == null){
while (testStr == null) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return testStr;
}
}
}
#范式
等待方:
synchronized( 对象 ) {
//not if
while(条件) {
对象.wait();
}
对应的处理逻辑
}
通知方:
synchronized( 对象 ) {
改变条件;
对象.notifyAll();
}
复制代码