JUC并发编程

JUC并发编程

进程和线程

并发和并行

并发(多个线程操作同一个资源)

  • CPU一核 模拟出来多条线程、

并行

  • CPU多核,多个线程可以同时执行
public class test1 {
    public static void main(String[] args) {
        //打印CPU核心数
        System.out.println(Runtime.getRuntime().availableProcessors());
    }

}
线程状态 (生命周期)

6个状态

public enum State {

    //新生
    NEW,


    //运行
    RUNNABLE,

    //阻塞
    BLOCKED,

    //等待 死死地等
    WAITING,


    //  超时等待
    TIMED_WAITING,

    
    
    //死亡
    TERMINATED;
}

wait、sleep的区别

wait–> Object

sleep -> Thread

wait 会释放锁

sleep 不会释放锁

wait必须在同步代码块中使用(调用wait()就是释放锁,释放锁的前提是必须要先获得锁,先获得锁才能释放锁。)

sleep可以在任何地方使用

wait不需要捕获异常

sleep必须捕获异常

LOCK锁(重点)

synchronized (可重入锁 悲观锁 独占锁 )

package com.xin.demo01;

public class test1 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sell();
            }

        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sell();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sell();
            }
        },"C").start();
    }

}

 class  Ticket{
    int num = 50;
    public synchronized void  sell(){
        if(num >0){
            System.out.println(Thread.currentThread().getName()+"卖出去了第"+num--+"张票剩余"+num);
        }

    }
 }

synchronized锁升级过程及其实现原理 (偏向锁 -> 轻量锁 -> 重量锁)

参考:https://blog.csdn.net/wangyy130/article/details/106495180/?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-1&spm=1001.2101.3001.4242

为什么需要锁升级?

应为最JDK1.6的时候Synchronized锁是重量锁,每次更新锁的状态就会进行系统调用,系统调用会涉及到用户态和内核态的切换。此过程比较复杂时间比较长,特别是同一个线程操作同一个资源的时候是不需要频繁地切换的,所以锁的升级就来了,事实上就是对synchronized的优化。它们并不是具体的锁,而是锁的几个状态。

锁升级原理: 事实上我们的锁的信息是存在对象头里的,占据3bit,还有使用此对象的线程id,线程获取资源的时候先判断锁的状态再来选择如何获得锁

img

还有就是有了CAS,也是锁升级的关键,不堵塞的关键

偏向锁:大多数情况下,锁不紧不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。(同一个线程频繁使用加了锁的资源时,且只有一个线程申请锁,默认使用轻量锁,是不堵塞的,且只有在第一次获得锁的时候去获得锁,平时不释放锁,直到要升级锁的时候)高并发时候偏向锁一般没有用,所以关闭偏向锁

获取锁过程:判断是偏向锁后,判断对象里存储的线程ID是否是此线程,是的话直接获得锁 否则通过CAS耗费CPU尝试将自己的线程id存储进当前锁对象的对象头中来获取偏向锁,没有有竞争的话就获得锁,否则开始锁竞争进行锁升级过程,升级为轻量级锁。

轻量锁(自旋锁): 当有一个线程加入竞争轻量锁的队列中,偏向锁会升级为轻量锁,竞争的线程会不断地自旋CAS竞争锁,也是不阻塞的。

重量锁: 当自旋超过一定的时间,或者有第三个线程加入竞争,会升级为重量锁,重量锁就是我们熟悉的Synchronized,独占、阻塞、

几种锁状态优缺点对比:

在这里插入图片描述

  • 只有一个线程进入临界区 -------偏向锁
  • 多个线程交替进入临界区--------轻量级锁
  • 多个线程同时进入临界区-------重量级锁

Lock (可重入锁 悲观锁 独占锁 默认不公平锁)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HC9b130C-1615907409126)(image-20201202221545534.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-skKYxrBA-1615907409134)(image-20201202225200384.png)]

公平锁:先来后到

不公平锁:可以插队(默认非公平)

public class test2 {
    public static void main(String[] args) {
        Ticket2 ticket = new Ticket2();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sell();
            }

        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sell();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sell();
            }
        },"C").start();
    }

}

