进程和线程
- 进程:进程是正在运行的程序的实例,是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
- 线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程并行执行不同的任务。
- 进程和程序的区别:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
- Java默认的两个线程:main和GC;
Java不能自己开启线程!
new Thread().start();
// start()方法源码:调用的是本地方法native,调用c++;
private native void start0();
并发和并行
- 并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行;简单地说就是把一个处理器划分为若干个短的时间片,每个时间片依次轮流地执行处理各个应用程序,由于一个时间片很短,相对于一个应用程序来说,就好像是处理器在为自己单独服务一样,从而达到多个应用程序在同时进行的效果。
并发即多线程操作同一个资源,通过线程快速交换达到并行的效果。 - 并行:指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。
- 并发编程目的:充分利用CPU资源,提升效率!
多线程和并发
多线程的实现靠的是并发执行机制,并发执行
多线程指从软件或者硬件上实现多个线程并发执行的技术,实现原理就是采用一种并发执行机制!
多线程就是把操作系统中的这种并发执行机制原理运用在一个程序中,把一个程序划分为若干个子任务,多个子任务并发执行,每一个任务就是一个线程。这就是多线程程序。
线程知识
- 线程状态
线程的状态:6个状态(Thread.State)
public enum State {
NEW, //新生
RUNNABLE, //运行
BLOCKED, //阻塞
WAITING, //等待:死等
TIMED_WAITING, //超时等待
TERMINATED; //终止
}
- wait和sleep的区别
- 出自的类不同:
wait: object
sleep:concurrent - 释放锁:
wait: 会释放
sleep:不会释放 (睡觉的时候抱着锁) - 使用范围:
wait: 同步代码块中
sleep:任何地方
在JUC包下的类:
TimeUnit.SECONDS.sleep(3);
TimeUnit.SECONDS.wait(3);
- start和run方法
start方法:是真正的启动了一个线程,调用本地方法,c++开启线程;不用等待run方法执行完,就可以继续执行下面的代码,是真正的多线程;
run方法:是thread里面的一个普通的方法,所以我们直接调用run方法,这个时候它是会运行在我们的主线程中的,因为这个时候我们的程序中只有主线程一个线程,所以如果有两个线程,都是直接调用的run方法,那么他们的执行顺序一定是顺序执行,所以这样并没有做到多线程的这种目的。
- start方法:
public synchronized void start() {
//这里private volatile int threadStatus = 0;初始化的时候就是0
//如果这里不为0的话就抛异常
if (threadStatus != 0)
throw new IllegalThreadStateException();
//把当前线程加入到线程组中
//private ThreadGroup group;就是这么个东西
group.add(this);
//初始化标记位未启动
boolean started = false;
try {
start0();
//标识为启动状态
started = true;
} finally {
try {
//如果没开启,标识为启动失败
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
start0()方法:private native void start0();调用本地方法
- run方法:
@Override
public void run() {
if (target != null) {
target.run();
}
}
直接使用对象调用方法,那必须是这个方法执行完了代码才能往下走!用于串行执行(一个线程的任务完整执行完后下一个线程才能执行!)
synchronized 和 volatile 深入理解
公平锁:
获取不到锁的时候,会自动加入队列,等待线程释放后,队列的第一个线程获取锁
非公平锁:
获取不到锁的时候,会自动加入队列,等待线程释放锁后所有等待的线程同时去竞争
什么是可重入?
同一个线程可以反复获取锁多次,然后需要释放多次
synchronized 是非公平锁,可以重入
CAS问题
- 概述:
compare and swap(比较并交换),比较当前工作内存中的值和主内存的值,如果是期望的,就执行操作,如果不是就一直循环(自旋锁),它是 CPU 的并发原语。
描述CAS问题:为了提升效率,在线程不加锁的情况下,一条线程开始执行了;
- 此时线程A读取到了工作内存中有一个值为 0,线程需要执行的操作是 +1;
- 加1后,值变为了 1;
- 然后需要重新读取主内存的值,判断是否还是 0;
- 如果为 0,则执行 +1操作并更新值成功,如果不为 0(说明线程A在执行+1操作的同时,有其他线程改变了 0这个值),则读取新的值,继续 +1,然后继续判断当前工作内存中的值和主内存的值是否相等。
- CAS的缺点:
- 循环浪费时间
- 一次只能保证一个共享变量的原子性
- 存在ABA问题
- ABA问题
当线程A执行CAS操作时,重新写回到主内存比较值之前,有线程B更新的主内存的0,将它改为1,然后又改为了0,此时线程A虽然比较当前工作内存中的值和主内存的值相等,但是这个值已经被修改过了。
-
如何解决ABA问题?
给数据再加一个版本号,只要主内存数据修改一次,就修改一次版本号,如果在重写过程中,版本号不一致,说明值已经被人动了! -
在java的原子类中:(AtomicXXX)
都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的CAS操作,在java.util.concurrent中的大多数类在实现时都直接或间接的使用了这些原子变量类。
// 比如这个类:AtomicInteger
AtomicInteger atomicInteger = new AtomicInteger();
有一个方法:atomicInteger.getAndIncrement(),给数值进行加操作并得到它。
源码:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// getAndAddInt源码:一直进行do while操作(自旋锁)
// 参数对应关系:this->var1、 valueOffset->var2、 1->var4
public final int getAndAddInt(Object var1, long var2, int var4) { //(对象,内存偏移值,1)
int var5;
do {
var5 = this.getIntVolatile(var1, var2); //获取内存地址中的值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); //如果当前对象的内存地址值还是var5,就完成+1操作
return var5;
}
// 再点入compareAndSwapInt方法,就是native方法了
这里有一个unsafe类:需要读jvm的 hotspot 的源码,里面又 unsafe.cpp代码
里面会有一个 cmpxchg (compare and exchange),再追踪就会到 automic_linux_x86inline.hpp,这是linux下的源码:
最终指令:cmpxchg=cas修改变量值,lock cmpxchg 指令。cmpxchg 它没有原子性,lock加上保证原子性。
Volatile
是Java虚拟机提供的轻量级同步机制
- 特点:
1、保证可见性
2、不保证原子性
3、禁止指令重排
synchronized和lock区别
- synchronized是java一个关键字,lock是一个接口类;
- synchronized无法判断锁的状态,lock可以判断是否获得了锁;
- synchronized自动释放锁,lock需要手动释放,不释放会产生死锁;
- synchronized当线程一获得锁,阻塞,线程二会死等;lock不会等待(lock.tryLock);
- synchronized可重入锁,不可以中断,非公平(默认);lock,可以判断锁,公平可以自己设置;
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
- synchronized适合锁少量的同步代码,lock适合锁大量的。
锁
- 公平锁、非公平锁:线程执行顺序执行不能插队/可以插队,默认都是非公平锁!
Lock lock = new ReentrantLock();
//源码:
public ReentrantLock() {
sync = new NonfairSync();
}
Lock lock = new ReentrantLock(true);
//源码:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
- 可重入锁(递归锁):拿到外面的锁,就等于拿到里面的锁!(自动)