说一说Java中的JUC

JUC

1.什么是JUC

2.进程和线程

进程 : cpu资源分配的最小单位

线程 : cpu调度和执行的最小单位

并发是指多个任务在同一个时间段内交替执行,通过时间片轮转等方式实现任务间的切换。换句话说,并发是指多个任务能够同时存在,但不一定同时执行。这种情况下,任务之间可能会相互干扰,需要使用同步机制来协调它们的执行次序。

并行则是指多个任务在同一时刻同时执行,每个任务都分配到独立的处理器核心或计算单元上进行并行执行。并行可以大大提高程序的执行速度,特别是对于需要大量计算的任务而言。

简而言之,可以将并发看作是任务的概念,而并行则是执行的方式。并发强调任务之间的交替执行,而并行则强调任务的同时执行。

在实际应用中,可以通过多线程、多进程等方式实现并发和并行。例如,在多核处理器上运行多个线程或进程,每个线程或进程负责执行一个任务,即可实现并行和并发。在分布式系统中,可以将任务分配给多台计算机进行并行处理。

综上所述,并发和并行是两个重要的计算机概念,理解它们的区别有助于优化和提高程序的性能。

并发 :多个任务在同一时刻执行

并行 : 多个任务在同一时间段内执行

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

Java中线程有几种状态?

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;
}

wait 和 notify 的区别

  1. 所在包不同,分别为 object和thread
  2. wait需要在同步代码块,会自动释放当前锁

3.Lock锁

传统Synchronized

package com.hkd.rjxy;

public class demo02 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        //多线程操作
        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"A").start();

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

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

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

    //卖票的方式
    //synchronized 本质就是排队
    public synchronized void sale() {
        if (number > 0){
            System.out.println(Thread.currentThread().getName() + "卖出了 " + number-- + "票,剩余"+number);

        }
    }
}

Lock

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

默认是非公平锁,参数为true是公平锁

package com.hkd.rjxy;

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

public class demo03 {
    public static void main(String[] args) {
        Ticket2 ticket = new Ticket2();
        //多线程操作
        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"A").start();

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

        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"C").start();
    }
}
class Ticket2{
    //属性,方法
    private int number = 50;
    Lock lock = new ReentrantLock();
    //卖票的方式
    //synchronized 本质就是排队
    public 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是一个接口
  2. synchronized无法判断锁的状态,Lock可以判断是否获取到了锁
  3. synchronized会自动释放锁资源,而Lock锁需要手动unlock() 否则会死锁
  4. synchronized会一直等待锁释放,Lock就不一定会等待下去
  5. synchronized适合少量代码,Lock适合大量

4.生产者和消费者问题

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

wait的调用周围的while循环检查正在等待的条件
同时超过两个线程记得notifyAll,否则会死锁!

JUC版生产者和消费者

public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();

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

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

        new Thread(() -> {
            for (int i = 0; i < 100; i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"C").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"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);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0){
                //等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + " : "+number);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
}

为什么有synchronized了还要有Lock接口

Condition的优势 : 精准的通知和唤醒线程!!!
指定线程打印数字
package com.hkd.rjxy.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 data = new Data3();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.printA();
            }
        },"A").start();

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

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.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;
    //我们要完成的任务 : 1A 2B 3C

    public void printA(){
        lock.lock();
        try {
            //写业务代码
            while (number != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + " : " + number++);
            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() + " : " + number++);
            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() + " : " + number);
            number = 1;
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

condition一个重要作用就是生产线

下单 -> 支付 -> 交易 -> 物流…

5. 8锁现象