class  Ticket2{
    Lock lock = new ReentrantLock();
    int num = 50;
    public  void  sell(){
        lock.lock();
        try {
            if(num >0){
                System.out.println(Thread.currentThread().getName()+"卖出去了第"+num--+"张票剩余"+num);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }}

synchronized 和 lock 的区别

1.synchronized是java的一个修饰符 lock是一个类

2.synchronized 可重入锁 不可中断 非公平锁 ;lock 可重入锁 可中断 可以设置公平或非公平锁

3.synchronized 是自动释放锁;lock需要手动加锁解锁

4.synchronized遇到阻塞会死死地等待;lock可以使用trylock()来尝试获得锁

5.synchronizd适合锁代码量较小的代码块。;lock适合锁代码量大的;

6.synchronizd无法判断锁的状态,lock可以判断是否获得了锁。

7.synchronizd是隐式锁,lock是显示锁

锁是什么?如何判断锁是谁?

生产者和消费者问题

synchronized版

/*
线程之间的通信问题: 生产者和消费者而之间的问题  等待  和通知唤醒
线程交替执行 AB之间操作同一个变量 num=0
 */

public class A {
    public static void main(String[] args) {
     Data data = new Data();
     new Thread(()->{
         for (int i = 0; i < 20; i++) {
             try {
                 data.increment();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     },"A").start();

        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

    }

}
//判断等待 业务 通知
class Data {
    private int number = 0;
    public synchronized  void increment() throws InterruptedException {
        if(number!=0){
        this.wait();//等待
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"->"+number);
        //通知其他线程我+1完毕了
        this.notifyAll();

    }

    public synchronized void decrement() throws InterruptedException {
        if(number==0){
        this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"->"+number);
        //通知其他线程我-1完毕了
        this.notifyAll();
    }
}
问题存在:ABCD 四个线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HBp1JDXO-1615907409137)(image-20201203131834714.png)]

使用if的话 线程唤醒会从wait处继续执行 if不会继续判断就会导致虚假唤醒 但是while会先进行while判断

为什么加while就可以解决虚假唤醒问题 因为唤醒之后是从wait处继续执行,会再次进入while进行判断,但是if的话直接执行下一步

package com.xin.pc;

/*
线程之间的通信问题: 生产者和消费者而之间的问题  等待  和通知唤醒
线程交替执行 AB之间操作同一个变量 num=0

 */

public class A {
    public static void main(String[] args) {
     Data data = new Data();
     new Thread(()->{
         for (int i = 0; i < 20; i++) {
             try {
                 data.increment();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     },"A").start();

        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();

    }

}
//判断等待 业务 通知
class Data {
    private int number = 0;
    public synchronized  void increment() throws InterruptedException {
        while (number!=0){
        this.wait();//等待
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"->"+number);
        //通知其他线程我+1完毕了
        this.notifyAll();

    }

    public synchronized void decrement() throws InterruptedException {
        while(number==0){
        this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"->"+number);
        //通知其他线程我-1完毕了
        this.notifyAll();
    }
}

LOCK版

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-csImo8XC-1615907409139)(image-20201203135635626.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V3mDiEao-1615907409142)(image-20201203133029746.png)]

package com.xin.pc;

/*
线程之间的通信问题: 生产者和消费者而之间的问题  等待  和通知唤醒
线程交替执行 AB之间操作同一个变量 num=0

 */

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

public class B {
    public static void main(String[] args) {
     Data data = new Data();
     new Thread(()->{
         for (int i = 0; i < 20; i++) {
             try {
                 data.increment();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     },"A").start();

        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();

    }

}

//判断等待 业务 通知
class Data2 {
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    public   void increment() throws InterruptedException {
       lock.lock();

        try {
            while (number!=0){
            //等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            //通知其他线程我+1完毕了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }


    }

    public synchronized void decrement() throws InterruptedException {
       lock.lock();
        try {
            while(number==0){
            //等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            //通知其他线程我-1完毕了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}
好处 condition精准地唤醒和线程

通过创建多个condition来控制线程

package com.xin.pc;

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

public class C {
    public static void main(String[] args) {
        Data3 data3 = new Data3();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                data3.printA();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                data3.printB();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                data3.printC();
            }
        },"C").start();
    }
}
class Data3{
    private Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    private int number = 1;
    public void printA(){
        lock.lock();
        try {
          while(number!=1){
              condition1.await();
          }
          number++;
          System.out.println("AAAAAAAAAA"+number );
          condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void printB(){
        lock.lock();
        try {
            while(number!=2){
            condition2.await();
        }
                number++;
                System.out.println("BBBBBB"
                +number);
            condition3.signal();

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

    public void printC(){
        lock.lock();
        try {
            while(number!=3){
                condition3.await();

            }
             number=1;
            System.out.println("CCCCCCCC");
            condition1.signal();

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

}

8锁现象

深刻理解我们的锁

1.带有synchronized 锁的方法 谁先拿到锁谁先执行

/*
8锁 就是锁的8个问题
带有synchronized 锁的方法 谁先拿到锁谁先执行
 */
public class test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            phone.sendMsg();
        }, "A").start();
        new Thread(() -> {
            phone.call();
        }, "B").start();
    }
}

class Phone {
    //锁的对象是方法的调用者
    //两个对象用同一把锁
    public synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send message");
    }

    public synchronized void call() {
        System.out.println("call");
    }
}

2.如果有普通的方法是不受锁的限制的


public class test2 {
    public static void main(String[] args) {
        Phone1 phone1 = new Phone1();
        new Thread(() -> {
            phone1.sendMsg();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone1.hello();
        }, "B").start();
    }
}

class Phone1 {
    //锁的对象是方法的调用者
    //两个对象用同一把锁
    public synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send message");
    }

    public synchronized void call() {
        System.out.println("call");
    }
    //这里没有锁不受锁的影响
    public void hello(){
        System.out.println("hello");
    }
}

3.如果调用锁的是两个的对象 就是两把锁 互不干扰

 Phone1 phone1 = new Phone1();
 Phone1 phone2 = new Phone1();
       

4.如果是两个static方法,锁的就是class对象模板(唯一) 所以看谁先获得锁


public class test3 {
    public static void main(String[] args) {
        Phone3 phone3 = new Phone3();

        new Thread(() -> {
            phone3.sendMsg();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone3.call();
        }, "B").start();
    }
}

class Phone3 {
    //锁的对象是方法的调用者
    public static synchronized  void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send message");
    }

    public static synchronized  void call() {
        System.out.println("call");
    }

}

5.如果建立两个对象的话也是先执行先获得锁的对象的方法,因为锁的是class对象 只有一个

Phone3 phone3 = new Phone3();
Phone3 phone4 = new Phone3();

6.一个是静态同步方法一个是普通同步方法,一个对象,一个是class一个是调用的对象 所以互不干扰

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

        new Thread(() -> {
            phone.sendMsg();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone.call();
        }, "B").start();
    }
}

class Phone4 {
   //静态同步方法
    public static synchronized  void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send message");
    }
   //普通同步方法
    public  synchronized  void call() {
        System.out.println("call");
    }

}

7.一个是静态同步方法一个是普通同步方法,两个对象,一个是class一个是调用的对象 所以互不干扰

Phone4 phone = new Phone4();
Phone4 phone1 = new Phone4();
new Thread(() -> {
    phone.sendMsg();
}, "A").start();

try { 
    TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
    e.printStackTrace();
}

new Thread(() -> {
    phone1.call();
}, "B").start();

集合类不安全

CopyOnWriteAaaryList

//并发修改异常ConcurrentModificationException
public class ListTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
  list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}
解决方法

1.使用Collections的synchronizedXXX方法 底层synchronized

public static void main(String[] args) {
    List<String> list = Collections.synchronizedList(new ArrayList<>());
    for (int i = 0; i < 100; i++) {
        new Thread(()->{
        list.add(UUID.randomUUID().toString().substring(0,5));
        System.out.println(list);
        },String.valueOf(i)).start();
    }
}

2.使用线程安全的Vector 底层synchronized

List<String> list = new Vector<>();

public synchronized boolean add(E e) {
        modCount++;
        add(e, elementData, elementCount);
        return true;
    }

3.使用JUC包下的copyOnWriteArraylist 底层Lock

1、读写分离,读和写分开 只给写的时候add加锁 lock ,保证只有一个线程在添加,读的时候不加锁 和写锁不同的是可加锁的时候可以读
2、最终一致性 (CopyOnWrite只能保证数据最终的一致性,不能保证数据的实时一致性。)
3、使用另外开辟空间的思路,来解决并发冲突.但是会浪费空间

List<String> list = new CopyOnWriteArrayList<>();

   public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();//先复制一份模板
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }

CopyOnWriteSet

同理发生ConcurrentModificationException异常

public static void main(String[] args) {
    Set<String> set = new HashSet<>();
    for (int i = 0; i < 100; i++) {
       new Thread(()->{
           set.add(UUID.randomUUID().toString().substring(0,5));
           System.out.println(set);
       },String.valueOf(i)).start();
    }
}
解决方法

1.使用Collections的synchronizedXXX方法

