18.彻底玩转单例模式
单例模式中分为饿汉式单例和懒汉式单例模式:
package JUC.SingletonMode;
//饿汉式单例模式
public class Hungry {
//饿汉式会一上来把全部的对象加载完毕可能会造成内存浪费
private final static Hungry hungry=new Hungry();
private final static byte[] data1=new byte[1024*1024];
private final static byte[] data2=new byte[1024*1024];
private final static byte[] data3=new byte[1024*1024];
private final static byte[] data4=new byte[1024*1024];
private Hungry(){
}
private Hungry getHungry(){
return hungry;
}
}
应为饿汉式单例模式可能会造成空间的浪费所以,出现了懒汉式单例模式:
package JUC.SingletonMode;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//懒汉式单例模式
public class LazyMan {
/**
* 这里一定要加volatile为了避免指令重排,加入内存屏障
* 应为一个类的创建为三步
* 1.分配内存空间 2.执行构造方法 3.把对象指向内存空间 如果指令重排了则为132着是不行的,如果这样则对象指向的空间是片虚无的为null!
* volatile的三个特性:1.保证线程的可见性2.非原子性3.避免指令重排
*/
private static volatile LazyMan lazyman;
//双重检测锁模式的懒汉式单例 DCL
public static LazyMan getLazyMan(){
if (lazyman==null){
synchronized (LazyMan.class){
if (lazyman==null){
lazyman=new LazyMan(); //不是一个原子性操作
}
}
}
return lazyman;
}
private LazyMan() {
System.out.println(Thread.currentThread());
}
}
但是这样如果使用反射的话依然不安全,必须使用枚举进行:
public enum EnumSingle {
INSTANCE;
}
使用枚举则不能进行反射:
package JUC.SingletonMode;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//枚举本身也是一种Class
public enum EnumSingle {
INSTANCE;
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
EnumSingle instance = EnumSingle.INSTANCE;
System.out.println(instance.hashCode());
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
//编译失败:Cannot reflectively create enum objects
EnumSingle enumSingle = declaredConstructor.newInstance();
}
}
19.深入理解CAS
什么是CAS?
CAS比较并交换它代表着原子类的一个方法compareAndSet(int expectedValue, int newValue)
表示是否到达了预期的值,如果到达了则交换否则不交换.
原子类是通过调用Unsafe类去修改内存的,导致效率极高!
如:
Unsafe类
应为这种操作向当于之间进行内存的操作所以效率非常的高!同时着也是一个标准的自旋锁!
缺点:
- 因为是自旋锁所以循环会耗时
- 一次只能保证一个共享变量的原子性问题
- 会存在ABA问题
CAS:ABA问题 (狸猫换太子)
package JUC.CAS;
import java.util.concurrent.atomic.AtomicInteger;
public class TestCAS02 {
public static void main(String[] args) {
AtomicInteger integer=new AtomicInteger(2020);
System.out.println(integer.compareAndSet(2020, 2021));
System.out.println(integer.get());
///捣乱的线程///
System.out.println(integer.compareAndSet(2021, 2020));
System.out.println(integer.get());
///
//期望的线程
System.out.println(integer.compareAndSet(2020, 6666));
System.out.println(integer.get());
}
}
20.原子引用
如果遇到了:ABA问题就使用AtomicStampedReference来进行原子性操作!
带版本号的原子操作:
package JUC.CAS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class TestCAS02 {
public static void main(String[] args) {
//注意这里的泛型如果使用包装类注意引用问题! 如Integer如果超过了+-1127则会新键一个对象,导致我们指向的值并不是一个值
AtomicStampedReference<Integer> integer = new AtomicStampedReference<>(1, 1);
new Thread(() -> {
//获得版本号
int stamp = integer.getStamp();
//暂定两秒
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("AA:"+integer.compareAndSet(1, 2, integer.getStamp(), integer.getStamp() + 1));
System.out.println("A1->"+integer.getStamp());
System.out.println("AA:"+integer.compareAndSet(2, 1, integer.getStamp(), integer.getStamp() + 1));
System.out.println("A1->"+integer.getStamp());
}, "A").start();
new Thread(() -> {
int stamp = integer.getStamp();
//暂定两秒
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("BB:"+integer.compareAndSet(1, 6, integer.getStamp(), integer.getStamp() + 1));
System.out.println("b1->"+integer.getStamp());
}, "B").start();
}
}
注意:Integer对象使用了缓存机制,默认范围是在-128到127,推荐使用静态工厂方法valueOf获取实例对象而不是new,因为valueOf使用缓存,而new一定会创造新的对象分配新的空间!
21.各种锁的理解
1.公平锁,非公平锁
- 公平锁:非常公平,不允许插队!(效率低)
- 非公平锁:非常不公平,允许插队!(效率高同时也是默认的)
2.可重入锁
package JUC.Lock02;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestReentrantLock {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(phone::dx,"A").start();
new Thread(phone::dx,"B").start();
new ReentrantLock();
}
}
class PhoneTest{
Lock lock=new ReentrantLock();
//注意锁必须配对!
public void dx(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"发短信!");
dh();
} finally {
lock.unlock();
}
}
public void dh(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"打电话!");
} finally {
lock.unlock();
}
}
}
3.自旋锁
自定义自旋锁:
package JUC.Lock02;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
AtomicReference<Thread> atomicReference=new AtomicReference<>();
//加锁
public void MyLock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"上锁");
//自旋锁
while (!atomicReference.compareAndSet(null,thread)){
}
}
//解锁
public void MyUnLock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(thread.getName()+"解锁");
}
}
4.死锁
package JUC.Lock;
import lombok.SneakyThrows;
import java.util.concurrent.TimeUnit;
public class TestDeadlock {
public static void main(String[] args) {
String LockA ="LOCKA";
String LockB="LCCKB";
new Thread(new Test(LockA,LockB),"X").start();
new Thread(new Test(LockB,LockA),"Y").start();
}
}
class Test implements Runnable{
String LockA;
String LockB;
public Test(String lockA, String lockB) {
LockA = lockA;
LockB = lockB;
}
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@SneakyThrows
@Override
public void run() {
synchronized (LockA){
System.out.println(Thread.currentThread().getName()+"获取"+LockA+"->想要去获取锁"+LockB+"中");
TimeUnit.SECONDS.sleep(2);
synchronized (LockB){
System.out.println(Thread.currentThread().getName()+"获取"+LockB+"->想要去获取锁"+LockA+"中");
}
}
}
}
解决问题:
1.使用:jps -l 定位进程号
2.使用jstack 进程号 查看找到死锁问题
互相拥有对方的锁进行等待!