JCU并发编程笔记

发布时间:2024年01月10日

1、什么是JUC

java.util 工具包 包 分类

业务:普通线程代码 Thread

Runable 没有返回值、效率相比于Callable较低

2、线程和进程

进程:一个程序 QQ.exe mUSIC.exe 程序的集合

一个进程往往包含多个进程 至少包含一个!

Java默认有几个线程? 2个:?main GC

对于java而言 开辟线程的方式 Thread Runnable Callable

Java可以开启线程吗? 不可以

源码:

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

//本地的方法调用native 操作系统底层的C++? java 无法直接直接操作硬件

并发和并行 -- 并发编程

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

--?CPU一核? 快速交替

并行(多个人一起行走)

-- CPU多核 多个线程可以同时执行;

package com.xmx.demo1;

public class Test1 {
    public static void main(String[] args) {
        //获取cpu的核数
        //CPU密集型 IO密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

并发编程的本质:充分利用cpu的资源

线程有几个状态? 6个

* @see #getState
     */
    public enum State {
        //新生
        NEW,

        //运行
        RUNNABLE,

        //阻塞
        BLOCKED,

        //等待 死死的等
        WAITING,

        //超时等待
        TIMED_WAITING,

        //终止
        TERMINATED;
    }

wait和sleep的区别

1)、来自不同的类

wait -> Object

sleep -> Thread

工作中用休眠大多是 TimeUnit

2)、关于锁的释放

wait会释放锁? ?sleep睡觉了? 抱着锁 不会释放

3)、使用的范围不同

wait 必须在同步代码块中

sleep可以在任何地方睡

4)、是否需要捕获异常

wait不需要 (现在也需要?

sleep不需要

3、LOCK锁(重点)

Synchronized(传统)? 解决票超卖问题

package com.xmx.demo1;

//基本的买票例子
/**
 * 真正的多线程开发 公司中在用  降低耦合性
 * 线程就是一个单独的资源类 没有任何的附属操作
 */
public class SaleTicketDemo1 {
    public static void main(String[] args) {
        //并发 多个线程操作同一个资源类  将资源类丢入线程
        Ticket ticket = new Ticket();
        //@FunctionalInterface函数式接口
        new Thread(() -> {
            for (int i = 1; i < 40; i++) {
                ticket.sale();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 1; i < 40; i++) {
                ticket.sale();
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 1; i < 40; i++) {
                ticket.sale();
            }
        },"C").start();
    }
}


//资源类
class Ticket{
    //属性 方法
    private int number = 30;

    //买票的方式
    //synchronized 本质:队列 锁
    public synchronized void sale(){
        if(number > 0){
            System.out.println(Thread.currentThread().getName() + "卖出" + number-- + "张票,剩余:" + number);
        }
    }
}

LOCK接口 :创建锁 加锁 解锁

公平锁 非公平锁

public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

公平锁:十分公平? 先来后到

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

package com.xmx.demo1;

//基本的买票例子

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

/**
 * 真正的多线程开发 公司中在用  降低耦合性
 * 线程就是一个单独的资源类 没有任何的附属操作
 */
public class SaleTicketDemo2 {
    public static void main(String[] args) {
        //并发 多个线程操作同一个资源类  将资源类丢入线程
        Ticket2 ticket2 = new Ticket2();
        //@FunctionalInterface函数式接口
        new Thread(() -> { for (int i = 1; i < 40; i++) ticket2.sale(); },"A").start();
        new Thread(() -> { for (int i = 1; i < 40; i++) ticket2.sale(); },"B").start();
        new Thread(() -> { for (int i = 1; i < 40; i++) ticket2.sale(); },"C").start();


    }
}


//资源类
class Ticket2{
    //属性 方法
    private int number = 30;

    Lock lock = new ReentrantLock();

    //买票的方式
    //synchronized 本质:队列 锁
    public synchronized void sale(){
        lock.lock();//加锁

        try {
            //业务代码
            if(number > 0){
                System.out.println(Thread.currentThread().getName() + "卖出" + number-- + "张票,剩余:" + number);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁
        }
    }
}

Synchronized和Lock的区别

1)Synchronized 是内置的java关键字 ; 而Lock是一个java类

2)Synchronized 无法判断获取锁的状态; lock可以判断是否得到锁