  Set<String> set =  Collections.synchronizedSet(new HashSet<>());

2.使用JUC包下的copyOnWriteArraylist 底层Lock

  Set<String> set = new CopyOnWriteArraySet<>();

CurrentHashMap

实现原理(写的很好)

https://blog.csdn.net/V_Axis/article/details/78616700?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control

1.使用Collections的synchronizedXXX方法

   Map<String,String> set =  Collections.synchronizedMap(new HashMap<>());

2.使用JUC包下的ConcurrentHashMap

   Map<String,String> map =  new ConcurrentHashMap<>();

肯定是兼顾了***线程安全和运行效率***的ConcurrentHashMap好点的,首先性能,synchronized会将整个集合上锁,导致其他操作阻塞

原理 :将hashmap再次分成多个map,放在segment中 做两次hash

计算size比较麻烦,为了不锁所有segment,首先乐观地假设size过程中不会有修改。当尝试一定次数,才无奈转悲观,锁住所有segment以保证一致性。

​ 将hashMap分为多个segment,这样我们就可以减小锁的粒度.对同一个segment加锁,不同的segment互不干扰,只对修改操作加锁,对于修改操作只有拥有段锁的才能执行

Callable

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zwGD5GE6-1615907409147)(image-20201204133109361.png)]

(1).实现Callable接口和Runnable接口实现类,都是可以被线程执行的任务

(2).Callable接口抛异常,Runnable接口不抛异常

(3).Callable任务运行后,可以获取current包Future对象,Future对象可以获取任务返回值,Future表示异步计算的结果,提供了检测计算是否完成的方法get(),通过cancel(task)方法,可以取消执行中的任务 Runnable接口不行

(4).类实现Callable接口,要实现Call方法,类实现Runnable接口,要实现run()方法

public class callableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallTest callTest = new CallTest();
        FutureTask<Integer> futureTask = new FutureTask<>(callTest);//包装类  因为FutureTask是runable的实现类
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();//对于相同的结果会存在缓存中,只执行一次
        Integer integer = futureTask.get();//可能会造成阻塞
        System.out.println(integer);
    }
}
class CallTest implements Callable<Integer> {


    @Override
    public Integer call() throws Exception {
        System.out.println("call()");
        return 1024;
    }
}

细节:

1、有缓存

2、结果可能需要等待,会阻塞

常见的辅助类(必会)

CountDownLatch –

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UcePPtoW-1615907409149)(image-20201204135730434.png)]

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"get out");
                countDownLatch.countDown();
            },String.valueOf(i)).start();

        }
        countDownLatch.await();//直到为0 不等待
        System.out.println("close door");
    }
}

原理

CountDownLatch countDownLatch = new CountDownLatch(6); 起始为6

countDownLatch.countDown();线程调用一次就-1

countDownLatch.await()直到0结束wait