public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(phone::sendMessage).start();
        new Thread(phone::call).start();
    }
}
class Phone{
    public synchronized void sendMessage(){
        System.out.println("message");
    }

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个例子永远是message在先,这是因为synchronized关键字修饰方法时,锁的对象是方法的调用者 -> phone

两个方法用的是同一个对象的锁,谁先拿到谁先执行

import java.util.concurrent.TimeUnit;

public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(phone::sendMessage).start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(phone::hello).start();
    }
}
class Phone{
    public synchronized void sendMessage(){
        try {
            TimeUnit.SECONDS.sleep(4);//延迟时间很长
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("message");
    }

    public synchronized void call() {
        System.out.println("call");
    }
    
    //这里没有锁,肯定hello先执行
    public void hello() {
        System.out.println("hello");
    }
}

如果方法前面有static修饰,那么锁的是class对象 Phone.class
    

6. 集合类

多线程下不安全集合类报错 : Java.util.ConcurrentModificationException 并发修改异常

并发环境下ArrayList是不安全的,怎么解决?

List list = new Vector();

List<String> list = Collections.synchronizedList(new ArrayList<>());

List<String> list = new CopyOnWriteArrayList<>();//写入时复制 多个线程调用的 

学习CopyOnWriteArrayList

源码描述:

ArrayList 的线程安全变体,其中所有可变操作(addset 等)都是通过制作底层数组的新副本来实现的。

这通常成本太高,但当遍历操作的数量远远超过突变时,它可能more 比替代方法更有效,并且在您不能或不想同步遍历但需要排除并发线程之间的干扰时很有用。 “快照”样式的迭代器方法在创建迭代器时使用对数组状态的引用。这个数组在迭代器的生命周期内永远不会改变,所以干扰是不可能的,迭代器保证不会抛出 ConcurrentModificationException 。迭代器不会反映自迭代器创建以来对列表的添加、删除或更改。不支持对迭代器本身(removesetadd)进行元素更改操作。这些方法抛出 UnsupportedOperationException

允许所有元素,包括 null

内存一致性影响:与其他并发集合一样,在将对象放入 CopyOnWriteArrayList 发生在之前 之前线程中的操作是在另一个线程中从 CopyOnWriteArrayList 访问或删除该元素之后的操作。

此类是 Java 集合框架 的成员。

总结,写操作上锁,先复制一份副本,写好后插入,并将容器指向新的容器,释放锁,用的Lock锁,没用synchronized,效率高

集合Set同理,HashSet的底层是什么?

public HashSet() {
        map = new HashMap<>();
    }
//add的本质就是map.put,所以key不能重复
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

ConcurrentHashMap

7.Callable

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package com.hkd.rjxy.unsafe;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyCallable{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallableDemo callable = new MyCallableDemo();
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        new Thread(futureTask,"A").start();
    }
}
class MyCallableDemo implements Callable<Integer>{
    @Override
    public Integer call() {
        System.out.println("call");
        return 11;
    }
}
s1mple

8.高频辅助类

8.1CountDownLatch 减法计数器

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

CountDownLatch 使用给定的 count 进行初始化。 await 方法会阻塞,直到当前计数由于调用 countDown() 方法而变为零,之后所有等待的线程都会被释放,并且任何后续的 await 调用都会立即返回。这是一种一次性现象——无法重置计数。如果您需要重置计数的版本,请考虑使用 CyclicBarrier

CountDownLatch 是一种多功能同步工具,可用于多种用途。初始化为 1 的 CountDownLatch 用作简单的开/关锁存器或门:所有调用 await 的线程都在门处等待,直到它被调用 countDown() 的线程打开。初始化为 NCountDownLatch 可用于使一个线程等待,直到 N 个线程完成某个操作,或者某个操作已完成 N 次。

CountDownLatch 的一个有用属性是它不需要调用 countDown 的线程在继续之前等待计数达到零,它只是阻止任何线程继续通过 await 直到所有线程都可以通过。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);//总数为6
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " GO");
                countDownLatch.countDown();//计数器减一
            },String.valueOf(i)).start();
        }
        countDownLatch.await();//等待计数器归零
        System.out.println("it is over");
    }
}
0 GO
5 GO
3 GO
4 GO
1 GO
2 GO
it is over
减法计数器

8.2CyclicBarrier 加法计数器

允许一组线程全部等待彼此到达公共屏障点的同步辅助工具。 CyclicBarriers 在涉及必须偶尔相互等待的固定大小线程组的程序中很有用。屏障被称为cyclic,因为它可以在等待线程被释放后重新使用。