3)Synchronized 会自动适放锁;? lock必须手动释放? 不是反 会导致死锁

4)Synchronized 线程 1(获得锁、阻塞)线程 2(一直等);lock就不一定会等下去

5)Synchronized 可重入锁 不可中断 非公平;Lock可重入锁 可以判断锁 非公平(可设置)

6)Synchronized 适合锁少量的代码同步问题;而Lock适合大量

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

4、生产者和消费者的关系

生产者和消费者问题Synchronized版

package com.xmx.ProCon;

/**
 * 线程之间的通信问题:生产者和消费者问题
 * 线程交替执行 A B 操作同一个变量 num = 0
 * A = num + 1
 * B = nmu - 1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

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

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

}

//判断等待 业务 通知
class Data{//数字 资源类
    private int number = 0;

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

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

}

问题存在? A B C D 线程呢?? 虚假唤醒

将if判断改为while判断

原因:wait()等待时进入多个线程?if只会判断一次?而while()循环会对每个线程唤醒重新判断

package com.xmx.ProCon;

/**
 * 线程之间的通信问题:生产者和消费者问题
 * 线程交替执行 A B 操作同一个变量 num = 0
 * A = num + 1
 * B = nmu - 1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

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

        },"B").start();

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

        },"C").start();

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

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

}

//判断等待 业务 通知
class Data{//数字 资源类
    private int number = 0;

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

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

}

生产者和消费者问题JUC

传统的和现在的

通过Lock可以找到? Condition? ?取代了notify和wait 替代同步监视器?; lock取代了synchronized

代码实现

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

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

        },"B").start();

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

        },"C").start();

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

        },"D").start();

Condition的优势? 精准通知 和 唤醒线程

代码

package com.xmx.ProCon;

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 < 10; i++) {
                data3.printA();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data3.printB();
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data3.printC();
            }
        },"C").start();

    }
}

class Data3{ //资源类 lock
    private Lock lock  = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    private int number = 1; //1A 2B 3C

    public void printA(){
        lock.lock();
        try {
            //业务代码  判断 执行 通知
            while(number != 1){
                //等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + " = A线程");
            //唤醒指定的人 B
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();
        try {
            //业务代码  判断 执行 通知
            while(number != 2){
                //等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + " = B线程");
            //唤醒指定的人 B
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();
        try {
            //业务代码  判断 执行 通知
            while(number != 3){
                //等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + " = C线程");
            //唤醒指定的人 B
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

输出结果

5、8锁现象

如何判断锁的是谁?? 永远的知道什么锁? 锁到底所得是谁?

对象、class

1)代码演示

package com.xmx.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁就是锁的八个问题
 * 1、标准情况下 : 先发短信 在打电话
 * 2、发短信方法中延迟4秒 : 结果依然是先发短信 在打电话
 */
public class Test1 {
    public static void main(String[] args) {
        phone phone = new phone();

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

        // 捕获休眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

    }
}

class phone{

    //synchronized 锁得对象是方法的调用者
    //两个方法用的是同一个锁  先得到的先执行

    public synchronized void sendMes(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发送短信..");
    }

    public synchronized void call(){
        System.out.println("拨打电话..");
    }

}

结果:

2)代码

package com.xmx.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 3、增加一个新的方法 在phone中  不加synchronized
 */
public class Test2 {
    public static void main(String[] args) {
        phone2 phone2 = new phone2();

        new Thread(() -> {
            phone2.sendMes();
        },"A").start();

        // 捕获休眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

    }
}

class phone2{

    //synchronized 锁得对象是方法的调用者
    //两个方法用的是同一个锁  先得到的先执行

    public synchronized void sendMes(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发送短信..");
    }

    public synchronized void call(){
        System.out.println("拨打电话..");
    }

    //同步方法  不受锁得影响
    public void hello(){
        System.out.println("hello!");
    }

}

结果:

3)代码 new两个对象

package com.xmx.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 3、增加一个新的方法 在phone中  不加synchronized
 */