CyclicBarrier ++

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g5joqClB-1615907409150)(image-20201204141014637.png)]

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclic = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙");
        });
        for (int i = 0; i < 7; i++) {
            final int tmp = i;
            new Thread(()->{
                System.out.println(tmp+"召唤");
                try {
                    cyclic.await();
                    System.out.println(tmp+"召唤zhihou");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

}

    /**
     *
     4召唤
     1召唤
     2召唤
     6召唤
     0召唤
     5召唤
     3召唤
     召唤神龙
     3召唤zhihou
     1召唤zhihou
     2召唤zhihou
     6召唤zhihou
     0召唤zhihou
     5召唤zhihou
     4召唤zhihou
     */

原理

CyclicBarrier cyclic = new CyclicBarrier(7, () -> { System.out.println("召唤神龙");}); 参数是数字加接口 达到这个数字就执行这个接口

cyclic.await(); 每次+1等待他执行 到达后其他线程执行后续的操作

Semaphore(相当于阻塞队列) 多个++

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wV47JCpm-1615907409152)(image-20201204142242811.png)]

public class SemaporeDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 6; i++) {
            final int tmp = i;
            new Thread(() -> {
                try {
                    semaphore.acquire(); //++
                    System.out.println(tmp + "进去了");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(tmp + "离开了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();// --
                }
            }, String.valueOf(i)).start();
        }
    }
}

semaphore.acquire();请求 ,当信号量满了就会等待

semaphore.release();释放, 唤醒等待的线程

ReadWriteLock

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-awvKkEbH-1615907409154)(image-20201204145702212.png)]

/*
共享锁(读锁)
独占锁(写锁)

读锁 读锁 可以共存
读锁 写锁 不能共存
写锁 写锁 不能共存
 */
public class ReadWriteLockDemp {
    public static void main(String[] args) {
        Cache cache = new Cache();
        //写入
        for (int i = 1; i <= 6; i++) {
            int tmp = i;
            new Thread(()->{
            cache.put(tmp,tmp);
            },String.valueOf(i)).start();
        }
        //读取
        for (int i = 1; i <= 6; i++) {
            int tmp = i;
            new Thread(()->{
                cache.get(tmp);
            },String.valueOf(i)).start();
        }
    }
}
class Cache{
    private ReadWriteLock lock = new ReentrantReadWriteLock();//锁的细粒度
    private volatile HashMap<Integer,Integer> map = new HashMap<>();
    public void put(int k,int v){
        lock.writeLock().lock();

        try {
            System.out.println(Thread.currentThread().getName()+"写入");
            map.put(k,v);
            System.out.println(Thread.currentThread().getName()+"写入成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }
    public void get(int k){
        lock.readLock().lock();
        int result = 0;
        try {
            System.out.println(Thread.currentThread().getName()+"读取");
             map.get(k);
            System.out.println(Thread.currentThread().getName()+"读取成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();

        }

    }
}

阻塞队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dK2JEuuu-1615907409155)(image-20201204154422416.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bu1ODiOq-1615907409160)(image-20201204154527777.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gErMom6V-1615907409162)(image-20201204155146883.png)]

什么情况下我们会使用到阻塞队列:多线程并发处理,线程池

四组API

方式抛出异常返回值阻塞等待超时等待
添加addofferputoffer
删除removepolltakepoll
移除队列首elementpeek--
/*
抛出异常
*/
public class Test {
    public static void main(String[] args) {
        test1();
    }

    public static void test1() {
        ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
        System.out.println(queue.add(1));
        System.out.println(queue.add(2));
        System.out.println(queue.add(3));
        System.out.println(queue.element());//返回队首
            //抛出异常
    //    System.out.println(queue.add(1));
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());

        System.out.println(queue.remove());

    }
}
/*
有放回值没有抛出异常
 */
public static void test2(){
    ArrayBlockingQueue<Object> q = new ArrayBlockingQueue<>(3);
    System.out.println(q.offer(1));
    System.out.println(q.offer(2));
    System.out.println(q.offer(3));
    System.out.println(q.offer(3));//false
    System.out.println(q.peek());//返回队首

    System.out.println(q.poll());
    System.out.println(q.peek());//返回队首
    System.out.println(q.poll());

    System.out.println(q.poll());
    System.out.println(q.poll());//null
}
/*
阻塞等待
 */
public static void test3() throws InterruptedException {
    ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3);
    queue.put(1);
    queue.put(2);
    queue.put(3);
    //queue.put(3);//会阻塞等待

    System.out.println(queue.take());
    System.out.println(queue.take());
    System.out.println(queue.take());
    System.out.println(queue.take());//会阻塞等待

}
/*
超时等待
 */
public static void test4() throws InterruptedException {
    ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3);
    queue.offer(1);
    queue.offer(2);
    queue.offer(3);
    queue.offer(3,2, TimeUnit.SECONDS);//阻塞等待2秒

    System.out.println(queue.poll());
    System.out.println(queue.poll());
    System.out.println(queue.poll());
    System.out.println(queue.poll(2,TimeUnit.SECONDS));//null
}

SynchronousQueue 同步队列

/**
 * 同步队列
 * 和其他的BlockingQueue不一样 SynchronizedQueue 不存储元素
 * put一个元素 必须等里面先取出来否则不能再put
 */
public class SynchronizedQueueDmo {
    public static void main(String[] args) {
        BlockingQueue<Integer> que = new SynchronousQueue<>();
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"1");
                que.put(1);
                System.out.println(Thread.currentThread().getName()+"2");
                que.put(2);
                System.out.println(Thread.currentThread().getName()+"3");
                que.put(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1").start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+"取了"+que.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+"取了"+que.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+"取了"+que.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();
    }
}

SynchronousQueue 不具有任何内部容量,甚至不具有一的容量,我们可以用来在线程间安全的交换单一元素。所以功能比较单一,优势应该就在于轻量吧~

