侧边栏壁纸
  • 累计撰写 53 篇文章
  • 累计收到 5 条评论

JUC_基础篇_中

bbchen
2023-05-27 / 0 评论 / 55 阅读 / 正在检测是否收录...

img

集合的线程安全

先来看GPT的回答:

Java集合在多线程环境中可能会出现ConcurrentModificationException异常,这是因为在多个线程同时操作集合时,可能会导致集合的结构发生变化,从而导致迭代器或者其他操作出现异常。

这个问题通常出现在使用非线程安全的集合(如ArrayList、HashMap等)时,多个线程同时对集合进行操作,例如同时进行添加、删除、修改等操作。当一个线程在迭代集合元素的同时,另一个线程修改了集合的结构(例如添加或删除元素)时,就有可能导致ConcurrentModificationException异常的出现。

为了避免这个问题,可以使用线程安全的集合,例如Vector、Hashtable等,或者使用Java集合框架提供的线程安全的集合类,例如Collections.synchronizedList()Collections.synchronizedMap()等。此外,也可以使用并发集合类(如ConcurrentHashMapCopyOnWriteArrayList等),它们提供了更高效的并发访问方式,可以避免ConcurrentModificationException异常的出现。

另外,在使用集合时,需要遵循以下原则,以避免ConcurrentModificationException异常的出现:

  1. 不要在迭代集合的同时修改集合的结构,否则会导致ConcurrentModificationException异常的出现。
  2. 在多线程环境下,使用线程安全的集合或者使用并发集合类,以避免多个线程同时对集合进行修改的问题。
  3. 在使用非线程安全的集合时,可以使用synchronized关键字或者Lock对象等方式来保证同步访问,避免多个线程同时对集合进行修改的问题。
  4. 在使用迭代器访问集合时,需要使用Iterator接口提供的remove()方法来删除集合中的元素,而不是使用集合提供的remove()方法,以避免ConcurrentModificationException异常的出现。

总之,在使用Java集合框架时,需要注意多线程环境下的并发访问问题,以确保程序的正确性和稳定性。

多线程锁

锁的八种情况

synchronized实现同步的基础:Java中的没一个对象都可以作为锁,具体表现为以下三种形式:

  • 对于普通方法,锁是当前实例对象,即this关键字所表示的对象,而不是方法本身。
  • 对于静态同步方法,锁是当前类的Class对象
  • 对于同步方法块,锁是synchronized括号里配置的对象,如果括号里配置的对象为null,则表示锁定当前实例对象。

公平锁和非公平锁

ReetrantLock若不传入参数,默认为非公平锁,其效果为:线程在尝试获取锁时,不会考虑等待时间的长短或者其他线程的优先级,而是直接尝试获取锁。

在高并发的情况下,非公平锁可能会导致某些线程一直无法获取到锁,从而导致线程饥饿现象,这种情况下,可以考虑采用公平锁来保证线程公平竞争。需要注意的是,公平锁的效率相对较低,因为它需要维护一个等待队列,以确保线程按照等待时间的长短或者其他优先级的顺序来获取锁。

可重入锁

synchronized(隐式)和Lock(显式)都是可重入锁

进入大门后,家中的其他门都可自由进入,即为可重入锁(是同一把锁),可重入锁的作用在于,当一个线程已经获取了某个锁,并且在持有该锁的情况下,又需要获取该锁时,如果该锁是可重入的,这个线程就可以继续获取锁,并且不会被自己所持有的锁所阻塞,从而避免了死锁的发生。

死锁

两个或两个以上进程/线程在执行过程中,因为争夺资源而造成的一种互相等待的现象,如果没有外力干涉,他们无法再执行下去

image-20230524152837064

死锁通常发生在多线程或多进程的环境中,而且需要满足以下四个条件:

  1. 互斥条件:某个资源只能被一个进程或线程占用,如果其他进程或线程要访问该资源,则必须等待当前进程或线程释放该资源。
  2. 请求与保持条件:进程或线程已经占有了某个资源,又请求获取其他资源时,如果该资源被其他进程或线程占用,则当前进程或线程会阻塞等待。
  3. 不剥夺条件:已经分配给进程或线程的资源不能被强制性地剥夺,只能由持有资源的进程或线程自行释放。
  4. 环路等待条件:存在一个进程或线程资源的请求序列,并且该序列形成了一个环路,即每个进程或线程都在等待下一个进程或线程所持有的资源。

模拟死锁:

package com.bbedu.juc.sync;

import java.util.concurrent.TimeUnit;