public class Test2 {
    public static void main(String[] args) {
        phone2 phone1 = new phone2();
        phone2 phone2 = new phone2();

        new Thread(() -> {
            phone1.sendMes();
        },"A").start();

        // 捕获休眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

    }
}

class phone2{

    //synchronized 锁得对象是方法的调用者
    //两个方法用的是同一个锁  先得到的先执行

    public synchronized void sendMes(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发送短信..");
    }

    public synchronized void call(){
        System.out.println("拨打电话..");
    }

    //同步方法  不受锁得影响
    public void hello(){
        System.out.println("hello!");
    }

}

结果:

4)代码? 将前面的方法添加static

静态方法? 类一加载就有了? 锁的是Class 模板

package com.xmx.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 5、将两个方法改为静态 只有一个对象
 */
public class Test3 {
    public static void main(String[] args) {
        phone3 phone1 = new phone3();

        new Thread(() -> {
            phone1.sendMes();
        },"A").start();

        // 捕获休眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

    }
}

class phone3{

    public static synchronized void sendMes(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发送短信..");
    }

    public static synchronized void call(){
        System.out.println("拨打电话..");
    }

}

结果:

5)代码? 在方法添加了static的基础上 在new一个对象

两个对的class类模板只有一个 而static锁就是class模板

package com.xmx.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 5、将两个方法改为静态 只有一个对象
 */
public class Test3 {
    public static void main(String[] args) {
        phone3 phone1 = new phone3();
        phone3 phone2 = new phone3();

        new Thread(() -> {
            phone1.sendMes();
        },"A").start();

        // 捕获休眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

    }
}

class phone3{

    public static synchronized void sendMes(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发送短信..");
    }

    public static synchronized void call(){
        System.out.println("拨打电话..");
    }

}

结果:

6) 代码? 一个对象 一个静态同步方法(发短信方法? 延迟4s)? 一个普通同步方法(注意)

package com.xmx.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 5、将两个方法改为静态 只有一个对象
 */
public class Test4 {
    public static void main(String[] args) {
        phone4 phone1 = new phone4();

        new Thread(() -> {
            phone1.sendMes();
        },"A").start();

        // 捕获休眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

    }
}

class phone4{

    //静态的同步方法  锁的是class模板
    public static synchronized void sendMes(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发送短信..");
    }

    //普通的同步方法  锁的是调用者
    public synchronized void call(){
        System.out.println("拨打电话..");
    }

}

结果: 两个方法用的不是同一个锁? 因此后面的方法执行不会受前面的延时方法的影响

继续上面的补充:在new一个phone 执行打电话方法? 原先的还是执行发短信

输出依然不变!!!!!!!!!!!!

小结:

new this 具体的一个phone

static Class 唯一的模板

6、集合类不安全

List不安全

代码

package com.xmx.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;


//报错java.util.ConcurrentModificationException  并发修改异常
public class ListTest {
    public static void main(String[] args) {
//        List<String> list = Arrays.asList("1", "2", "3");
//        list.forEach(System.out ::println);



        /**
         * 解决方案
         * 1、List<String> list1 = new Vector<>();
         * 2、List<String> list2 = Collections.synchronizedList(new ArrayList<>());
         * 3、JUC 方案   import java.util.concurrent.CopyOnWriteArrayList;
         *    List<String> list3 = new CopyOnWriteArrayList<>();
         */
        //并发下 ArrayList是不安全的
//        List<String> list = new ArrayList<>();
//        List<String> list1 = new Vector<>();
//        List<String> list2 = Collections.synchronizedList(new ArrayList<>());
        //写入时复制 cow 计算机程序设计领域的一种优化策略
        //多个线程调用的时候 list  读取的时候是固定的  写入覆盖
        //在写入得时候避免覆盖  造成数据问题
        List<String> list3 = new CopyOnWriteArrayList<>();

        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                list3.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list3);
            },String.valueOf(i)).start();
        }

    }
}

读写分离

CopyOnWriteArrayList 是 Java 并发包(java.util.concurrent)中的一个类,它是一个线程安全的 ArrayList。其设计目标是支持高并发访问,同时提供相对较低的写操作开销。

