JUC知识点学习

1. 什么是JUC?

JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包.

2. 并发和并行?

单核并发就是多个线程操作同一个资源.本质就是想让电脑效能更高.充分利用cpu资源.
并行就是多核执行多个任务.

3. 线程和进程

进程中至少包含一个线程.进程是程序的一次执行过程
一个程序可以有多个进程.
进程有:初始态,执行态,等待状态,就绪状态,终止状态。
在这里插入图片描述

线程:线程是CPU调度和分派的基本单位它可与同属一个进程的其他的线程共享进程所拥有的全部资源。.

进程和线程的区别

理解它们的差别,我从资源使用的角度出发。(所谓的资源就是计算机里的中央处理器,内存,文件,网络等等)

根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位

开销方面:每个进程都有独立的代码和数据空间(程序上下文),进程之间切换开销大;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小

所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

内存分配:系统为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源

包含关系:线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程
线程的五种状态.

NEW,新生
RUNNABLE,运行
BLOCKED,阻塞
WAITING,等待
TIMED_WAITING,等待一段时间超时等待.
TERMINATED;死亡

3.1 wait和sleep的区别:

  1. 他们来自不同的类wait 来自object,sleep来自Thred
  2. 关于锁的释放
    wait:会释放锁.
    sleep:不会释放锁.
  3. 使用范围不同
    wait必须在同步代码块中使用.
    sleep可以在任何地方使用.

4. Lock 锁

在这里插入图片描述公平锁:可以先来后到.
非公平锁:插队(默认)

4.1synchronized和Lock的区别

  • 一个是关键字一个是接口.
  • synchronized无法判断锁的状态,lock可以判断是否获取到了锁.
  • synchronized是全自动的可以自己释放锁.lock要手动释放锁否则会造成死锁.
  • synchronized当有线程阻塞,其他线程只能等待.Lock就不一定会等下去.所以通常Lock要跟着Try,catch,finally联合使用.
  • synchronized默认是可重入锁.不可以中断的非公平锁.lock可重入锁,可以判断锁,默认非公平.可以自己设置.
  • synchronized适合锁少量的代码同步问题.lock适合较为大量的灵活度很高.
    在这里插入图片描述

4.3锁是什么,如何判断锁的是谁?

为了更好地理解我们先要知道:

生产者消费者

在这里插入图片描述

步骤总体分为判断等待,业务,通知
具体代码实现