/**
 * @projectName: jvm-study
 * @package: com.bbedu.juc.sync
 * @className: DeadLock
 * @author: BBChen
 * @description: 模拟死锁
 * @date: 2023/5/27 14:38
 * @version: 1.0
 */
public class DeadLock {

    static Object a = new Object();
    static Object b = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (a) {
                System.out.println(Thread.currentThread().getName() + " 持有锁a,试图获取锁b");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (b) {
                    System.out.println(Thread.currentThread().getName() + " 获取到锁b");
                }
            }
        }, "A").start();

        new Thread(()->{
            synchronized (b) {
                System.out.println(Thread.currentThread().getName() + " 持有锁b,试图获取锁a");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (a) {
                    System.out.println(Thread.currentThread().getName() + " 获取到锁a");
                }
            }
        }, "B").start();


    }
}

验证是否是死锁

  1. jps
  2. jstack

image-20230527144700259

image-20230527144745000

Callable

创建线程的多种方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口
  4. 线程池方式

Runnable和Callable

名称Runnable接口Callable接口
是否有返回值
是否抛出异常
实现方法名称run()call()
package com.bbedu.juc.callable;

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


class MyThread1 implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable");
    }
}

class MyThread2 implements Callable {
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable");
        return 200;
    }
}

/**
 * @projectName: jvm-study
 * @package: com.bbedu.juc.callable
 * @className: Demo1
 * @author: BBChen
 * @description: 演示Runnable和Callable接口使用
 * @date: 2023/5/27 14:55
 * @version: 1.0
 */
public class Demo1 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // Runnable接口
        new Thread(new MyThread1(), "AA").start();

        FutureTask<Integer> futureTask1 = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + " callable");
            return 1024;
        });
        FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + " callable");
            return 2048;
        });

        // Callable接口
//        new Thread(new FutureTask(new MyThread2()), "BB").start();

        new Thread(futureTask1, "Lucy").start();
        new Thread(futureTask2, "Mary").start();

//        while (!futureTask.isDone()) {
//            System.out.println("wait...");
//        }

        System.out.println(futureTask1.get());
        System.out.println(futureTask2.get());

        System.out.println(Thread.currentThread().getName() + " over");
    }
}

JUC辅助类

CountDownLatch

用于协调多个线程之间的同步操作。它可以让一个或多个线程等待其他线程完成某个操作之后再继续执行。

package com.bbedu.juc.util;

import java.util.concurrent.CountDownLatch;

/**
 * @projectName: jvm-study
 * @package: com.bbedu.juc.util
 * @className: CountDownLatchDemo
 * @author: BBChen
 * @description: 演示CountDownLatch的使用
 * @date: 2023/5/27 15:39
 * @version: 1.0
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {

        // 创建CountDownLatch,设置初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + " 号线程离开");
                // 计数 -1
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }

        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "线程 over");
    }
}

CyclicBarrier

用于协调多个线程之间的同步操作。它可以让一组线程在达到某个屏障点之前互相等待,然后在屏障点处被阻塞,直到所有线程都到达屏障点,然后所有线程才能继续执行。

package com.bbedu.juc.util;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * @projectName: jvm-study
 * @package: com.bbedu.juc.util
 * @className: CyclicBarrierDemo
 * @author: BBChen
 * @description: 演示CyclicBarrier的使用
 * @date: 2023/5/27 20:06
 * @version: 1.0
 */
public class CyclicBarrierDemo {

    private static final int NUMBER = 7;

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {
            System.out.println("集齐七龙珠可以召唤神龙");
        });

        for (int i = 1; i <= 7; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() +
                            " 星龙珠已经集齐");
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

Semaphore

package com.bbedu.juc.util;

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

/**
 * @projectName: jvm-study
 * @package: com.bbedu.juc.util
 * @className: SemaphoreDemo
 * @author: BBChen
 * @description: 演示Semaphore的使用
 * @date: 2023/5/27 20:19
 * @version: 1.0
 */
public class SemaphoreDemo {
    public static void main(String[] args) {

        // 模拟三个停车位
        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(new Random().nextInt(5));

                    System.out.println("[-]" + Thread.currentThread().getName() +
                            " 离开了车位");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    // 释放,离开停车位
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }

    }
}

读写锁

悲观锁和乐观锁

悲观锁和乐观锁是并发编程中的两种常见的锁机制。

悲观锁是指在操作共享资源时,假定其他线程可能会修改该资源,因此在访问共享资源时会先加锁,保证在操作期间其他线程不能修改该资源,操作完成后再释放锁。悲观锁的实现一般使用互斥锁(如 synchronized 关键字或 ReentrantLock 类)来实现。