线程池

线程池: 三大方法 7大参数 4种拒绝策略

线程池运行过程

池化技术

线程池 连接池 内存池 对象池 …创建和销毁十分浪费资源

池化技术: 事先准备好一些资源,有人要用来这里拿,用完再还回去

线程的好处

怎么分析呢?首先线程池最大的作用就是线程的复用,因为线程创建和销毁需要花费大量的CPU资源,那么除了可以线程复用还有什么好处呢?我们可以从线程池的七大参数来分析,首先我们可以设置线程的最大并发数,更好管理线程,其次,对于等待的线程我们可以设置最优的拒绝策略来处理线程,或者使用队列来将线程缓存处理,我们还可以使用不同的线程池处理不同的方向的任务,实现业务独立不互相影响。还有线程池提供一些方法实现定时执行、周期执行。

总结如下:

  • 利用线程池管理并复用线程、控制最大并发数等。
  • 实现任务线程队列缓存策略和拒绝机制。
  • 实现某些与时间相关的功能,如定时执行、周期执行等。
  • 隔离线程环境。比如,交易服务和搜索服务在同一台服务器上,分别开启两个线程池,交易线程的资源消耗明显要大;因此,通过配置独立的线程池,将较慢的交易服务与搜索服务隔开,避免个服务线程互相影响。

线程复用、可以控制最大并发数、管理线程

线程池 三大方法

public class Demo1 {
    public static void main(String[] args) {
        //三大方法
        // ExecutorService service = Executors.newSingleThreadExecutor();//创建单个线程池
        //ExecutorService service = Executors.newFixedThreadPool(5);//提供设置好的线程数
        ExecutorService service = Executors.newCachedThreadPool();//遇强则强

        try {
            for (int i = 0; i < 10; i++) {
                service.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            service.shutdown();
        }
    }
}

7大参数

源码分析

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1,
                                    1,
                                    0L,
                                    TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
      
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
//本质是:ThreadPoolExecutor


public ThreadPoolExecutor(int corePoolSize,//线程池大小
                              int maximumPoolSize,//最大线程池大小
                              long keepAliveTime,//超时了  没人调用就会释放
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//阻塞队列
                              ThreadFactory threadFactory,//线程工厂创建线程的一般不用动
                              RejectedExecutionHandler handler) {//拒绝策略
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IWTYNdVQ-1615907409164)(image-20201204183650521.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4lbjnpwk-1615907409165)(image-20201204183917491.png)]

自定义一个线程池


//最大线程该如何定义
ExecutorService service = new ThreadPoolExecutor(2,
                                                 5,
                                                 3L,
                                                 TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),
         new ThreadPoolExecutor.AbortPolicy());

四大拒绝策略


/**
AbortPolicy  银行满了 还有人进来 不处理这个人  抛出异常
DiscardPolicy 队列满了 丢掉任务  不会抛出异常
DiscardOldestPolicy 尝试和最早的线程竞争,成功就执行 不成功就丢掉  不会抛出异常
CallerRunsPolicy 哪来的去哪里  有可能抛给main 所有可能随便抛出一个线程
*/

IO密集型和CPU密集型

最大线程数怎么定义

  • CPU密集型: 根据CPU的核数来定义最大的线程数,Runtime.getRuntime().availableProcessors()获取系统的CPU核数 保证效率最高

  • IO密集型: 根据最耗IO的线程,选择大于这些IO线程的线程数因为IO线程十分耗资源

四大函数接口

新时代的程序员: lambda表达式 链式编程 函数式接口 Stream流式计算

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13Dj7eiD-1615907409165)(image-20201205131620709.png)]

function

//有一个输入有一个输出
public static void main(String[] args) {

//        Function<String, String> stringStringFunction = new Function<String, String>(){
//            @Override
//            public String apply(String s) {
//                return s;
//            }
//        };
        Function<String, String> function = (str)->{return str;};//lambda表达式
        System.out.println(function.apply("a"));
    }

predicate

public static void main(String[] args) {
/**
* 断定式函数接口  输入一个参数返回一个布尔值
*/
//        Predicate<String> predicate = new Predicate<>() {
//            @Override
//            public boolean test(String s) {
//                return s.isEmpty();
//            }
//        };
        Predicate<String> predicate = (s)->{return s.isEmpty();};
        System.out.println(predicate.test(""));
    }

Consumer 消费型接口

    public static void main(String[] args) {
        /**
         * Consumer 只有输入没有返回值
         */
//        Consumer<String> consumer = new Consumer<>() {
//            @Override
//            public void accept(String s) {
//                System.out.println(s);
//            }
//        };
        Consumer<String> consumer = (s)->{
            System.out.println(s);};

        consumer.accept("sdfsdf");

    }

Supplier 供给型接口

    public static void main(String[] args) {
        /**
         * Supplier 只有返回值没有输入
         */
//        Supplier<Integer> supplier = new Supplier<>() {
//
//            @Override
//            public Integer get() {
//                return 1024;
//            }
//        };
        Supplier<Integer> supplier =()->{return  1024;};
        System.out.println(supplier.get());

    }

String流式计算

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nkW8eUXY-1615907409167)(image-20201205140446955.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wNl5Fo4b-1615907409168)(image-20201205140932664.png)]

ForkJoin

有点像归并排序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YfMUSEPK-1615907409170)(image-20201205141636449.png)]

工作窃取

使用双端队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wxB0buZR-1615907409171)(image-20201205141824196.png)]