CopyOnWriteArrayList 的主要优势在于其对并发操作的处理。当你修改列表时(例如添加、删除元素),它会首先创建一个新数组,然后在该新数组上进行修改,最后将内部引用指向新数组。这个过程称为写时复制(Copy-on-Write)。

这种机制保证了以下特点:

  1. 线程安全:在读取时不需要同步,因为读取操作只访问一个共享的数组。当进行修改操作时,它会锁定整个数组,从而避免了多线程环境下的并发问题。
  2. 低开销:读操作不会阻塞,且在写操作时,也只是在修改新数组的时候才进行同步。
  3. 数据持久性:由于写操作涉及新数组的创建,所以可以保证在多线程环境下对数据的修改是持久的。

但是,CopyOnWriteArrayList 也有其局限性:

  • 它不适用于大量写操作和少量读操作的场景,因为在这种情况下,写操作的高开销可能会成为性能瓶颈。
  • 由于写操作涉及数组的复制,所以对于大量写操作的场景,CopyOnWriteArrayList 的性能可能不如其他并发集合类。
  • 另外,由于写操作会创建新数组,所以 CopyOnWriteArrayList 的内存占用可能会随着时间的推移而增加。

总的来说,CopyOnWriteArrayList 的设计目标是提供一个线程安全的、读操作开销低、写操作开销高的数据结构,适用于读多写少的并发场景。

CopyOnWriteArrayList相比于Vector的优势在哪里?

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

CopyOnWriteArrayList和Vector都是线程安全的集合类,但它们实现线程安全的方式和性能特点有所不同。

  1. 线程安全:Vector是绝对线程安全的,它在每个增删改查方法上都加了synchronized关键字,保证了同步。而CopyOnWriteArrayList通过写时复制(Copy-On-Write)技术来保证线程安全,它只在增删改操作上加锁,读操作不加锁。
  2. 性能:由于Vector的每个方法都加了锁,因此在性能上会受到一定的影响。当有大量写操作时,CopyOnWriteArrayList的并发性能更好。而CopyOnWriteArrayList的读操作不会加锁,因此在读多写少的场景下,它的性能表现更优。
  3. 数据持久性:由于Vector的每个修改操作都需要同步,因此可以保证修改操作的持久性。而CopyOnWriteArrayList在修改数据时,会先复制一份新数组,再进行修改,因此它也具有数据持久性。
  4. 内存占用:由于CopyOnWriteArrayList使用了写时复制技术,因此在内存占用上可能比Vector更大,因为它需要额外的空间来存储复制的数据。

总的来说,CopyOnWriteArrayList适合读多写少的并发场景,而Vector更适合需要绝对线程安全的场景。

Set不安全

package com.xmx.unsafe;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * 同理出现 报错java.util.ConcurrentModificationException  并发修改异常
 * 解决方案
 * 1、Set<String> set = Collections.synchronizedSet(new HashSet<>());  工具类
 * 2、Set<String> set = new CopyOnWriteArraySet<>();  JUC写入时复制
 */
public class SetTest {
    public static void main(String[] args) {

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

        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

hashset底层是什么?底层就是hashmap

/**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }

//add? set本质就是map? key是不可以重复的? ?PRESENT是不变的 ??

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }



Map不安全

代码

package com.xmx.unsafe;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * java.util.ConcurrentModificationException
 */
public class MapTest {

    public static void main(String[] args) {
        //map是这样用的吗 ?  不是,工作中不用hashmap
        //默认等价于什么?  new HashMap<>(16,0.75);
//        Map<String, String> map = new HashMap<>();
        Map<String, String> map = new ConcurrentHashMap<>();
        //加载因子 初始容量

        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }

    }
}

ConcurrentHashMap的原理?

ConcurrentHashMap 是 Java 并发包中的一个类,它是一个线程安全的哈希表实现。其原理主要包括以下几个方面:

  1. 分段锁机制:ConcurrentHashMap 使用了分段锁机制,将整个哈希表分成多个段(Segment),每个段都是一个独立的锁。当进行修改操作时,只对需要修改的段加锁,其他段仍然可以并发访问。这种机制提高了并发性能,允许多个线程同时对不同的段进行读写操作。
  2. 哈希表和红黑树:ConcurrentHashMap 的存储结构主要由哈希表和红黑树组成。在哈希表阶段,元素按照哈希值存储在链表中。当链表长度超过一定阈值时,链表会转换为红黑树,以加快查找速度。红黑树是一种自平衡的二叉查找树,能够保证查找、插入和删除操作的平均时间复杂度为 O(log n)。
  3. 读操作和写操作的分离:在 ConcurrentHashMap 中,读操作和写操作是分离的。读操作不受锁的限制,可以并发进行。而写操作需要对相关段加锁,以避免冲突。这种分离方式进一步提高了 ConcurrentHashMap 的并发性能。
  4. 动态扩容:当 ConcurrentHashMap 中的元素数量超过当前段容量的两倍时,会触发扩容操作。扩容过程中,会将当前段的所有元素复制到新的数组中,并重新分布。这种动态扩容机制保证了 ConcurrentHashMap 的可伸缩性。

通过以上原理,ConcurrentHashMap 提供了高并发、高性能的线程安全哈希表实现。它在 Java 并发编程中广泛应用,适用于需要处理大量并发读写操作的场景。

7、callable

1)、可以有返回值

2)、可以抛出异常

3)、方法不同? call方法? runable为run方法

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

代码

package com.xmx.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        new Thread(new Runnable()).start();
//        new Thread(new FutureTask<V>()).start();
//        new Thread(new FutureTask<V>(Callable)).start();
        new Thread().start();//如何启动Callable

        MyThread thread = new MyThread();
        FutureTask futureTask = new FutureTask(thread);//适配器

        new Thread(futureTask,"A").start();

        String o = (String)futureTask.get();//获取callable的返回结果
        System.out.println(o);


    }
}

class MyThread implements Callable<String> {

    @Override
    public String call() {
        System.out.println("call()被执行..." );
        return "123456";

    }
}

上面的get方法可能会产生阻塞? ?将其放到最后一行? 或者使用异步处理

new Thread(futureTask,"A").start();

new Thread(futureTask,"B").start();

同时执行只会输出一次call? ??

原因:? 由于JVM第二次再次调用FutureTask对象所持有的线程

FutureTask的run方法:if (state != NEW || ...) return ;是没有执行

8、常用的辅助类

1、CountDownLatch? 减法计数器

  • 允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。

代码

package com.xmx.add;

import java.util.concurrent.CountDownLatch;

//计数器
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //倒计时 总计数6  必须要执行的任务才使用
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                //数量减一
                System.out.println(Thread.currentThread().getName() + "线程结束..");
                countDownLatch.countDown();
            },String.valueOf(i)).start();

        }

        countDownLatch.await(); //等待计数器归零 继续向下执行

        System.out.println("Close Door!");
    }
}

down方法:? 减一操作

awiat方法 :??down方法减为0该方法被唤醒 !!!时继续向下执行

2、CyclicBarrier? 加法计数器

代码

package com.xmx.add;

import org.omg.PortableServer.THREAD_POLICY_ID;