乐观锁是指在操作共享资源时,假定其他线程不会修改该资源,因此在访问共享资源时不加锁,直接进行操作,然后在更新共享资源时检查是否有其他线程对该资源进行了修改。如果没有修改,则更新成功,否则需要重试。乐观锁的实现一般使用版本号或时间戳来实现。

表锁和行锁

表锁和行锁是数据库中常见的两种锁机制。

表锁是指对整张表加锁,当一个事务对表进行修改时,会锁定整张表,其他事务不能同时对该表进行修改,直到该事务释放锁为止。表锁的优点是实现简单,锁开销小,缺点是并发度低,对于高并发的应用场景不适用。

行锁是指对表中的某一行数据加锁,当一个事务对某一行数据进行修改时,会锁定该行数据,其他事务只能在该行数据没有被锁定的情况下进行操作,否则需要等待锁释放。行锁的优点是并发度高,对于高并发的应用场景适用,缺点是实现相对复杂,锁开销相对大。

在实际应用中,表锁和行锁都有各自的应用场景。如果应用程序的并发量不高,或者对数据的操作相对简单,使用表锁可以实现较高的性能。但是如果应用程序的并发量较高,或者对数据的操作比较复杂,使用行锁可以提高并发度,减少锁等待时间,从而提高性能。

读锁和写锁

读锁:共享锁

写锁:独占锁

读锁和写锁都可能发生死锁

读锁是用于读取共享资源的锁,当一个线程持有读锁时,其他线程也可以持有读锁,但是不能持有写锁。读锁可以多个线程同时持有,因为读操作不会修改共享资源,不会产生竞争条件。

写锁是用于修改共享资源的锁,当一个线程持有写锁时,其他线程不能持有读锁或写锁,只有当写锁被释放后,其他线程才能获取读锁或写锁。写锁的目的是为了保证在写操作期间,共享资源不会被其他线程进行读取或写入,避免竞争条件的产生。

package com.bbedu.juc.readwrite;

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

/**
 * @projectName: jvm-study
 * @package: com.bbedu.juc.readwrite
 * @className: ReadWriteLockDemo
 * @author: BBChen
 * @description: 演示读写锁操作
 * @date: 2023/5/27 20:40
 * @version: 1.0
 */
public class ReadWriteLockDemo {

    public static void main(String[] args) throws InterruptedException {
        MyCache myCache = new MyCache();

        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.put("" + num, "" + num);
            }, String.valueOf(i)).start();
        }

//        TimeUnit.MILLISECONDS.sleep(300);

        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.get("" + num);
            }, String.valueOf(i)).start();
        }
    }
}

// 资源类
class MyCache {

    // 创建读写锁对象
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    // 创建map集合
    private volatile Map<String, Object> map = new HashMap<>();

    public void put(String key, Object value) {

        // 添加写锁
        readWriteLock.writeLock().lock();


        try {
            System.out.println(Thread.currentThread().getName() +
                    " 正在写操作" + key);
            TimeUnit.MILLISECONDS.sleep(300);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() +
                    " 写完成" + key);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //释放写锁
            readWriteLock.writeLock().unlock();
        }

    }

    public Object get(String key) {

        // 添加读锁
        readWriteLock.readLock().lock();

        Object o;

        try {
            System.out.println(Thread.currentThread().getName() +
                    " 正在读取操作" + key);
            TimeUnit.MILLISECONDS.sleep(300);
            o = map.getOrDefault(key, null);
            System.out.println(Thread.currentThread().getName() +
                    " 读取完成" + key);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // 释放读锁
            readWriteLock.readLock().unlock();
        }
        return o;
    }
}

image-20230527211509272

锁降级

将写入锁降级为读锁

image-20230527223101988

package com.bbedu.juc.readwrite;

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @projectName: jvm-study
 * @package: com.bbedu.juc.readwrite
 * @className: Demo1
 * @author: BBChen
 * @description: 演示锁降级
 * @date: 2023/5/27 22:33
 * @version: 1.0
 */
public class Demo1 {

    public static void main(String[] args) {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        // 读锁
        ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();

        // 写锁
        ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

        // 锁降级
        // 获取写锁
        writeLock.lock();
        System.out.println("锁降级");
        // 获取读锁
        readLock.lock();
        System.out.println("锁降级--read");
        // 释放写锁
        writeLock.unlock();
        // 释放读锁
        readLock.unlock();
    }
}
0

评论

博主关闭了所有页面的评论