CyclicBarrier 支持可选的 Runnable 命令,该命令在每个障碍点运行一次,在派对中的最后一个线程到达之后,但在释放任何线程之前。这个 barrier action 对于在任何一方继续之前更新共享状态很有用。

CyclicBarrier cyclicBarrier = new CyclicBarrier(8,() -> {
            System.out.println("计数器到达8");
        });
        for (int i = 0; i < 7; i++) {
             final int temp = i;
             new Thread(() -> {
                 System.out.println(Thread.currentThread().getName() + "收集" +
                         temp + "个");
                 try {
                     cyclicBarrier.await();//等待和计数
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 } catch (BrokenBarrierException e) {
                     throw new RuntimeException(e);
                 }
             }).start();
        }
//如果计数器到达不了传入的参数8,程序就会死在这
Thread-0收集0Thread-5收集5Thread-3收集3Thread-1收集1Thread-4收集4Thread-2收集2Thread-6收集6//如果传入参数调整为7,程序就会输出
Thread-3收集3Thread-5收集5Thread-0收集0Thread-6收集6Thread-1收集1Thread-2收集2Thread-4收集4个
计数器到达7

8.3Semaphore

计数信号量。从概念上讲,信号量维护一组许可。如果有必要,每个 acquire() 都会阻塞,直到获得许可,然后再获取许可。每个 release() 添加一个许可,可能会释放一个阻塞的获取者。但是,没有使用实际的许可对象; Semaphore 只是保持可用数量的计数并相应地采取行动。

信号量通常用于限制可以访问某些(物理或逻辑)资源的线程数

Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"拿到资源");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"释放资源");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }finally{
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
2拿到资源
1拿到资源
3拿到资源
3释放资源
1释放资源
2释放资源
4拿到资源
6拿到资源
5拿到资源
5释放资源
6释放资源
4释放资源

多个共享资源的互斥使用,并发限流

9.读写锁

public interface ReadWriteLock

ReadWriteLock 维护一对关联的 locks ,一个用于只读操作,一个用于写入。 读锁 可以由多个读取器线程同时持有,只要没有写入器。 写锁 是排他性的。

所有 ReadWriteLock 实现必须保证 writeLock 操作的内存同步效果(如 Lock 接口中所指定)也适用于关联的 readLock 。也就是说,成功获取读锁的线程将看到在先前释放写锁时所做的所有更新。

与互斥锁相比,读写锁在访问共享数据时允许更高级别的并发。它利用了这样一个事实,即虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程都可以并发读取数据(因此有 reader 线程)。从理论上讲,使用读写锁所允许的并发性增加会导致使用互斥锁的性能提升。实际上,这种并发性的增加只会在多处理器上完全实现,并且只有在共享数据的访问模式合适的情况下才能实现。

读写锁是否会比使用互斥锁提高性能取决于与修改数据相比读取数据的频率、读写操作的持续时间以及对数据的争用——即即同时尝试读取或写入数据的线程数。例如,一个集合最初填充了数据,此后很少被修改,同时被频繁搜索(例如某种目录)是使用读写锁的理想候选者。但是,如果更新变得频繁,那么数据大部分时间都被独占锁定,并且并发性几乎没有增加。此外,如果读取操作太短,读写锁实现的开销(本质上比互斥锁更复杂)会主导执行成本,特别是因为许多读写锁实现仍然通过一个序列化所有线程一小段代码。最终,只有分析和测量才能确定读写锁的使用是否适合您的应用程序。

尽管读写锁的基本操作很简单,但实现必须做出许多策略决策,这可能会影响给定应用程序中读写锁的有效性。这些策略的示例包括:

  • 决定授予读锁还是授予写锁,当读写者都在等待时,在写者释放写锁的时刻。作者偏好很常见,因为写作预计会很短且不频繁。读者偏好不太常见,因为如果读者像预期的那样频繁和长寿,它会导致写入的长时间延迟。公平或“按顺序”实现也是可能的。
  • 确定在读者处于活动状态且写入者正在等待时请求读锁的读者是否被授予读锁。对读者的偏好可以无限期地延迟作者,而对作者的偏好可以降低并发的可能性。
  • 判断锁是否可重入:拥有写锁的线程能否重新获取?是否可以在持有写锁的同时获取读锁?读锁本身是可重入的吗?
  • 写锁能否降级为读锁而不允许干预写入器?读锁能否升级为写锁,优先于其他等待的读者或写者?

在评估给定实现对您的应用程序的适用性时,您应该考虑所有这些事情。

写线程互斥,读线程不互斥

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

10.阻塞队列BlockingQueue

添加操作:

add()会抛出异常

offer()不会抛出异常,返回false

弹出操作:

remove()会抛出异常

poll()不会抛出异常,返回null

查看队首:

element()抛出异常

peek()不会抛出异常,返回null

等待阻塞操作:

put()队列没位置了一直阻塞

take()队列空了一直阻塞

blockingQueue.offer("d",2, TimeUnit.SECONDS);//阻塞的话,等待2s,超时退出

同步队列SynchronousQueue

SynchronousQueue(同步队列)是Java并发包中的一种特殊的队列实现。它是一个没有存储元素的阻塞队列,主要用于线程之间的数据交换。

在SynchronousQueue中,每个插入操作必须等待另一个线程的相应删除操作,反之亦然。换句话说,当一个线程尝试向SynchronousQueue中插入元素时,它将被阻塞,直到另一个线程从队列中取走这个元素;同样,当一个线程尝试取出元素时,它也会被阻塞,直到另一个线程向队列中插入一个元素。

由于这种特性,SynchronousQueue被用作线程之间的一种双向数据交换的工具。它可以用于实现生产者-消费者模式,让生产者线程和消费者线程能够同步地进行数据交换,而且不需要使用显式的锁或条件变量。

总的来说,SynchronousQueue是一种非常特殊且有用的并发工具,可以帮助开发者实现高效的线程之间通信和协作。
    BlockingQueue<String> synchronousQueue = new SynchronousQueue(); // 同步队列
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName() + "put 1");
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "put 2");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName() + "put 3");
                synchronousQueue.put("3");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"T1").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + synchronousQueue.take());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"T2").start();