public class IncrementDemo {
    public static void main(String[] args) {
        T1 t=new T1();

        new Thread(()->{
            try {
                for (int i = 0; i < 30; i++) {
                    t.increment();
                    t.decrement();

                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                for (int i = 0; i < 30; i++) {
                    t.increment();
                    t.decrement();

                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
class T1{
    int num=0;
    public synchronized void  increment() throws InterruptedException {
        if (num!=0) {
            this.wait();
        }
        num++;
        System.out.println("+++++"+num);
        this.notifyAll();
    }
    public synchronized  void decrement() throws InterruptedException {
        if (num==0) {
            this.wait();
        }
        num--;
        System.out.println("----"+num);

        this.notifyAll();
    }
}

但是这种编程会出现虚假唤醒问题.假如我们有四个线程同时开启.得到的结果就会是非期望结果.
在这里插入图片描述这是因为if判断语句导致的所以应该使用while在这里插入图片描述

我们已经发现,已经出现了负数,显然已经出现了问题。我们来简单分析一下原因。其实很好理解,当两个消费者线程同时执行decrement方法时,产品售空,那么都将执行wait方法,处于挂起等待状态,并释放锁,然后生产者拿到锁,生产产品,执行notifyAll方法,唤醒了所有消费者线程,那么当第一个消费者执行了消费以后,第二个消费者又进行消费,此时便出现了负数,出现了问题。像这样的情况,就叫做虚假唤醒。
那么如果解决这个问题呢?我们只需要将判断的if换成while即可。换句话说,为了避免虚假唤醒问题,应该将判断一直使用在循环中。

4.4JUC版本的生产者消费者案例

比较一下使用方法的不同
在这里插入图片描述在这里插入代码片

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class IncrementDemo2 {
    
    
    public static void main(String[] args) {
    
    
        T2 t=new T2();

        new Thread(()->{
    
    
            try {
    
    
                for (int i = 0; i < 30; i++) {
    
    
                    t.increment();

                }

            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();

        new Thread(()->{
    
    
            try {
    
    
                for (int i = 0; i < 30; i++) {
    
    
                    t.decrement();

                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
    
    
            try {
    
    
                for (int i = 0; i < 30; i++) {
    
    
                    t.increment();

                }

            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();

        new Thread(()->{
    
    
            try {
    
    
                for (int i = 0; i < 30; i++) {
    
    
                    t.decrement();

                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();
    }
}
 class T2{
    
    
    int num=0;
    Lock lock=new ReentrantLock();
    Condition condition=lock.newCondition();
    public void  increment() throws InterruptedException {
    
    
        try {
    
    
            lock.lock();
            while (num!=0) {
    
    
                condition.await();
            }
            num++;
            System.out.println("+++++"+num);
            condition.signalAll();

        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }


    }
    public void decrement() throws InterruptedException {
    
    
        try {
    
    
            lock.lock();
            while (num==0) {
    
    
                condition.await();
            }
            num--;
            System.out.println("----"+num);
            condition.signalAll();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }

    }
}

相比之下和Condition有什么优势呢?
Condition可以精准的唤醒线程
接下来我们给线程分别起名字ABCD四个线程.让他有序的执行.

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class IncrementDemo3 {
    
    
    public static void main(String[] args) {
    
    
        T3 t=new T3();

        new Thread(()->{
    
    
            try {
    
    
                for (int i = 0; i < 30; i++) {
    
    
                    t.printA();

                }

            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        },"d").start();

        new Thread(()->{
    
    
            try {
    
    
                for (int i = 0; i < 30; i++) {
    
    
                    t.printB();

                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        },"c").start();
        new Thread(()->{
    
    
            try {
    
    
                for (int i = 0; i < 30; i++) {
    
    
                    t.printC();

                }

            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        },"b").start();

        new Thread(()->{
    
    
            try {
    
    
                for (int i = 0; i < 30; i++) {
    
    
                    t.printD();

                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        },"A").start();
    }
}

class T3{
    
    
   int num=0;
   Lock lock=new ReentrantLock();
   Condition c1=lock.newCondition();
   Condition c2=lock.newCondition();
   Condition c3=lock.newCondition();
   Condition c4=lock.newCondition();
    int i=1;
   public void  printA() throws InterruptedException {
    
    
       try {
    
    
           lock.lock();
           while (i!=1)
               c1.await();
           i=2;
           System.out.println(Thread.currentThread().getName()+"->"+"AAAAAAAAAAA");
                c2.signal();
       } finally {
    
    
            lock.unlock();
       }
   }
   public void  printB() throws InterruptedException {
    
    
       try {
    
    
           lock.lock();
           while (i!=2)
               c2.await();
           i=3;
           System.out.println(Thread.currentThread().getName()+"->"+"BBBBBBBBB");
                c3.signal();
       } finally {
    
    
            lock.unlock();
       }
   }
   public void  printC() throws InterruptedException {
    
    
       try {
    
    
           lock.lock();
           while (i!=3)
               c3.await();
           i=4;
           System.out.println(Thread.currentThread().getName()+"->"+"cCCCCCCCC");
                c4.signal();
       } finally {
    
    
            lock.unlock();
       }
   }
   public void  printD() throws InterruptedException {
    
    
       try {
    
    
           lock.lock();
           while (i!=4)
               c4.await();
           i=1;
           System.out.println(Thread.currentThread().getName()+"->"+"DDDDDDDD");
                c1.signal();
       } finally {
    
    
            lock.unlock();
       }
   }
}

5. 8锁现象

上代码

import java.util.concurrent.TimeUnit;

public class PhoneTest {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Phone phone=new Phone();
        new Thread(()->phone.sendMsg()).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->phone.call()).start();
    }
}
class Phone{
    
    
    public synchronized void sendMsg(){
    
    
        System.out.println("发短信");
    }
    public synchronized void call(){
    
    
        System.out.println("打电话");
    }
}

标准情况下永远是先打印sendMSg.这是因为有锁的存在.
synchronized锁的对象是调用该方法的对象.两个方法锁的是同一个对象谁先拿到谁先执行.代码中main方法是从上至下执行所以send方法先拿到锁

当方法被静态修饰时锁的是Class对象.

6、CopyOnArrayList

ArrayList在多线程环境下是不安全 的.比如:

 List list=new ArrayList();
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(()->{
    
    

                    list.add(UUID.randomUUID().toString());
                System.out.println(list);
            }).start();
        }

会报ConcurrentModificationException异常
为了解决线程不安全我们可以用Vector,使用Collections包中的工具比如
List list= Collections.synchronizedList(new ArrayList<>());
使用JUC下的CopyOnArrayList也可以解决cow写入时复制是计算机程序设计的一种优化策略在写入的时候避免覆盖,造成数据问题

CopyOnWriteArrayList 相比于Vector有什么优点
效率高:Vector都是synchronized修饰的方法.

7、CopyOnWriteSet

同理线程安全可以使用
Collection.SynchronizedSet
还可以使用
CopyOnWriteSet,底层原理实现就是HashMap

8、ConcurrentHashMap

在这里插入图片描述
实现原理:待续

9、Callable

多线程的第三种创建方式,可以有返回值
可以抛出异常
方法不同 run/call
方法测试

public class CallAbleDemo {
    
    
    public static void main(String[] args) {
    
    
        new Thread(new FutureTask<String>(new MyThread())).start();

    }
}
class MyThread implements Callable{
    
    

    @Override
    public Object call() throws Exception {
    
    
        System.out.println('s');
        return null;
    }
}

为什么在Thread构造中要添加FutureTask?
因为他实现了Runable接口,并且在FutureTask在构造器中允许有CallAble的实现

FutureTask.get会造成堵塞

总结:
Future他只会执行一次.结果会进行缓存.
可能会堵塞,需要等待

10.常用辅助类

10.1 CountDownLantch

他是一个减法计数器,当构造参数值等于0时则执行剩余代码否则会一直占用
在这里插入图片描述代码描述:

package lant;

import java.util.concurrent.CountDownLatch;

public class Countdown {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        CountDownLatch countDownLatch=new CountDownLatch(3);
        for (int i = 0; i <3; i++) {
    
    
            new Thread(()->{
    
    
                    System.out.println("go"+Thread.currentThread().getName());
                countDownLatch.countDown();

            },i+">>>>>>>>>>>").start();
        }
        countDownLatch.await();  // 等待计数器为0
        System.out.println("sss");
    }
}

10.2CyclicBarrier

可以简单理解为加法计数器

public class DemoCyclicBarrier {
    
    
    public static void main(String[] args) {
    
    
        CyclicBarrier cyclicBarrier=new CyclicBarrier(8,()->{
    
    
            System.out.println("召唤成功");
        });
        for (int i = 0; i < 8; i++) {
    
    
            new Thread(()->{
    
    
                try {
    
    
                    System.out.println("线程-1");
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
    
    
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

10.3Semaphore(信号量)

他就相当于一个定量容器,当容器沾满的时候需要容器中的线程执行完之后其它线程才能进入.否则会一直等待.

package lant;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    
    
    public static void main(String[] args) {
    
    
        Semaphore semaphore=new Semaphore(3);

        for (int i = 0; i < 6; i++) {
    
    
            new Thread(()->{
    
    
                try {
    
    
                    semaphore.acquire();
                    System.out.println("得到"+Thread.currentThread().getName());
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    semaphore.release();
                    System.out.println("lose"+Thread.currentThread().getName());
                }
            },i+"").start();
        }
    }
}

在这里插入图片描述

11.1ReadWriteLock

读可以被同时读写只能一个线程去写.
已知实现类是ReentrantReadWriteLock。
读和读:可以共存
读和写,写和写:不可共存

独占锁:写锁 只能被一个线程占用
共享锁:读锁 可以被多个占用

11.2阻塞队列

应用场景:线程并发处理,线程池。
在这里插入图片描述
在这里插入图片描述使用offer添加不会返回异常
使用poll删除不会抛异常,当无源素返回null
在这里插入图片描述

11.3 synchronizedqueue 同步队列

进去一个元素后必须等待取出来之后才能继续添加

12 线程池

池化技术:事先创建好资源放在池中,有需要直接拿。
开发手册是这样讲到:
在这里插入图片描述

好处

  1. 降低资源的消耗
  2. 提高响应速度
  3. 方便管理
  4. 线程复用,管理线程,控制线程最大数。

线程池:三大方法,七大参数,4中拒绝策略
三大方法

 public static void main(String[] args) {
    
    
//        ExecutorService service= Executors.newCachedThreadPool();遇强则强可以根据情况开辟线程总数
        ExecutorService service= Executors.newFixedThreadPool(5);   //只能开辟五个线程
//        ExecutorService service= Executors.newSingleThreadExecutor();//只有一个
        try {
    
    
            for (int i = 0; i < 100; i++) {
    
    
                final int i1=i;
                service.execute(()->{
    
    
                    System.out.println(Thread.currentThread().getName()+"->"+i1);
                });
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            service.shutdown();
        }
    }

七大参数

int corePoolSize,   核心线程数
 int maximumPoolSize,  最大线程数
long keepAliveTime,   超时释放线程时间
TimeUnit unit,        等待单位
BlockingQueue<Runnable> workQueue   阻塞队列
ThreadFactory threadFactory,  线程创建工厂
RejectedExecutionHandler handler  拒绝策略

核心线程数:当请求少量只会开启部分 ,不会开启到最大线程数。
最大线程数:当请求达到一定量后,可以开启最大的线程数
拒绝策略:当线程数已经开到最大,且阻塞队列最大值,则会触发拒绝策略
阻塞队列:当执行线程都被占用的时候,请求会存在阻塞队列中
超时等待:当业务都办理完成,非核心线程将等待一段时间如果还没有请求去调用这个线程就会自动关闭。

举个例子:
在这里插入图片描述
手写自定义线程池:


    public static void main(String[] args) {
    
    

        ExecutorService service =new ThreadPoolExecutor(
                2
                ,5
                ,2
                ,TimeUnit.SECONDS,new ArrayBlockingQueue<>(100)
                ,Executors.defaultThreadFactory()
                ,new ThreadPoolExecutor.AbortPolicy()
        );
        try {
    
    
            for (int i = 0; i < 100; i++) {
    
    
                final int i1=i;
                service.execute(()->{
    
    
                    System.out.println(Thread.currentThread().getName()+"->"+i1);
                });
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            service.shutdown();
        }
    }

当最大线程总数加上阻塞队列长度小于 线程总请求数的时候 使用拒绝策略
AbortPolicy() 会抛出异常
CallerRunsPolicy() 哪里来的回哪里去
DiscardPolicy() 不会抛出异常,超出线程的任务不会执行
DiscardOldestPolicy() 尝试去和第一个任务去竞争,成功则执行,失败则丢掉

最大线程该如何定义
cpu密集型: Runtime.getRuntime().availableProcessors()
通过代码获取cpu总核数。设置最大线程数。
io密集型:判断程序中十分消耗io的任务有几个,线程数一定要大于这几个任务。一般会设置成两倍

13 volatile

轻量级同步机制
保证可见性
不保证原子性
禁止重排序

保证可见性他与JMM挂钩。

什么是JMM?
关于jvm的一些同步约定

线程加锁前;t将主内存中的最新数据 刷新到 工作内存中
线程解锁前:将工作内存中的数据同步到主内存中
加锁解锁需要同一把锁

在这里插入图片描述
write和store换一下位置这个图错了;

volatile关键字的作用:保证了变量的可见性(visibility)。被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象。

不保证原子性
原子性:不可分割

这里只能通过加锁保证正确结果。

public class MyVolatile {
    
    
    static ReadWriteLock lock=new ReentrantReadWriteLock();
    public static void main(String[] args) {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            new Thread(()->{
    
    
                add();
            }).start();
        }
        while (Thread.activeCount()>2)
            Thread.yield();
        System.out.println(num);
    }
    static  void add(){
    
    
        lock.writeLock().lock();
        num++;
        lock.writeLock().unlock();

    }
    static int num;

}

那么如何保证原子性且不使用lock和synchronized。

基本类型juc 提供了Atomic类,在这里插入图片描述atomic类都和操作系统底层挂钩在内存中修改值,unsafe是很特殊的存在。

指令重排

虚拟机运行程序的时候并不是我们写程序时所期望的,他可能会指令重排,优化,但是不会影响结果,数据之间的依赖性约束着它

添加volatile 会添加内存屏障防止指令重排

且内存屏障在单例模式使用最多;

14 单例模式

饿汉式

package single;
//饿汉式单例模式会造成浪费空间 ,因为我们只想使用hungry实例 
public class Hungry {
    
    
    byte[] data=new byte[1024*1024];
    private static final Hungry HUNGRY=new Hungry();
    private   Hungry(){
    
    

    }
    public static Hungry getInstance(){
    
    
        return HUNGRY;
    }
}

使用final修饰可以避免反射通过成员属性修改。
dcl懒汉式

package single;

public class LazyMan {
    
    
    private LazyMan(){
    
    
        System.out.println(Thread.currentThread().getName());

    }
    private static volatile  LazyMan LAZY_MAN;

    public static  LazyMan getLazyMan(){
    
    
        if (LAZY_MAN==null){
    
    
            synchronized(LazyMan.class){
    
    
                if (LAZY_MAN==null)
                LAZY_MAN=new LazyMan();

            }
        }
        return LAZY_MAN;
    }

    public static void main(String[] args) {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            new Thread(()->{
    
    
                LazyMan.getLazyMan();
            },i+"").start();
        }
    }
}

这种操作在极端情况下也是有问题的。因为赋值阶段=new LazyMan()不是原子操作
创建实例大可分为三步

  • 分配内存空间
  • 执行构造方法初始化对象
  • 把对象指向这个空间

双if 有可能引用不为空,但是对象成员变量值未初始化。因为期间上面三个步骤可能会发生重排序 比如123 或132 为了解决这个问题需要在单例对象中添加volatile关键字,同步。

静态内部类实现单例

package single;

public class StaticClass {
    
    
    public static StaticClass getInsTance(){
    
    
        return Inner.instance;
    }
    static class Inner{
    
    
        public static StaticClass getInsTance(){
    
    
            return instance;
        }
        private static StaticClass instance=new StaticClass();
    }
}

但是这些方法都是不安全的我们可以通过反射来得到我们想要的实例并且可以破坏他。
但通过源码中getinstance方法得知如果类型是枚举类型,则会抛出异常。所以使用枚举实现单例是最安全的

Constructor中getinstance方法中有这样一段
在这里插入图片描述意思就是如果是枚举类则会抛出异常

枚举类继承的是Enum

public enum SingleEnum {
    
    
    instance;

}

通过jad 工具生成 java文件发现他其实是有参构造的

在这里插入图片描述
我们用反射强行得到实例试一试:

public enum SingleEnum {
    
    
    instance;

}
class Test{
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            Class<SingleEnum> singleEnumClass = SingleEnum.class;
            Constructor<SingleEnum> declaredConstructor = singleEnumClass.getDeclaredConstructor(String.class, int.class);
            SingleEnum singleEnum = declaredConstructor.newInstance();
            System.out.println(singleEnum);
            System.out.println(SingleEnum.instance);
            System.out.println(SingleEnum.instance);
        } catch (NoSuchMethodException e) {
    
    
            e.printStackTrace();
        } catch (InstantiationException e) {
    
    
            e.printStackTrace();
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        } catch (InvocationTargetException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
        }
    }
}

运行后发现报了我们意料之中的错误
在这里插入图片描述

所以用枚举实现单例也是最安全的;

15 深入CAS算法

cas 他保证的就是在不加锁的状态下数据的一致性.

具体就是 比如我们在有一个值0 现在要对他 +1 操作,自旋锁就会先判断 当前这个值是否还是 0,若是 那么直接 改成1,如果不是1是2那么 读取改过的值 并重新判断 是否当前值 是否还等于2.
说道CAS不得不提到AtomicXXX这个类,比如AtomicInteger,
其中有个getAndAdd(参数),底层实现是调用Unsafe类,unsafe类操作的是计算机内存,所以会很快。其中使用了自旋锁。

 public final int getAndIncrement() {
    
    
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

    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;
    }

compareAndSwapint是比较然后交换。这个方法和AtomicXX类中的CompareAndSet方法道理是一样的,只是更加底层,Var5是内存地址中的值如果var1对象的内存地址偏移值var2的值还是期望的var5,那么则执行var5+var4.这个过程相当于一个自旋锁。
CAS:比较工作内存中的值和主内存中的值,如果这个值是期望的,那么就执行操作。如果不是就一直循环(自旋锁)
此方法可能会造成线程阻塞
缺点:循环会耗时;
一次性只能保证一个共享变量的原子性;

ABA问题
比如一个线程A定义了一个数A=1;假如期望值也是1,那么执行CAS操作。这时候有个线程B修改了A=2,但是又迅速的将A改回了1.期间A是不知道的。所以就可能会出现问题。

package CAS;

import java.util.concurrent.atomic.AtomicInteger;

public class Demo1 {
    
    
    public static void main(String[] args) {
    
    
        AtomicInteger atomicInteger=new AtomicInteger(1);
        new Thread(()->{
    
    
            System.out.println(atomicInteger.compareAndSet(1, 2));

            System.out.println(atomicInteger.compareAndSet(2, 1));
        }).start();
        while (Thread.activeCount()>2){
    
    
            Thread.yield();
        }
        System.out.println(atomicInteger.compareAndSet(1, 3));

    }
}

乐观锁比较适用于读多写少的情况(多读场景),悲观锁比较适用于写多读少的情况(多写场景)。

16.原子引用

其实就是通过版本号来控制对象。当其他线程修改后,控制版本号加一,这就与我们期望的版本号不同了。所以会修改失败。

public class Demo2 {
    
    
    public static void main(String[] args) {
    
    
        AtomicStampedReference<Integer> atomicReference = new AtomicStampedReference<Integer>(1, 1);
        new Thread(() -> {
    
    
            System.out.println(atomicReference.compareAndSet(1, 2, atomicReference.getStamp(), atomicReference.getStamp() + 1));
            System.out.println(atomicReference.compareAndSet(2, 1, atomicReference.getStamp(), atomicReference.getStamp() + 1));
        }).start();
        while (Thread.activeCount() > 2) {
    
    
            Thread.yield();

            System.out.println(atomicReference.compareAndSet(1, 6666, 1, 2));
            System.out.println(atomicReference.getReference());

        } }
}

这里有个坑,Integer不要大于127,因为这关系到Integer的缓存机制,当值在-128~127之间会缓存到静态内部类中。具体可以参考
Integer缓存机制

18 各种锁

18.1重入锁

可重入锁。某个线程获得一把锁之后还可以在获取锁就是重入锁,synchronized和
ReenTrantLock都是重入锁。两种实现方式

public class ReenTrantLock {
    
    
    public static void main(String[] args) {
    
    
       final ReenTrantLock lock=new ReenTrantLock();
        new Thread(()->{
    
    
            lock.methodA();
        },String.valueOf("A")).start();
        new Thread(()->{
    
    
            lock.methodA();
        },String.valueOf("B")).start();
    }
    public synchronized void  methodA(){
    
    
        System.out.println(Thread.currentThread().getName()+"methodA");
        methodB();
    }
    public synchronized void  methodB(){
    
    
        System.out.println(Thread.currentThread().getName()+"methodb");
    }
}

ReenTrantLock:

 public void methodC(){
    
    
        try {
    
    
            reentrantLock.lock();
            System.out.println(Thread.currentThread().getName()+"methodC");
            methodD();
        } finally {
    
    
            reentrantLock.unlock();

        }
    }
    public void methodD(){
    
    
        try {
    
    
            reentrantLock.lock();
            System.out.println(Thread.currentThread().getName()+"methodD");
        } finally {
    
    
            reentrantLock.unlock();
        }

    }

18.2自旋锁.

自旋锁在cas中用到了,unsafe类中有个compareAndSwap方法就是一个自旋锁,原理就是循环,如果不是期望值就一直循环.会造成堵塞.
我们可以自定义一个自旋锁:

public class SpinLockDemo {
    
    
    AtomicReference<Thread> atomicReference=new AtomicReference<>();
    static SpinLockDemo spinLockDemo=new SpinLockDemo();

    public static void main(String[] args) {
    
    
        new Thread(()->{
    
    
            try {
    
    
                spinLockDemo.Lock();
                System.out.println("main");
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }finally {
    
    
                spinLockDemo.unLock();
            }
        }).start();
        new Thread(()->{
    
    
            try {
    
    
                spinLockDemo.Lock();
                System.out.println("main2");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }finally {
    
    
                spinLockDemo.unLock();
            }
        }).start();
    }
    public void Lock() throws InterruptedException {
    
    
        Thread thread = Thread.currentThread();
        while (!atomicReference.compareAndSet(null, thread)) {
    
    

        }
        System.out.println(thread.getName()+"加锁成功");
    }
    public void unLock()  {
    
    
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(thread.getName()+"解锁成功");
    }
}

18.3死锁

出现死锁就是多个线程都拿着对方的那把锁.

public class Dielock {
    
    
    static Object object=new Object();

    public static void main(String[] args) {
    
    

        Ticket ticket=new Ticket();
        new Thread(()->{
    
    
            while (true){
    
    
                if (Thread.currentThread().getName().equals("1")){
    
    
                    synchronized (object){
    
    
                        ticket.sell();
                    }
                }else
                    ticket.sell();

            }
        },"1").start();
        new Thread(()->{
    
    
            while (true){
    
    
                if (Thread.currentThread().getName().equals("1")){
    
    
                    synchronized (object){
    
    
                        ticket.sell();
                    }
                }else
                    ticket.sell();

            }

        },2+"").start();
    }
   static class Ticket{
    
    
        private int ticket=1000;
        public synchronized void sell(){
    
    
            synchronized (object){
    
    
                ticket--;
                System.out.println(ticket);
            }
        }
    }
}

解决方法:
使用jps定位进程号 jps -l在这里插入图片描述
使用jstack查看进程信息找到死锁信息.
在这里插入图片描述发现死锁.

当被问到这类问题的时候.有两种办法解决.
1查看日志
2查看堆栈信息.

猜你喜欢

转载自blog.csdn.net/weixin_43203363/article/details/109175599
今日推荐