forkjoin

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V27ybVJt-1615907409173)(image-20201205142535249.png)]

/**
 * 如何使用forkjoin
 * 1.forkjoinpool 通过它来执行
 * 2.计算任务forkjoinpool.excute(ForkjoinTask)
 * 3.计算类要继承ForkJoinTask
 */
public class forkjoindemo extends RecursiveTask<Long>{
    private  long start;
    private  long end;
    private long temp = 10000L;//临界值

    public forkjoindemo(long start, long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if((start-end)<temp){
            long sum = 0;
            for (long i = start; i <=end ; i++) {
              sum+=i;
            }
            return sum;
        }else
        {//forkjoin//递归
        long middle = (end-start)/2;
         forkjoindemo task1 = new forkjoindemo(start,middle);
         task1.fork();//把线程压入消息队列
         forkjoindemo task2 = new forkjoindemo(middle+1,end);
         task2.fork();
            return task1.join()+task2.join();
        }

    }
}

测试一下三种方法

普通方法 forkjoin Stream流计算

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
       //test1(0, 50_0000_0000L);//2447
       //test2(0, 50_0000_0000L);//3511 还是大数据比较好用
       //test3(0, 50_0000_0000L);//1227

    }
    public static long test1(long s,long e){
        long sum = 0;
        long st = System.currentTimeMillis();
        for (long i = s; i <= e; i++) {
            sum+=i;
        }
        long et = System.currentTimeMillis();
        System.out.println(sum+" "+(et-st));
        return sum;
    }
    public static long test2(long s,long e) throws ExecutionException, InterruptedException {
        long st = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask task = new forkjoindemo(s, e);
        ForkJoinTask submit = forkJoinPool.submit(task);
        long sum=(long) submit.get();
        long et = System.currentTimeMillis();
        System.out.println(sum+" "+(et-st));
        return sum;
    }
    public static long test3(long s,long e){
        long st = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(s, e).parallel().reduce(0, Long::sum);
        long et = System.currentTimeMillis();
        System.out.println(sum+" "+(et-st));
        return sum;
    }
}

异步回调

future 初衷 : 对未来某个时间的结果进行建模

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bMoJJ2ku-1615907409175)(image-20201205152210170.png)]

package com.xin.frture;

import java.sql.SQLOutput;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 异步调用
 * 异步执行
 * 成功回调
 * 失败回调
 */
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        //没有返回值的runAsync 异步回调
//        CompletableFuture <Void> completableFuture = CompletableFuture.runAsync(()->{
//            try {
//                TimeUnit.SECONDS.sleep(3);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName()+"runAsync=>void");
//        });
//        System.out.println("1111");
//        completableFuture.get();//阻塞获取结果
//        System.out.println("1111");
        //有返回值的runAsync 异步回调
        //ajax 成功和失败的回调
        CompletableFuture <Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"runAsync=>Interger");
            int v = 1/0;
            return 1024;
        });
        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t=" + t);//正常的返回结果
            System.out.println("u=" + u);//错误信息
        }).exceptionally((e) -> {//错误的返回结果
            System.out.println(e.getMessage());
            return 203;
        }).get());

    }


}

JMM

请你谈谈对Volatile的理解

Volatile是java提供的一个轻量级的同步机制

1.保证可见性

2.不保证原子性

3.禁止指令重排

什么是JMM

JMM : java内存模型 虚拟机 概念约定

1.线程解锁前,必须立刻把内存刷回主存

2.线程加锁前,必须读取主存中的最新值到工作内存中

线程 工作内存 主存

八种操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-17aaRGwH-1615907409179)(image-20201205160504412.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qh5jkvEE-1615907409180)(image-20201205160614580.png)]

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

    • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
    • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
    • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
    • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
    • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
    • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
    • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
    • write  (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

JMM对这八种指令的使用,制定了如下规则:

    • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
    • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
    • 不允许一个线程将没有assign的数据从工作内存同步回主内存
    • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
    • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
    • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
    • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
    • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

JMM对这八种操作规则和对volatile的一些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安全的了。但是这些规则实在复杂,很难在实践中直接分析。所以一般我们也不会通过上述规则进行分析。更多的时候,使用java的happen-before规则来进行分析。

问题:线程不知道主存中的值已经发生变化

public class JMMDemo {
    private static int num = 0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while (num==0){}
            
        }).start();
        TimeUnit.SECONDS.sleep(1);
        num=1;
        System.out.println(num);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0J7PMMnT-1615907409182)(image-20201205161214212.png)]

主内存和本地内存的关系
JMM规定的主内存和本地内存的关系

所有的变量都存储在主内存中, 同时每个线程也有自己独立的工作内存, 工作内存中所有的变量内容都是主内存的拷贝.
线程不能直接读写主内存中的变量, 而是只能操作自己工作内存中的变量, 再同步到主内存中 . 即在修改变量之前, 要先执行第一步规定的,把数据拷贝到工作内存中再修改, 再同步.
主内存是多个线程共享的 , 但线程间不共享工作内存, 如果线程间需要通信, 必须借助主内存中转来完成.
总结: 所有的共享变量, 是存在于主内存中的, 每个线程有自己的本地内存, 而且线程读写共享数据也是通过本地内存交换的,正是由于有交换的过程, 并且这个交换不是实时的, 所以才导致了可见性的问题.

Volatile

参考 https://blog.csdn.net/lc13571525583/article/details/90345760

保证可见性

