锁的智慧【java线程高并发提升二】

上文链接:速记基础知识【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 stateAQS有无线程占用锁,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)不会按照原来顺序设置抢占锁。
两者相同点是:唤醒节点线程代码相同,都是唤醒最前面一个非取消状态的节点线程。
不同点是:公平实现会先校验等待队列中当前线程是否可以抢占锁,非公平实现是任意线程均可竞争锁。

下文链接:线程池,你知多少【java线程高并发提升三】


  1. 公平锁指A、B、C按时间先后顺序使用锁;非公平锁指A、B、C不按顺序的竞争锁。 ↩︎ ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

河南-殷志强

希望我的文章能帮助到你

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值