上文链接:速记基础知识【java线程高并发提升一】
1、java内存模型。
java内存具有可见性、原子性、有序性三种性质,相应性质讲解如下:
(1)可见性:是指线程之间的可见性,即一个线程修改的结果,另一个线程立即就能看到。比如:用volatile修饰的变量,就会具有可见性。在 Java 中 volatile、synchronized 和 final 实现可见性。
(2)原子性:原子性指的是不可分的操作。在 Java 中 synchronized 和ReentranLock的 lock、unlock 操作保证原子性。
(3)有序性:Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。
每个线程有自己的工作内存,工作内存包括局部变量(Local Variables)和主内存副本拷贝(Copy Memory),而多个线程之间共享资源通过主内存进行同步。
那么共享资源如何同步呢?
public class ThreadLocal {
private static int a = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
System.out.println(Thread.currentThread().getName()+":"+a);
a = 1;
System.out.println(Thread.currentThread().getName()+":"+a);
},"thread1");
Thread t2 = new Thread(()->{
System.out.println(Thread.currentThread().getName()+":"+a);
a = 2;
System.out.println(Thread.currentThread().getName()+":"+a);
},"thread2");
t1.start();
t2.start();
t1.join();
t2.join();
}
}
比如上题,定义资源a=0,在线程t1中操作将a变为1,在线程t2中操作将a变为2,结果显示a就算在t1中变为了1,在t2中也会变回0,可见上述过程中java内存对资源使用进行了重排,如何进行的重排呢?
Happens-before规则给出了解释:
2、synchronized关键字。
synchronized用来给方法或者类对象进行加锁的,控制方法或者对象在同一时间只能被一个线程占用,其主要解决多个线程同时访问的并发问题,保证线程安全。方法及对象上锁逻辑如下。
2.1 synchronized方法锁。
package Two_ThreadSynchronized;
import java.lang.reflect.Executable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.sun.org.apache.bcel.internal.generic.NEW;
public class ThreadSynchronized {
private static int allTickets = 1000;
private static final Object tObject = new Object();
public static void main(String[] args) {
//定义两买票线程
Runnable runnableA = () -> {
int Atickets = 0;
while (synMethod(1)) {
Atickets++;
}
System.out.println("A:"+Atickets+"票");
};
Runnable runnableB = () -> {
int Btickets = 0;
while (synMethod(1)) {
Btickets++;
}
System.out.println("B:"+Btickets+"票");
};
//注册到线程池
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
newFixedThreadPool.execute(runnableA);
newFixedThreadPool.execute(runnableB);
}
private synchronized static boolean synMethod(int count) {
if(allTickets-count<0)return false;
else
{
allTickets-=count;
return true;
}
}
}
该例中锁在方法上,功能为单线程调用购买票数,结果为AB票数和为1000。需要注意点:每个使用synchronized的方法在javap反编译之后都具有一个ACC_SYNCHRONIZED标识(标识该方法正在被某个线程占用,其他线程需要等待),如果方法锁是static,那么锁作用在该类上,如果方法锁不是static,那么锁作用在同类的不同对象上(该类的不同对象互不关联)。
2.2 synchronized对象锁。
package Two_ThreadSynchronized;
import java.lang.reflect.Executable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.sun.org.apache.bcel.internal.generic.NEW;
public class ThreadSynchronized {
private static int allTickets = 1000;
private static final Object tObject = new Object();
public static void main(String[] args) {
//定义两买票线程
Runnable runnableA = () -> {
int Atickets = 0;
while (synobject(1)) {
Atickets++;
}
System.out.println("A:"+Atickets+"票");
};
Runnable runnableB = () -> {
int Btickets = 0;
while (synobject(1)) {
Btickets++;
}
System.out.println("B:"+Btickets+"票");
};
//注册到线程池
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
newFixedThreadPool.execute(runnableA);
newFixedThreadPool.execute(runnableB);
}
private static boolean synobject(int count) {
synchronized (tObject) {
if(allTickets-count<0)return false;
else
{
allTickets-=count;
return true;
}
}
// TODO Auto-generated method stub
}
}
该锁是对象锁,作用结果同方法锁,需要注意点:与ACC_SYNCHRONIZED不同,对象锁使用monitorenter和monitorexit控制线程进出,当该对象锁处于monitorenter时,其他线程需等待。除了使用synchronized还可以使用ReentrantLock对对象上锁,如图展示ReentrantLock上锁取消锁及synchronized上锁取消锁过程。
package Two_ThreadSynchronized;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadReentrantLock {
private static Object rLock=new Object();
public static void main(String[] args) {
synobject();//synchronized上锁
reeobject();// reenlock上锁
}
private static void reeobject() {
// TODO Auto-generated method stub
ReentrantLock lock = new ReentrantLock();
lock.lock();//上锁
//中间使用某对象或方法,即为给该对象或方法取消锁。
lock.unlock();//取消锁
}
private static void synobject() {
// TODO Auto-generated method stub
synchronized (rLock) {
System.out.println("对象上锁");
synchronized (rLock) {
System.out.println("对象取消锁");
}
}
}
}
synchronized与ReentrantLock的比较如下(S代表synchronized,R代表ReentrantLock):
相同点 | 不同点 | |
---|---|---|
S与R比较 | 均为上锁并控制单线程访问锁 | S为Java关键字,R为Java sdk API级别锁;S可以为方法上锁,R不行;R可以通过方法tryLock 请求超时取消锁的资源,S不行;R有公平锁1和非公平锁,S只有非公平锁1 |
3、Volatile关键字
Volatile是Java语言关键字,也是指令关键字。其特点为:1、用来保证将最先变量值及时通知给其他线程,2、禁止volatile前后的程序指令进行重排序,3、不保证线程安全,不可用于数字的线程安全递增。其应用场景有:实时展示状态值;避免多线程中内存不可见而重复构建对象。
4、乐观锁、悲观锁
先来个对比。
乐观锁 | 悲观锁 | |
---|---|---|
定义 | 不加锁,但是判断是否失败 | 加锁,不让其他线程占用 |
区别 | 不加锁 | 加锁 |
所用场景 | 少写大量读 | 大量写 |
示例 | CAS、java.util.consurrent.atomic包下的类 | Synchronized、ReentrantLock |
有没有不明白的地方?我先介绍CAS。
4.1 CAS
CAS全称compare and swap ( 源码中compareAndSet方法 ),先比较后设置(具有原子性),其适用场景是允许请求资源失败的任务,其局限是同一时间只有多个请求操作时(并发时)会出错。
CAS的自旋问题
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
Atomicinteger类中的compareAndSet方法是native调用JNI(Java Native Interface)的方法,底层使用cpu指令。
当添加新的数据时假设请求失败会一直继续请求浪费大量开销,比如以下源码中当添加一个新数据时,在unsafe.getAndAddInt会出现失败一直检测的while循,这就是CAS的自旋问题。
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
另外CAS还有ABA问题
4.2 ReentrantLock
AQS与ReentrantLock息息相关,先行介绍AQS。
4.2.1 AQS (高同步能力)
AQS的全称是AbstractQueuedSynchronizer,它提供一个用来实现阻塞锁和等待队列的框架,内部有各种同步组件的核心抽象实现类,它适用于管理等待队列、锁的占用和释放、中断、超时等任务,主要应用场景是:
- 可重入锁的公平非公平锁的实现
- 可重入读写锁的公平非公平锁的实现
- 信号量的公平非公平锁的实现
- 线程池工作线程Worker
- CountDownLatch闭锁实现
AQS参数:
名称 | 作用 |
---|---|
Node head | 等待队列头结点、初始化null,之后通过setHead()设置 |
Node tair | 等待队列尾结点,初始化null,之后通过eng()设置 |
int state | AQS有无线程占用锁,0没有,1有后续线程需等待获取锁,大于1表示已占用锁的线程重入了锁 |
Thread exclusiveOwnerThread | 获得独占锁的线程 |
4.2.2 ReentrantLock加解锁过程
private static void reeobject() {
// TODO Auto-generated method stub
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();
}
//加锁源码
public void lock() {
sync.lock();
}
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//解锁源码
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
加锁时通过CAS的方式将占用锁线程从0置为1,成功则至当前线程为独占者线程。
不成功则可能是当前占用线程数大于1(重入方式),即AQS的state大于1,此时执行acquire调用到nonfairTryAcquire,成功则设置否则失败。
解锁时调用ReentrantLock.tryRelease,判断当前的独占线程数量并减一,当为零时,将独占线程数置为空。
4.2.3 ReentrantLock公平不公平实现方式
//公平方式
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//非公平方式
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平实现时,ReentranLock通过hasQueuedPredecessors判断是否还有等待节点存在,若有则优先按先后顺序进行抢占锁的设置,而不公平实现方式(nonfairTryAcquire)不会按照原来顺序设置抢占锁。
两者相同点是:唤醒节点线程代码相同,都是唤醒最前面一个非取消状态的节点线程。
不同点是:公平实现会先校验等待队列中当前线程是否可以抢占锁,非公平实现是任意线程均可竞争锁。