volatile是两条实现原则:
1.Lock前缀指令会引起处理器缓存会写到内存
当对volatile变量进行写操作的时候,JVM会向处理器发送一条lock前缀的指令,将这个缓存中的变量回写到系统主存中
2.一个处理器的缓存回写到内存会导致其他处理器的缓存失效
处理器使用嗅探技术保证内部缓存 系统内存和其他处理器的缓存的数据在总线上保持一致。

上一个问题的解决

public class JMMDemo {
    //加上volatile保证可见性
    private static volatile int num = 0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while (num==0){}
        }).start();
        TimeUnit.SECONDS.sleep(1);
        num=1;
        System.out.println(num);
    }
}

不保证原子性

线程A在执行任务的时候,不能被打扰,也不能被分割

public class Demo02 {
    private static volatile int num = 0;//不保证原子性
    public static synchronized void add(){//保证原子性
        num++;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int i1 = 0; i1 < 1000; i1++) {
                    add();
                }
            }).start();
        }
        while(Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(num);
    }
}

如果不加lock 和synchronized 怎么保证原子性

使用JUC包下的原子类解决原子性问题

public class Demo02 {
    private static AtomicInteger num = new AtomicInteger();//不保证原子性
    public static  void add(){//保证原子性
        //num++;
        num.getAndIncrement();//+1 底层CAS
    }
    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int i1 = 0; i1 < 1000; i1++) {
                    add();
                }
            }).start();
        }
        while(Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(num);
    }
}

禁止指令重排

内存屏障 CPU指令 作用:

1.保证特定操作的执行顺序

2.可以保证内存某个变量的可见性(volatile)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k28pMWaw-1615907409184)(image-20201206211954052.png)]

彻底玩转单例模式

饿汉式

package com.xin.single;
//饿汉式
public class Hungry {
    //浪费内存空间
    private byte [] data1 = new byte[1024*1024];
    private byte [] data2 = new byte[1024*1024];
    private byte [] data3 = new byte[1024*1024];
    private Hungry(){

    }
    private final static Hungry  hungry = new Hungry();
    public static Hungry getInstance(){
        return hungry;
    }
}

懒汉式 DCL懒汉式

//饿汉式
public class Hungry {
    //浪费内存空间
    private byte [] data1 = new byte[1024*1024];
    private byte [] data2 = new byte[1024*1024];
    private byte [] data3 = new byte[1024*1024];
    private Hungry(){

    }
    private final static Hungry  hungry = new Hungry();
    public static Hungry getInstance(){
        return hungry;
    }
}

使用放射破解单例模式

//使用反射破解单例模式
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    LazyMan lazyMan1 =  LazyMan.getInstance();
    Constructor<? extends LazyMan> declaredConstructor = lazyMan1.getClass().getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);//破坏私有性
    LazyMan lazyMan2 = declaredConstructor.newInstance();
    System.out.println(lazyMan1);
    System.out.println(lazyMan2);

}

反射解决方案

private LazyMan() {
    synchronized (LazyMan.class){
        if(lazyMan!=null){
            throw new RuntimeException("不要试图用反射破坏单例");
        }
    }

}

再次破解

//只使用放射来建立
LazyMan lazyMan2 = declaredConstructor.newInstance();
LazyMan lazyMan1 = declaredConstructor.newInstance();

再次解决

private static boolean  flag = true;
private LazyMan() {
        if(flag == true){
            flag=false;}
        else{
            throw new RuntimeException("不要试图用反射破坏单例");
    }
}

但是我们也可以通过反射获得flag的值…

最终

枚举使用单例

public enum EnumSingle {
     INSTANCE;
    private EnumSingle(){}
    public static EnumSingle getInstance(){
        return INSTANCE;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AOrHfrdS-1615907409195)(image-20201206230320387.png)]

深入理解CAS

什么是CAS 乐观锁 无锁算法

img

public class CASdemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
         //期望  目标值  如果期望值达到了就更新  CAS是CPU原语
        System.out.println(atomicInteger.compareAndSet(2020, 2021));

        atomicInteger.getAndIncrement();//cas
        System.out.println(atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
    }
}

Unsafe

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nw8GTyn5-1615907409198)(image-20201207145903128.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wB9gOMSA-1615907409200)(image-20201207150133501.png)]

使用自旋保证原子性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cZvnwquW-1615907409203)(image-20201207150237506.png)]

CAS:比较当前工作内存中的值和主存中的值,如果这个值是期望的,那么执行操作,如果不是就一直循环

缺点:

1.循环会耗时

2.一次只能保证一个共享变量的原子性

3.ABA问题

CAS ABA问题(狸猫换太子)

乐观锁

public class CASdemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
         //期望  目标值  如果期望值达到了就更新  CAS是CPU原语
        //捣乱的线程
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2021, 2020));
        System.out.println(atomicInteger.get());
        //期望的线程
        System.out.println(atomicInteger.compareAndSet(2020, 6666));
        System.out.println(atomicInteger.get());
    }
}

原子引用

AtomicStampedReference//解决ABA

解决ABA问题 对应的思想:乐观锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hZSJoAAW-1615907409206)(image-20201207153218758.png)]

public class CASdemo {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> asr = new AtomicStampedReference<Integer>(1, 1);

        //CAS比较并交换
        new Thread(() -> {
            int stamp = asr.getStamp();
            System.out.println("a-" + stamp);//版本号

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(asr.compareAndSet(1, 2, asr.getStamp(), asr.getStamp() + 1));
            System.out.println(asr.getReference());

        }, "a").start();


