Java 中的那些“琐”事

在 Java 中,synchronized 是典型的非公平锁,而 ReentrantLock 既可以是公平锁也可以是非公平锁,可以在初始化的时候指定。

查看 ReentrantLock 的源码会发现,初始化时可以传入 true 或 false,来得到公平或非公平锁。

//源码
//默认为非公平
public ReentrantLock() {
sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public class FairLockDemo {
public static void main(String[] args) {
//公平锁
Lock fairLock = new ReentrantLock(true);
//非公平锁
Lock unFairLock = new ReentrantLock(false);
}
}
可重入锁也叫递归锁,是指线程可以进入任何一个它已经拥有的锁所同步的代码块。通俗来讲,就好比你打开了你家的大门,就可以随意的进入客厅、厨房、卫生间…

如下图,线程 M1 和 M2 是被同一把锁同步的方法,M1 中调用了 M2,那么线程 A 访问 M1 时,再访问 M2 就不需要重新获取锁了。

优:可以一定程度上避免死锁
缺:暂时不知道
synchronized和ReentrantLock都是典型的可重入锁

synchronized

public class ReentrantDemo1 {
public static void main(String[] args) {
Phone phone = new Phone();

    new Thread(() -> {
        phone.sendSMS();
    }).start();

    new Thread(() -> {
        phone.sendSMS();
    }).start();
}

}
class Phone {
public synchronized void sendSMS() {
System.out.println(Thread.currentThread().getId() + “:sendSMS()”);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
sendEmail();
}

public synchronized void sendEmail() {
    System.out.println(Thread.currentThread().getId() + ":sendEmail()");
}

}
ReentrantLock

public class ReentrantDemo2 {
public static void main(String[] args) {
User user = new User();

    new Thread(() -> {
        user.getName();
    }).start();

    new Thread(() -> {
        user.getName();
    }).start();
}

}

class User {
Lock lock = new ReentrantLock();

public void getName() {
    lock.lock();
    try {
        System.out.println(Thread.currentThread().getId() + ":getName()");
        TimeUnit.SECONDS.sleep(1);
        getAge();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

public void getAge() {
    lock.lock();
    try {
        System.out.println(Thread.currentThread().getId() + ":getAge()");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

}
点击查看我之前的博客 多线程之8锁问题,搞懂八锁问题,可以更深刻的理解 synchronized 锁的范围

public class UnReentrantLockDemo {

private AtomicReference<Thread> atomicReference = new AtomicReference<>();

public void lock() {
    Thread current = Thread.currentThread();
    //自旋
    while(!atomicReference.compareAndSet(null, current)) {

    }
}

public void unlock() {
    Thread current = Thread.currentThread();
    atomicReference.compareAndSet(current, null);
}

}
尝试获取锁的线程不会立即阻塞,而是以循环的方式不断尝试获取锁

优:减少线程上下文切换的消耗
缺:循环消耗CPU
CAS:CompareAndSwap,比较并交换,它是一种乐观锁。

CAS 中有三个参数:内存值V、旧的预期值A、要修改的新值B;只有当预期值A与内存值V相等时,才会将内存值V修改为新值B,否则什么都不做

public class CASTest {
public static void main(String[] args) {
AtomicInteger a1 = new AtomicInteger(1);
//V=1, A=1, B=2
//V=A,所以修改成功,此时V=2
System.out.println(a1.compareAndSet(1, 2) + “,” + a1.get());
//V=2, A=1, B=2
//V!=A,修改失败,返回false
System.out.println(a1.compareAndSet(1, 2) + “,” + a1.get());
}
}
源码解析:以 AtomicInteger 中的 getAndIncrement() 方法为例

//获取并增加,相当于i++操作
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}

//调用UnSafe类中的getAndAddInt()方法
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//获取当前内存值
var5 = this.getIntVolatile(var1, var2);
//循环比较内存值和预期值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;

}
CAS 也存在一些问题:

如果一直交换不成功,会一直循环,开销大
只能保证一个共享变量的原子操作
ABA 问题:即 A 被修改为 B,又被改为 A,虽然值没发生变化,但这种操作还是存在一定风险的
可以通过加时间戳或版本号的方式解决 ABA 问题:

public class ABATest {
public static void main(String[] args) {
showABA();
}

/**
 * 重现ABA问题
 */
private static void showABA() {
    AtomicReference<String> atomicReference = new AtomicReference<>("A");
    //线程X,模拟ABA问题
    new Thread(() -> {
        atomicReference.compareAndSet("A", "B");
        atomicReference.compareAndSet("B", "A");
    }, "线程X").start();

    //线程Y睡眠一会儿,等待X执行完
    new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        atomicReference.compareAndSet("A", "C");
        System.out.println("最终结果:" + atomicReference.get());
    }, "线程Y").start();
}

/**
 * 解决ABA问题
 */
private static void solveABA() {
    //初始版本号为1
    AtomicStampedReference<String> asr = new AtomicStampedReference<>("A", 1);

    new Thread(() -> {
        asr.compareAndSet("A", "B", 1, 2);
        asr.compareAndSet("B", "A", 2, 3);
    }, "线程X").start();

    new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        asr.compareAndSet("A", "C", 1, 2);
        System.out.println(asr.getReference() + ":" + asr.getStamp());
    }, "线程Y").start();
}

}
public class SpinLockDemo {
/**
* 初始值为 null
*/
AtomicReference atomicReference = new AtomicReference<>(null);

public static void main(String[] args) {
    SpinLockDemo spinLockDemo = new SpinLockDemo();

    new Thread(() -> {
        spinLockDemo.lock();
        spinLockDemo.unLock();
    }, "线程A").start();

    new Thread(() -> {
        spinLockDemo.lock();
        spinLockDemo.unLock();
    }, "线程B").start();
}

public void lock() {
    //获取当前线程对象
    Thread thread = Thread.currentThread();
    do {
        System.out.println(thread.getName() + "尝试获取锁...");
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //当赋值成功才会跳出循环
    } while (!atomicReference.compareAndSet(null, thread));
}

public void unLock() {
    //获取当前线程对象
    Thread thread = Thread.currentThread();
    //置为null,相当于释放锁
    atomicReference.compareAndSet(thread, null);
    System.out.println(thread.getName() + "释放锁...");
}

}
共享锁:也可称为读锁,可被多个线程持有
独占锁:也可称为写锁,只能被一个线程持有,synchronized和ReentrantLock都是独占锁
互斥:读读共享、读写互斥、写写互斥
读写分离,适用于大量读、少量写的场景,效率高

ReentrantReadWriteLock 中的读锁是共享锁、写锁是独占锁

class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

/**
 * 写锁控制写入
 */
public void put(String key, Object value) {
    lock.writeLock().lock();
    try {
        System.out.println(Thread.currentThread().getName() + "开始写入...");
        //睡一会儿
        TimeUnit.SECONDS.sleep(1);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入完成...");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.writeLock().unlock();
    }
}

/**
 * 读锁控制读取
 */
public Object get(String key) {
    lock.readLock().lock();
    try {
        System.out.println(Thread.currentThread().getName() + "开始读取...");
        //睡一会儿
        TimeUnit.SECONDS.sleep(1);
        Object value = map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取结束...value=" + value);
        return value;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.readLock().unlock();
    }
    return null;
}

public void clear() {
    map.clear();
}

}
public class ReentrantReadWriteLockDemo {
public static void main(String[] args) {
MyCache cache = new MyCache();
for (int i = 1; i <= 5; i++) {
int finalI = i;
new Thread(() -> {
cache.put(String.valueOf(finalI), String.valueOf(finalI));
cache.get(String.valueOf(finalI));
}, “线程” + i).start();
}
cache.clear();
}
}
亚马逊测评 www.yisuping.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值