没有任何加锁的代码进行测试
package cn.yun.basic.thread.wait;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class CasDemo {
/**
* @Author longhua.shang1
* @Description 卖票操作,100张票卖个2000个人,分别没有任何锁,以及加锁状态下的各种实现
* @Date 10:12 2021/6/18
**/
private static int count = 10;
public static void main(String[] args) {
scrambleTicketWithNoLock();
}
/**
* @Author longhua.shang1
* @Description //TODO 10:14
* @Date 10:14 2021/6/18
**/
public static void scrambleTicketWithNoLock() {
for (int i = 0; i < 100; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
getTicket();
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
public static void getTicket() {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "获得了票" + count);
count--;
}
}
}
输出的结果可能为:
Thread-0获得了票10
Thread-6获得了票10
Thread-4获得了票10
Thread-2获得了票10
Thread-3获得了票10
Thread-1获得了票10
Thread-5获得了票7
Thread-7获得了票8
Thread-11获得了票4
Thread-9获得了票1
public class MyStudy {
public static int data = 0;
public static void main(String[] args) throws Exception{
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
long dataAddress = unsafe.staticFieldOffset(MyStudy.class.getDeclaredField("data"));
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
int anInt = unsafe.getInt(MyStudy.class, dataAddress);
while(!unsafe.compareAndSwapInt(MyStudy.class,dataAddress,anInt,anInt+1))
anInt = unsafe.getInt(MyStudy.class, dataAddress);
}
}
}).start();
}
Thread.sleep(1000);
System.out.println(data);
}
}
二. java中的一些锁的分类:
1.公平锁 / 非公平锁
2.可重入锁 / 不可重入锁
3.独享锁 / 共享锁
4.互斥锁 / 读写锁
5.乐观锁 / 悲观锁
6.分段锁
7.偏向锁 / 轻量级锁 / 重量级锁
8.自旋锁
三. 介绍:
公平锁是指多个线程按照申请锁的顺序来获取锁,公平锁如ReentrantLock,即是公平锁也是非公平锁,在new ReentrantLock()的时候可以指定类型,默认无参为非公平锁,ReentrantLock是基于AQS来实现的
其中公平锁中的tryAcquire中多了一句
hasQueuedPredecessors这个判断;也就是说每次获取锁都需要判断队列中是否还有排在前面的线程,若不存在才能继续获取锁!!,若队列还存在等待线程则获取失败,当前线程进入队列尾部
非公平锁
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。
对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁
可重入锁 / 不可重入锁
可重入锁
广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁
synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}
上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁
不可重入锁
不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。看到一个经典的讲解,使用自旋锁来模拟一个不可重入锁,代码如下
import java.util.concurrent.atomic.AtomicReference;
public class UnreentrantLock {
private AtomicReference owner = new AtomicReference();
public void lock() {
Thread current = Thread.currentThread();
//这句是很经典的“自旋”语法,AtomicInteger中也有
for (;;) {
if (!owner.compareAndSet(null, current)) {
return;
}
}
}
public void unlock() {
Thread current = Thread.currentThread();
owner.compareAndSet(current, null);
}
}
代码也比较简单,使用原子引用来存放线程,同一线程两次调用lock()方法,如果不执行unlock()释放锁的话,第二次调用自旋的时候就会产生死锁,这个锁就不是可重入的,而实际上同一个线程不必每次都去释放锁再来获取锁,这样的调度切换是很耗资源的。
把它变成一个可重入锁:
import java.util.concurrent.atomic.AtomicReference;
public class UnreentrantLock {
private AtomicReference owner = new AtomicReference();
private int state = 0;
public void lock() {
Thread current = Thread.currentThread();
if (current == owner.get()) {
state++;
return;
}
//这句是很经典的“自旋”式语法,AtomicInteger中也有
for (;;) {
if (!owner.compareAndSet(null, current)) {
return;
}
}
}
public void unlock() {
Thread current = Thread.currentThread();
if (current == owner.get()) {
if (state != 0) {
state--;
} else {
owner.compareAndSet(current, null);
}
}
}
}
在执行每次操作之前,判断当前锁持有者是否是当前对象,采用state计数,不用每次去释放锁。
ReentrantLock中可重入锁实现
这里看非公平锁的锁获取方法:
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;
}
在AQS中维护了一个private volatile int state来计数重入次数,避免了频繁的持有释放操作,这样既提升了效率,又避免了死锁