T1put 1
T21
T1put 2
T22
T1put 3
T23

11.线程池(重点)

Java中的线程池是一种用于管理和重复利用线程的机制,它可以有效地控制并发执行的线程数量,从而提高程序的性能和响应速度。

线程池由一个线程队列和一个任务队列组成。当一个任务到来时,线程池会从线程队列中取出一个空闲的线程来执行任务,如果没有空闲线程,任务将被放入任务队列中等待。线程池会根据设定的参数来动态调整线程数,以适应当前的工作负载,避免创建过多的线程消耗过多的系统资源。

通过使用线程池,可以减少线程创建和销毁的开销,提高系统的稳定性和性能。此外,线程池还提供了一些额外的功能,比如定时执行任务、对执行任务进行统计和监控等,使得多线程编程变得更加方便和灵活。

在Java中,线程池通过Executor框架提供支持,常用的线程池实现类包括ThreadPoolExecutorScheduledThreadPoolExecutor,开发者可以根据自己的需求选择合适的线程池类型和参数配置来优化程序的性能。

线程池在多线程编程中具有许多好处,主要包括以下几点:

  1. 降低资源消耗:线程池可以重复利用已创建的线程,避免频繁创建和销毁线程所带来的性能开销,从而减少了系统资源的消耗。
  2. 提高响应速度:线程池能够快速分配线程来处理任务,无需等待新线程的创建,因此可以更快地响应用户请求,提高了系统的响应速度。
  3. 控制并发度:通过限制线程池中的线程数量,可以有效控制并发执行的任务数量,避免因线程过多而导致系统负载过重,提高了系统的稳定性。
  4. 灵活管理:线程池提供了丰富的参数配置和管理功能,可以动态调整线程数、统计执行情况、监控线程状态等,使得线程的管理变得更加灵活和方便。
  5. 提供任务队列:线程池通常包含一个任务队列,可以暂存未执行的任务,当线程池中的线程都在忙碌时,新的任务可以排队等待执行,避免任务丢失或被拒绝执行。
  6. 统一管理:通过线程池,可以统一管理和监控系统中的线程,使得多线程编程变得更加规范和可控,降低了出错的可能性。