        new Thread(() -> {
            int stamp = asr.getStamp();
            System.out.println("b-" + stamp);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(asr.compareAndSet(1, 2, asr.getStamp(), asr.getStamp() + 1));
        }, "b").start();
        System.out.println(asr.getReference());
    }
}

各种锁的理解

悲观锁与乐观锁

乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。

悲观锁 (上锁)

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronizedReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁 (不上锁)

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

https://blog.csdn.net/qq_34337272/article/details/81072874

公平锁和不公平锁

公平锁: 非常公平 不可以插队

不公平锁 非常不公平 可以插队(默认是非公平 )

public ReentrantLock() {
    sync = new NonfairSync();
}
new ReentrantLock(true);//构造器改为公平

可重入锁

可重入锁的前提是当前持锁线程的可重入(获得锁的线程可以再次进入获得锁的同步代码块)

递归锁

https://blog.csdn.net/u012545728/article/details/80843595

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kaKnoz30-1615907409208)(image-20201207165119871.png)]

synchronized

public class test {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
        phone.call();
        },"a").start();

        new Thread(()->{
            phone.call();
        },"b").start();
    }
    }
    
class Phone {

    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"call");
        msg();
    }
    public synchronized void msg(){
        System.out.println(Thread.currentThread().getName()+"msg");
    }

}

lock

package com.xin.cas;

import com.xin.spincas.SpinLock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class test {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{

           phone.call();

        },"a").start();

        new Thread(()->{
            phone.call();
        },"b").start();
    }
    }

class Phone {
    ReentrantLock lock = new ReentrantLock();
    public  void call(){
        lock.lock();

        try {
            System.out.println(Thread.currentThread().getName()+"call");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            msg();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public  void msg(){
       lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"msg");
        } finally {
            lock.unlock();
        }
    }

}

自旋锁

spinlock

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iQxN9dkO-1615907409238)(image-20201207150237506.png)]

自定义一个锁

package com.xin.spincas;

import java.util.concurrent.atomic.AtomicReference;

public class SpinLock {
    static AtomicReference<Thread> atomicReference = new AtomicReference<>();
    public void mylock(){
        System.out.println(Thread.currentThread()+"mylock");

        Thread thread = Thread.currentThread();
        while (!atomicReference.compareAndSet(null,thread)){

        }
    }
    public void myunlock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread()+"myunlock");
        atomicReference.compareAndSet(thread, null);
    }

}

谁没有拿到锁谁就自旋 等解锁

package com.xin.cas;

import com.xin.spincas.SpinLock;

import java.util.concurrent.TimeUnit;

public class test {

    public static void main(String[] args) {
        SpinLock spinLock = new SpinLock();
        new Thread(()->{
            spinLock.mylock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                spinLock.myunlock();
            }

        },"a").start();

        new Thread(()->{
          spinLock.mylock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                spinLock.myunlock();
            }

        },"b").start();
    }

}

死锁排查

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xQVkRi3o-1615907409239)(image-20201207170755430.png)]

四大条件 互斥性 循环等待 不可剥夺 请求保持

import java.util.concurrent.TimeUnit;

public class DeadLockDemo implements Runnable {

    private String a;
    private String b;

    public DeadLockDemo(String a, String b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void run() {
        synchronized (a){
            System.out.println(Thread.currentThread().getName()+"get "+b);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (b){
                System.out.println(Thread.currentThread().getName()+"get "+a);
            }
        }
    }
}
class test1{
    public static void main(String[] args) {
        String a = "a";
        String b = "b";

        new Thread(new DeadLockDemo(a,b),"a").start();
        new Thread(new DeadLockDemo(b,a),"b").start();

    }
}

怎么排查

jps -l 查看所有的进程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qi2c91BE-1615907409243)(image-20201207201323721.png)]

jstack + 进程号 查看堆栈信息

thread = Thread.currentThread();
while (!atomicReference.compareAndSet(null,thread)){

    }
}
public void myunlock() {
    Thread thread = Thread.currentThread();
    System.out.println(Thread.currentThread()+"myunlock");
    atomicReference.compareAndSet(thread, null);
}

}


谁没有拿到锁谁就自旋  等解锁

```java
package com.xin.cas;

import com.xin.spincas.SpinLock;

import java.util.concurrent.TimeUnit;

public class test {

    public static void main(String[] args) {
        SpinLock spinLock = new SpinLock();
        new Thread(()->{
            spinLock.mylock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                spinLock.myunlock();
            }

        },"a").start();

        new Thread(()->{
          spinLock.mylock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                spinLock.myunlock();
            }

        },"b").start();
    }

}

死锁排查

[外链图片转存中…(img-xQVkRi3o-1615907409239)]

四大条件 互斥性 循环等待 不可剥夺 请求保持

import java.util.concurrent.TimeUnit;

public class DeadLockDemo implements Runnable {

    private String a;
    private String b;

    public DeadLockDemo(String a, String b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void run() {
        synchronized (a){
            System.out.println(Thread.currentThread().getName()+"get "+b);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (b){
                System.out.println(Thread.currentThread().getName()+"get "+a);
            }
        }
    }
}
class test1{
    public static void main(String[] args) {
        String a = "a";
        String b = "b";

        new Thread(new DeadLockDemo(a,b),"a").start();
        new Thread(new DeadLockDemo(b,a),"b").start();

    }
}

怎么排查

jps -l 查看所有的进程

[外链图片转存中…(img-qi2c91BE-1615907409243)]

jstack + 进程号 查看堆栈信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d532cZRt-1615907409244)(image-20201207201553531.png)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值