import javax.print.AttributeException;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {

        //召唤龙珠的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> {
            System.out.println("召唤神龙成功!");
        });

        for (int i = 1; i <=7 ; i++) {
            final int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集" + temp + "颗龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

3、Semaphore? ?信号量

代码

package com.xmx.add;

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 = 1; i <=6 ; i++) {
            new Thread(() -> {
                //acquire() 得到
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到车位..");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "离开车位..");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

acquire获得? 已经满了等待被释放

realease 释放 将当前的信号释放量+1 然后唤醒等待的线程

9、读写锁

补充

在Java中,java.util.concurrent.locks包提供了几种不同类型的锁,其中最常用的两种是公平锁和非公平锁。

  1. 公平锁
    公平锁保证线程按照它们请求锁的顺序获得锁。如果线程A首先请求锁,然后线程B也请求锁,那么线程A将始终首先获得锁。这种锁通常用于避免忙等待,忙等待是指线程不断地检查锁是否可用,而没有做其他有用的工作。

Java中的ReentrantLock类支持公平锁。可以通过构造函数来指定是否为公平锁,例如:

 

java复制代码

ReentrantLock lock = new ReentrantLock(true); // 公平锁
  1. 非公平锁
    非公平锁不保证线程按照它们请求锁的顺序获得锁。线程可能会在等待了很长时间之后,突然一个线程释放了锁,而这个锁被一个刚刚请求锁的线程获得。这种锁可能会导致忙等待,因为线程可能会不断地检查锁是否可用。

Java中的ReentrantLock类也支持非公平锁。与公平锁类似,可以通过构造函数来指定是否为非公平锁,例如:

 

java复制代码

ReentrantLock lock = new ReentrantLock(false); // 非公平锁

选择使用公平锁还是非公平锁取决于具体的应用场景。在某些情况下,公平锁可能更合适,例如当线程需要按照某种顺序执行时。在其他情况下,非公平锁可能更合适,例如当需要快速地获取和释放锁时。

我懂了,写锁会禁止其他写锁操作,也会禁止其他读锁,而读锁不会禁止其他读锁(这是和写锁有区别的,也是和sync的区别),同时禁止其他写锁

代码

package com.xmx.rw;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 独占锁就是写锁   一次只能一个线程占有
 * 共享锁就是读锁   多个线程可以同时占有
 * ReadWriteLock
 * 读-读 可以共存
 * 读-写 不能共存
 * 写-写 不能共存
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        //只做写入的操作
        for (int i = 1; i <= 5 ; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.put(temp + "",temp + "");
            },String.valueOf(i)).start();
        }

        //只做读取的操作
        for (int i = 1; i <= 5 ; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.get(temp + "");
            },String.valueOf(i)).start();
        }

    }
}

/**
 * 自定义缓存---加了锁的
 */
class MyCache{
    private volatile Map<Object, Object> objectObjectHashMap = new HashMap<>();
    //读写锁  更加细粒度的控制
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();


    //存,写  希望只有一个线程写
    public void put(String key,Object value){
        //加锁
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入ing");
            objectObjectHashMap.put(key,value);
            System.out.println(Thread.currentThread().getName() + "写入OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //解锁
            readWriteLock.writeLock().unlock();
        }


    }

    //取,读   所与人都可以读
    public void get(String key){
        //加锁
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取ing");
            objectObjectHashMap.get(key);
            System.out.println(Thread.currentThread().getName() + "读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //解锁
            readWriteLock.readLock().unlock();
        }

    }
}

///**
// * 自定义缓存 -- 不含锁
// */
//class MyCache{
//    private volatile Map<Object, Object> objectObjectHashMap = new HashMap<>();
//
//    //存,写
//    public void put(String key,Object value){
//        System.out.println(Thread.currentThread().getName() + "写入ing");
//        objectObjectHashMap.put(key,value);
//        System.out.println(Thread.currentThread().getName() + "写入OK");
//
//    }
//
//    //取,读
//    public void get(String key){
//        System.out.println(Thread.currentThread().getName() + "读取ing");
//        objectObjectHashMap.get(key);
//        System.out.println(Thread.currentThread().getName() + "读取OK");
//
//    }
//}

10、阻塞队列

FIFO先进先出,阻塞,队列写入:如果队列满了,就必须阻塞等待,取:如果阻塞是空的,就必须阻塞等待生成(不得不阻塞)

阻塞队列:

BlockingQueue?不是新的东西

阻塞队列的使用场景:多线程并发处理? ???线程池

学会使用队列

添加 移出

四组API

1、抛出异常

/**
     * 抛出异常
     */
    public static void test1(){
        //队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));

        //抛出异常: java.lang.IllegalStateException: Queue full
        //System.out.println(blockingQueue.add("d"));

        System.out.println("===================================");

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

        //抛出异常: java.util.NoSuchElementException
        System.out.println(blockingQueue.remove());
    }

查看队首元素

2、不会抛出异常? 有返回值

检测队首元素

3、阻塞等待

4、超时等待

SynchronousQueue? 同步队列

没有容量?

进去一个元素 必须等取出来之后? 才能继续放下一个元素

10、线程池

文章来源:https://blog.csdn.net/m0_73865631/article/details/135416210
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。