综上所述,线程池能够有效地提高系统的性能、稳定性和可维护性,是多线程编程中不可或缺的重要工具。

//三个方法
ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程
        ExecutorService executorService = Executors.newFixedThreadPool(5);// 创建一个固定的线程池的大小
        ExecutorService executorService1 = Executors.newCachedThreadPool();// 可伸缩的
//本质 new ThreadPoolExecutor()

//7个参数
public ThreadPoolExecutor(int corePoolSize,//核心线程数
                              int maximumPoolSize,//最大线程数
                              long keepAliveTime,//存活时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//阻塞队列
                              ThreadFactory threadFactory,//线程工场,创造线程的,一般不用动
                              RejectedExecutionHandler handler//拒绝策略)

问题,最大线程数如何设置好

cpu密集型和IO密集型

几核CPU定义为几,可以保证CPU效率高

判断程序中有多少个线程十分消耗IO,设为两倍

12.四大函数式接口(imp)

//函数型接口,有一个输入参数,有一个输出
        Function function = new Function<String,String>() {
            @Override
            public String apply(String o) {
                return o;
            }
        };

        System.out.println(function.apply("asd"));

        Function<String,String> f = (str) -> {return str;};
        System.out.println(f.apply("aa"));
//断定型接口,有一个输入参数,返回值只能是布尔值
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.isEmpty();
            }
        };

        System.out.println(predicate.test(""));
//消费型接口,只有输入,没有返回值
        Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        consumer.accept("asd");
//供给型接口
        Supplier<String> stringSupplier = new Supplier<String>() {
            @Override
            public String get() {
                return "asd";
            }
        };

        Supplier<Integer> integerSupplier = () -> {return 1;};

        System.out.println(stringSupplier.get());
        System.out.println(integerSupplier.get());

13.stream流式计算

User u1 = new User(1,"a",21);
        User u2 = new User(2,"b",22);
        User u3 = new User(3,"c",23);
        User u4 = new User(4,"d",24);
        User u5 = new User(5,"e",25);
        User u6 = new User(6,"f",26);
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5,u6);
        //id偶数,年龄大于23,用户名转为大写,字母倒着排序,只输出一个用户

        //先打印id为偶数
        list.stream().filter(user->{return user.getId() %2 == 0;}).filter(user->{return user.getAge() > 23;})
                .map(user -> {return user.getName().toUpperCase();})
                .sorted((uu1,uu2) -> {return uu2.compareTo(uu1);})
                .limit(2)
                .forEach(System.out::println);

14.ForkJoin

15.Future

16.JMM

Java内存模型,不存在,是一个约定

  1. 线程解锁前,必须把共享变量立刻及时刷回主存
  2. 线程加锁前,必须读取主存中的最新值到工作内存中
  3. 加锁和解锁必须是同一把锁

线程B修改了flag=false,线程A不能及时拿到最新值

内存交互操作有8种:lock unlock read load use assign store write

请你谈谈对volatile的理解

17.volatile

volatile是关键字,可以理解为轻量级的同步机制

  1. 可见性
  2. 不保证原子性
  3. 禁止指令重排序
