1、概述
1.1 进程和线程
进程:操作系统资源分配的最小单位。
- 程序由指令和数据组成,指令要执行,数据要读写,就必须将指令加载至cpu,数据加载至内存,在指令运行过程中还需要用到磁盘、网络等设备,进程就是用来加载指令、管理内存、管理io的。
- 当一个程序被运行,从磁盘加载这个代码至内存,这时就开启了一个进程。
线程:处理器任务调度和执行的最小单位。
- 一个进程之内可以分为一到多个线程
- 一个线程就是一个指令流,将指令流中的指令以一定的顺序交给cpu执行
区别
- 进程基本上相互独立,线程存在于进程内,是进程的一个子集
- 进程拥有共享的资源,如内存空间等,供其内部的线程共享
- 进程间的通信:同一台计算机的进程通信称为IPC,不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,如HTTP协议
- 线程间通信比较简单,因为他们共享进程内的内存
- 线程更轻量,线程上下文切换成本一般比进程上下文切换低
1.2 并发和并行
- 并发
同一时间应对多件事情的能力
单核cpu下,线程实际还是串行执行的,任务调度器将cpu的时间片分给不同的线程使用,只是由于cpu在线程间的切换时间非常快,给人感觉同时运行,微观串行,宏观并行,cpu的这种做法称为并发
- 并行
同一时间处理多件事情的能力
1.3 应用
单核仍是串行,多核才有意义
- 异步调用
- 提高效率
2、线程
2.1 创建线程
- 使用Thread
@Slf4j
public class Test01 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run() {
log.info("thread is run");
}
};
thread.start();
}
}
- 使用Runnable
@Slf4j
public class Test01 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = ()-> log.info("thread is run");
Thread thread = new Thread(runnable);
thread.start();
}
}
- 使用Callable+FutureTask
@Slf4j
public class Test01 {
public static void main(String[] args) throws InterruptedException {
Callable<String> callable = () -> {
log.info("thread is run");
return "success";
};
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
}
}
2.2 查看线程
- windows
任务管理器
tasklist 查看进程
taskkill 杀死进程
- linux
ps -ef 查看所有进程
ps -fT -p 查看某个进程的所有线程
kill 杀死进程
top 按大写H切换是否显示线程
top -H -p 查看某个进程的所有线程
- java
jps 查看所有java进程
jstack 查看进程的所有线程
jconsole
2.3 线程运行原理
- 栈与栈帧
- 每个栈由多个栈帧组成,对应着每次方法调用时所占的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
- 线程上下文切换
- 线程的cpu时间片用完
垃圾回收
有更优先级的线程需要运行
线程自己调用了sleep、yield、wait、join、park、synchronized、lock等方法
当上下文切换时,由操作系统保存当前线程的状态,并恢复另一个线程的状态,java中对应的就是程序计数器,它的作用是记住下一条jvm指令的执行地址,它是线程私有的。
2.4 线程的使用
- start & run
- sleep & yeild
- join
- interrupt 打断
打断sleep,wait,join的线程会抛出异常
@Slf4j
public class Test01 {
public static void main(String[] args) throws InterruptedException {
log.debug("enter main...");
Thread t1 = new Thread(() -> {
log.debug("t1 start...");
while (true) {
if (Thread.currentThread().isInterrupted()) {
log.debug("t1 exit...");
break;
}
}
});
t1.start();
Thread.sleep(1000);
log.debug("t1 start interrupt...");
t1.interrupt();
log.debug("" + t1.isInterrupted());
log.debug("exit main...");
}
}
- park
@Slf4j
public class Test01 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打断状态"+ Thread.interrupted());
LockSupport.park();
log.debug("unpark...");
},"t1");
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
2.5 守护线程
@Slf4j
public class Test01 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while (true){
if(Thread.interrupted()){
break;
}
}
log.debug("t1 end");
},"t1");
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
log.debug("main end");
}
}
2.6 线程的状态
2.6.1 分类
- 按操作系统分
- 按java api分
2.7 应用
异步调用: 主线程执行期间,其他线程异步执行耗时操作
提高效率: 并行计算,缩短运算时间
同步等待:join
统筹规划:合理使用线程,得到最优效果
2.7.1 两阶段终止模式
@Slf4j
public class Test01 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
twoPhaseTermination.start();
twoPhaseTermination.stop();
}
}
@Slf4j(topic = "TwoPhaseTermination")
class TwoPhaseTermination {
private Thread monitor;
public void start() {
monitor = new Thread(() -> {
Thread current = Thread.currentThread();
while (true){
if(current.isInterrupted()){
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("执行监控记录");
} catch (InterruptedException e) {
e.printStackTrace();
current.interrupt();
}
}
});
monitor.start();
}
public void stop() {
monitor.interrupt();
}
}
2.7.2 统筹规划
@Slf4j
public class Test01 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
try {
log.debug("洗水壶");
Thread.sleep(1);
log.debug("烧开水");
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"小王");
Thread t2 = new Thread(()->{
try {
log.debug("洗茶壶");
Thread.sleep(1);
log.debug("洗茶杯");
Thread.sleep(2);
log.debug("拿茶叶");
Thread.sleep(1);
t1.join();
log.debug("泡茶");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"小张");
t1.start();
t2.start();
}
}
3、并发-共享内存模型
3.1 管程-悲观锁(阻塞)
3.1.1 共享带来的问题
- 临界区:
一段代码块如果存在对共享资源的多线程读写操作,成这段代码块为临界区。
- 竞态条件
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
3.1.2 synchronized解决方案
- 应用互斥
- 阻塞式的解决方案:synchronized(俗称对象锁)、lock
- 非阻塞式的解决方案:原子变量
synchronized实际是利用对象锁保证了临界区的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断
@Slf4j
public class Test01 {
public static void main(String[] args) throws InterruptedException {
LockTest lockTest = new LockTest();
Thread t1 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
lockTest.increment();
}
},"t1");
Thread t2 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
lockTest.decrement();
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(lockTest.get());
}
}
class LockTest{
private int count = 0;
public void increment(){
synchronized (this){
count++;
}
}
public void decrement(){
synchronized (this){
count--;
}
}
public int get(){
synchronized (this){
return this.count;
}
}
}
- 同步方法和静态同步方法的区别