private static int num = 0;
    public static void main(String[] args) {
        //main
        


        new Thread(()->{ // 线程1
            while(num == 0){
                //一直循环
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        num = 1;
        System.out.println(num);
    }
// num调整为1,线程1应该停止才对,但没有停止

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

private volatile static int num = 0; // 加入关键字volatile就会停止,保证可见性验证
    public static void main(String[] args) {
        //main

        new Thread(()->{ // 线程1
            while(num == 0){
                //一直循环
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        num = 1;
        System.out.println(num);
    }

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

验证volatile不保证原子性(不可分割:

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

// 验证volatile不保证原子性
    private static int num = 0;

    public static void add() {
        num++;
    }

    public static void main(String[] args) {
        // 理论上num应为20000
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2){
            //main gc
            Thread.yield();
        }

        //循环结束就跑完了
        System.out.println(num);
    }

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

不加Lock和synchronized怎么保证原子性

num++不是原子性操作

可以使用原子类

private volatile static AtomicInteger num = new AtomicInteger();// 原子类

    public static void add() {
        num.getAndIncrement();// + 1方法,CAS
    }

    public static void main(String[] args) {
        // 理论上num应为20000
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2){
            //main gc
            Thread.yield();
        }

        //循环结束就跑完了
        System.out.println(num);
    }

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

没加锁得到了正确结果

Atmoic类底层直接和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在

什么是指令重排序: 你写的程序,计算机并不是按照你写的那样去执行的

什么情况发生重排序 : 原代码 -> 编译器优化重排 -> 指令并行也可能重排 -> 内存系统也会重排 -> 执行

18.四个单例模式实现

19.深入理解CAS

  1. 什么是CAS?它有什么作用?
  • 回答: CAS是一种用于实现多线程同步的技术,通过比较内存位置的值与预期数值,并在相等时将该位置的值更新为新值,整个过程不用加锁,避免了锁机制带来的性能损耗。CAS通常用来实现无锁算法,并提高并发性能
  1. Java中如何使用CAS?
  • 回答:在Java中可以通过java.util.concurrent.atomic包提供的原子类来使用,比如AtomicInteger,AtomicLong等。这些原子类提供了CAS操作的接口,可以在多线程环境下对共享变量进行原子操作

    3.CAS操作有那些问题?

  • 回答:ABA问题,假设内存中的值原本是A,线程1读取到并进行操作,线程2修改为B,并在某一时刻线程1又将其改回A。此时线程1在进行CAS操作时,由于内存中确实是A,所以CAS操作成功,但实际 上这个位置的值已经发生了变化。此外CAS需要循环测试的,当竞争激烈时,会导致性能开销增加

  1. CAS与传统锁机制相比有什么优势?
  • 回答:CAS与传统锁机制相比开销更小,因为不需要线程阻塞。而且在对共享变量进行简单原子操作时,CAS表现得性能更好
  1. CAS的适用场景?
  • 回答资源竞争较少的情况:在资源竞争较少(线程冲突较轻)的情况下,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态到内核态间的切换操作会额外浪费消耗cpu资源。而CAS基于操作借助C来调用CPU底层指令实现的,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

20.ABA

在Java开发中,ABA问题通常与并发编程和CAS(Compare and Swap)操作相关。ABA问题指的是一个共享变量的值在操作过程中经历了从A到B再到A的变化,可能导致意外结果的发生。这个问题在多线程环境下尤为常见。以下是一个关于ABA问题的面试题:

题目

请解释什么是ABA问题,在Java并发编程中如何解决这个问题?

答案
  1. ABA问题:ABA问题是指一个共享变量的值在操作过程中经历了从A到B再到A的变化。这种情况在并发编程中会导致意外的结果,尤其是在使用CAS(Compare and Swap)操作时更容易引发ABA问题。
  2. 解决方法:在Java中,可以通过引入版本号或者使用AtomicStampedReference类来解决ABA问题。具体而言,AtomicStampedReference类可以通过引入版本号来检测在CAS操作过程中是否发生了值的变化,从而避免了ABA问题的发生。

示例代码如下:

javaCopy Codeimport java.util.concurrent.atomic.AtomicStampedReference;

public class ABADemo {
    private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(1, 0);

    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = atomicStampedRef.getStamp();
            System.out.println("Thread-1: stamp = " + stamp); // 初始版本号

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            atomicStampedRef.compareAndSet(1, 2, stamp, stamp + 1); // A->B
            System.out.println("Thread-1: A->B");
        }).start();

        new Thread(() -> {
            int stamp = atomicStampedRef.getStamp();
            System.out.println("Thread-2: stamp = " + stamp); // 初始版本号

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            boolean c3 = atomicStampedRef.compareAndSet(2, 1, stamp, stamp + 1); // B->A
            System.out.println("Thread-2: B->A: " + c3);
        }).start();
    }
}

这样,通过AtomicStampedReference类的帮助,我们可以避免ABA问题的发生,确保在CAS操作中不会出现意外的结果。

这样的回答能够展现出对于ABA问题的理解和针对该问题的解决方案在Java中的实际应用能力。

​ 2023/11/12

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/211463.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot-Vue项目初始搭建

SpringBoot-Vue项目初始搭建 1、项目搭建 前提&#xff1a;配置过nodejs环境&#xff0c;安装了vuecli&#xff08;如果未配置&#xff0c;可以参照此教程&#xff1a;https://www.bilibili.com/video/BV18E411a7mC/ p12&#xff09; 新建文件夹(最好不要有中文) 打开cmd …

Azure Machine Learning - 在 Azure 门户中创建AI搜索技能组

你将了解 Azure AI 搜索中的技能组如何通过添加光学字符识别 (OCR)、图像分析、语言检测、文本翻译和实体识别&#xff0c;在搜索索引中创建可搜索文本的内容。 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff…

使用dlib简单进行人脸特征点检测和换脸

0.出于好奇,思考中想要把2维图像怎么转化为3维图像,我在考虑怎么把草莓二维转为三维图像,这个领域其实早有人研究了,术语叫三维重建,使用matlab可以实现三维坐标标点来表达,后来我发现一个很好玩的事情就是我看到直播有人卖替换人脸和换装的程序,我就想试试怎么实现换脸… //ma…

Springboot3+vue3从0到1开发实战项目(二)

前面完成了注册功能这次就来写登录功能&#xff0c; 还是按照这个方式来 明确需求&#xff1a; 登录接口 前置工作 &#xff1a; 想象一下登录界面&#xff08;随便在百度上找一张&#xff09; 看前端的能力咋样了&#xff0c; 现在我们不管后端看要什么参数就好 阅读接口文档…

最小生成树(Minimum Spanning Tree)及生成MST的几种方法

最小生成树 (Minimum Spanning Tree) 最小生成树是图论领域的一个基本概念&#xff0c;适用于加权连通图&#xff0c;其中包括若干顶点&#xff08;节点&#xff09;以及连接这些顶点的边&#xff08;边可以有权重&#xff09;。在一个加权连通图中&#xff0c;生成树&#xf…

MSTP实验

目录 一、实验拓扑 二、实验要求 三、实验步骤 1、创建vlan 2、创建端口组&#xff0c;放通vlan 3、配置MSTP 4、配置主备奋根 一、实验拓扑 二、实验要求 1、所有交换机上创建vlan10&#xff0c;vlan20&#xff0c;vlan30和vlan40 2、所有交换机之间的端口配置为Trunk…

wordpress忘记密码怎么办?

有的时候&#xff0c;我们会忘记网站的密码&#xff0c;所以网站的密码要记住&#xff0c;那记不住&#xff0c;怎么样才可以登录后台呢&#xff1f;下面来给大家说一下方法&#xff0c;第一种方法&#xff0c;就是进入数据库里面修改密码&#xff0c;第二种就是从新搭建&#…

windows系统mobaxterm远程执行linux上ssh命令

命令如下 start "" "%~dp0\MobaXterm_Personal_23.4.exe" -newtab "sshpass -p root ssh root192.168.11.92 mkdir 33" -p 是密码 左边是用户名&#xff0c;右边是服务器ip 后面跟的是服务器上执行的命令 第一次执行的时候要设置mobaxt…

SQL Server 2016(基本概念和命令)

1、文件类型。 【1】主数据文件&#xff1a;数据库的启动信息。扩展名为".mdf"。 【2】次要&#xff08;辅助&#xff09;数据文件&#xff1a;主数据之外的数据都是次要数据文件。扩展名为".ndf"。 【3】事务日志文件&#xff1a;包含恢复数据库的所有事务…

LeetCode 2661. 找出叠涂元素:多次映射

【LetMeFly】2661.找出叠涂元素&#xff1a;多次映射 力扣题目链接&#xff1a;https://leetcode.cn/problems/first-completely-painted-row-or-column/ 给你一个下标从 0 开始的整数数组 arr 和一个 m x n 的整数 矩阵 mat 。arr 和 mat 都包含范围 [1&#xff0c;m * n] 内…

服务注册发现 配置中心 springcloud alibaba nacos

文章目录 0100 系统环境0200 nacos安装0201 下载0202 安装 0300 工程说明0301 结构说明0302 运行效果 0400 代码说明0401 服务提供者&#xff08;Provider Service&#xff09;0402 服务消费者&#xff08;Consumer Service&#xff09;服务提供者SDK&#xff08;Provider Serv…

阿里云服务器跨区域迁移(多数据盘)

方法一. 复制镜像&#xff0c;共享镜像&#xff08;只有系统盘没有数据盘的情况&#xff01;&#xff09; 正常阿里云同区域服务器迁移只需要选择共享镜像即可&#xff0c;但是由于新老服务器区域限制所以需要先复制到新服务器区域再进行共享 选择服务器实例先创建后复制 比如…

1145. 北极通讯网络(Kruskal,并查集维护)

1145. 北极通讯网络 - AcWing题库 北极的某区域共有 n 座村庄&#xff0c;每座村庄的坐标用一对整数 (x,y) 表示。 为了加强联系&#xff0c;决定在村庄之间建立通讯网络&#xff0c;使每两座村庄之间都可以直接或间接通讯。 通讯工具可以是无线电收发机&#xff0c;也可以是…

基于SpringBoot的仓库管理系统设计与实现附带源码和论文

博主24h在线&#xff0c;想要源码文档部署视频直接私聊&#xff0c;全网最低价&#xff0c;9.9拿走&#xff01; 【关键词】仓库管理系统&#xff0c;jsp编程技术&#xff0c;mysql数据库&#xff0c;SSM&#xff0c;Springboot 目 录 摘 要 Abstract 第1章 绪论 1.1 课题…

shell编程系列(10)-使用paste拼接列

使用paste拼接列 前言使用paste拼接列拼接两个文件 结语 前言 在前面的文章中讲解了使用cut命令选择列&#xff0c;这篇文章我们介绍使用paste命令拼接列&#xff0c;其实这个命令的使用场景很有限&#xff0c;做科研的同学可能才会用到&#xff0c;但是却非常好用&#xff0c…

使用凌鲨进行内网穿透

为了方便在本地进行开发和调试工作&#xff0c;有时候需要安全地连接内网或Kubernetes集群中的服务。 在net proxy server中可以限制访问用户&#xff0c;也可以设置端口转发的密码。 使用 连接端口转发服务 列出可转发端口 可转发端口是服务端设置的&#xff0c;不会暴露真…

Linux 基础认识

文章目录 前言Linux历史window历史Linux地位发行版本 前言 建议只看概述 Linux历史 概述&#xff1a; 由一个研究生受Minix操作系统启发编写的&#xff0c;因为功能实用&#xff0c;代码开源被世界人接收和开发 &#xff0c;最终正式发布 。 详情&#xff1a; 1991年10月5日…

12.2_黑马Redis实战篇达人探店好友关注

目录 实战篇03 thinking &#xff1a;提取公共部分为一个方法的快捷键&#xff1f; thinking&#xff1a;redis中的ismember&#xff1f; thinking:BooleanUtil.isTrue? 实战篇04 thinking&#xff1a;zscore的用法&#xff1f; thinking&#xff1a;stream().map().co…

centos7 yum安装redis

1.安装epel源 yum install epel-release -y 2.安装 参数-y是遇到yes/no时 自动yes yum install redis -y 3.查看redis安装的位置 whereis redis 4.打开配置文件 vim /etc/redis.config 5.修改密码 在打开的文件中输入 /requirepass 后按下确认键&#xff0c;(找下一个关…

JVM虚拟机:JVM参数之标配参数

本文重点 本文我们将学习JVM中的标配参数 标配参数 从jdk刚开始就有的参数&#xff0c;比如&#xff1a; -version -help -showversion