JUC

JUC:

java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks

并发编程根本原因:充分利用CPU的资源
java代码获取CPU核数:

public static void main(String[] args) {
        System.out.println(Runtime.getRuntime().availableProcessors());
    }

1,线程6种状态:

public enum State {   
        NEW,			//新生
        RUNNABLE,		//运行
        BLOCKED,		//阻塞
        WAITING,		//等待
        TIMED_WAITING,	//超时等待
        TERMINATED;		//终止
    }

2,wait sleep区别:

1,来自不同的类 wait -> Object sleep->Thread。
2,wait会释放锁,sleep不会释放锁。
3,使用范围不同,wait必须在同步代码块中使用,sleep可以在任何地方使用。
4,wait不需要捕获异常,sleep必须捕获异常。

3,Lock锁

Interface Lock:

所有已知实现类:
    ReentrantLock , ReentrantReadWriteLock.ReadLock , ReentrantReadWriteLock.WriteLock 
//可重入锁
	public ReentrantLock() {
        sync = new NonfairSync();//默认非公平锁
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
3.1 synchronized和lock区别:。

1,synchronized是Java内置关键字,Lock是一个Java接口。
2,synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁。
3,synchronized会自动释放锁,Lock必须要手动释放锁。如果不释放会造成死锁。
4,synchronized线程会等待锁,Lock锁不一定会等待下去。
5,synchronized适合锁少量的代码同步问题,Lock锁适合锁大量的同步代码块。

4 集合类不安全

4.1 ArrayList:并发下不安全,测试如下:
public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }

结果:
结果可见出现了并发修改异常java.util.ConcurrentModificationException。
解决方案:

//解决方案:
//通过Vector
List<String> list = new Vector<>();//add时用的synchronized
//通过集合工具类转换
List<String> list = Collections.synchronizedList(new ArrayList<>());
//通过JUC java.util.concurrent.CopyOnWriteArrayList;
List<String> list = new CopyOnWriteArrayList<>();//add时用的lock锁
4.2 Set
public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        //解决方案:
        //1,通过集合工具类转换
        Set<String> set1 = Collections.synchronizedSet(new HashSet<>());
        //2,通过JUC java.util.concurrent.CopyOnWriteArraySet;
        Set<String> set2 = new CopyOnWriteArraySet<>();

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

顺便说一下HashSet底层是什么:

private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
    
public HashSet() {
        map = new HashMap<>();
    }

可见底层是一个HashMap。再来看add方法

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

因为map的key不能重复,所以set中的值不能重复。

4.3 Map
public static void main(String[] args) {
        Map<String, Object> map = new HashMap<>();
        //解决方案:
        //通过集合工具类转换
        Map<String, Object> map1 = Collections.synchronizedMap(new HashMap<>());
        //通过JUC java.util.concurrent.ConcurrentHashMap;
        Map<String, Object> map2 = new ConcurrentHashMap<>();
    }

5 Callable

Callable接口类似于Runnable。

1,可以有返回值
2,可以抛出异常
3,方法不同,callable->call() Runnable->run()

另外Callable有使用缓存,结果可能需要等待,有缓存。

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

Callable使用方式:通过FutureTask类

public class Test {
    public static void main(String[] args) {
        //Callable调用方式  通过FutureTask
        //因为FutureTask间接实现了Runnable接口,且能接受Callable接口类型参数;
        FutureTask task = new FutureTask(new Callable_Thread());
        new Thread(task).start();
    }
}
class Callable_Thread implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("call()");
        return "call";
    }
}

6 常用辅助类

6.1 CountDownLatch 减法计数器

使用方法:

public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "已执行");
                countDownLatch.countDown();//计数器减一
            },String.valueOf(i)).start();
        }
        countDownLatch.await();//等待计数器归零,再向下执行
        System.out.println("end");
    }

countDownLatch.countDown();//计数器减一
countDownLatch.await();//等待计数器归零,再向下执行

6.2 CyclicBarrier 加法计数器
public static void main(String[] args) throws InterruptedException {
        //CyclicBarrier有两个构造方法
        //CyclicBarrier(int parties, Runnable thread)构造方法,当计数器到达parties时,会执行thread.start()方法
        CyclicBarrier cyclicBarrier = new CyclicBarrier(8,new Thread(()->{
            System.out.println("计数器已到8");
        }));

        for (int i = 0; i < 8; i++) {
            new Thread(()->{
                try {
                    cyclicBarrier.await();
                } catch (Exception e) {
                }
            }).start();
        }
    }
6.3 Semaphore 计数信号量
public static void main(String[] args) throws InterruptedException {
        //抢车位 只有3个位置
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到了车位");
                    TimeUnit.SECONDS.sleep(2);//睡两秒

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                    System.out.println(Thread.currentThread().getName() + "释放了车位");
                }
            }).start();
        }
    }

semaphore.acquire();获得信号量,如果满了,则等待。
semaphore.release();释放信号量,将当前的信号量释放,然后唤醒等待的线程。

作用:多个资源互斥的使用,可以控制最大的线程数,进行并发限流。

7 ReadWriteLock 读写锁

读操作可以被多线程同时读。
写操作只能同时被一个线程写。
更加细粒度的控制

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

其实现类为ReentrantReadWriteLock。
使用如下:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyCache myCache = new MyCache();
        //5个线程写操作
        for (int i = 0; i < 5; i++) {
            final int temp = i;//该值用于lambda表达式中
            new Thread(()->{
                myCache.write(Thread.currentThread().getName(),temp);
            },String.valueOf(i)).start();
        }
        //5个线程读操作
        for (int i = 0; i < 5; i++) {
            final int temp = i;//该值用于lambda表达式中
            new Thread(()->{
                myCache.read(Thread.currentThread().getName());
            },String.valueOf(i)).start();
        }
    }
}
class MyCache{
    private volatile Map<Object,Object> map = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void write(Object key, Object value){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "开始写入" + key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "写入" + key + "完毕");
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public void read(Object key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "开始读取" + key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取" + key + "完毕,值为:" + o);
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}
"S:\Program Files\JDK\jdk1.8.0_221\bin\java.exe" ... 
0开始写入0
0写入0完毕
1开始写入1
1写入1完毕
2开始写入2
2写入2完毕
3开始写入3
3写入3完毕
4开始写入4
4写入4完毕
3开始读取3
3读取3完毕,值为:3
4开始读取4
0开始读取0
2开始读取2
2读取2完毕,值为:2
4读取4完毕,值为:4
1开始读取1
0读取0完毕,值为:0
1读取1完毕,值为:1

分析结果可知,写操作是同步执行的,读操作不是。

独占锁(写锁)同时只能被一个线程占有
共享锁(读锁)同时可以被多个线程占有

8 阻塞队列 BlockingQueue

4种API(Application Programming Interface,应用程序接口)
方法抛出异常不抛出异常,有返回值阻塞等待超时等待
添加add(E e)offer(E e)put(E e)offer(E e, long timeout, TimeUnit unit)
移除remove()poll()take()poll(long timeout, TimeUnit unit)
检测队首元素element()peek()--

blockingQueue.add(E e)队列已满时执行抛出异常"java.lang.IllegalStateException: Queue full"
blockingQueue.remove() 队列为空时执行会抛出和"java.util.NoSuchElementException"

blockingQueue.offer(E e) 队列已满时执行返回false。
blockingQueue.poll() 队列为空时执行返回null。

lockingQueue.put(E e) 队列已满时执行会一直等待下去,直到put进去。
blockingQueue.take() 队列为空时执行会一直等待下去,知道取到值。

blockingQueue.offer(E e, long timeout, TimeUnit unit) 队列已满时执行,会等待timeout unit,如果没有offer进去会自动结束,不再offer值。
blockingQueue.poll(long timeout, TimeUnit unit) 队列为空时执行,如果在timeout unit时间内取不到值会返回null

9 线程池

3大方法,7大参数,4种拒绝策略

池化技术:优化资源的使用。事先准备好一些资源。(线程池,连接池,内存池,对象池)

好处:
1,降低资源消耗
2,提高响应的熟速度
3,方便管理线程
4,线程复用
5,控制最大并发数

3大方法
ExecutorService singlePool = Executors.newSingleThreadExecutor();
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
ExecutorService cachePool = Executors.newCachedThreadPool();
7大参数
public ThreadPoolExecutor(
			int corePoolSize,					//核心线程数
            int maximumPoolSize,				//最大线程数
            long keepAliveTime,					//线程保持活跃时间
            TimeUnit unit,						//时间单位
            BlockingQueue<Runnable> workQueue,	//阻塞队列
            ThreadFactory threadFactory,		//线程工厂
            RejectedExecutionHandler handler	//拒绝策略
            ){
        ***
    }
4种拒绝策略

interface RejectedExecutionHandler 共有4种实现类。如下:
4种拒绝策略

1,AbortPolicy:中止策略,拒绝任务的处理,并抛出一个RejectedExecutionException异常
2,CallerRunsPolicy:继续运行策略 ,拒绝任务,返回给线程的调用者执行该任务。
3,DiscardPolicy:放弃策略,拒绝任务并直接丢弃,不会抛出异常。
4,DiscardOldestPolicy:舍弃最旧策略,丢弃最旧的未处理任务,然后重试,不行的话继续丢弃任务

最大线程数如何确定?

1,CPU密集型:线程数等于或略小于cpu核数,可以保持cpu效率最高。
2,IO密集型:线程数大于耗IO资源的线程。

10 Volatile关键字

使变量在多个线程间可见

强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

3个特性:

1,保证可见性
2,不保证原子性
3,禁止指令重排(内存屏障)

内存屏障:
1,保证特性操作的执行顺序
2,保证某些变量的内存可见性

【拓展】

  • 如果不用lock锁,不用synchronized,怎么保证原子性?
  • 可用JUC下的原子类解决。

volatile和synchronized的区别:

volatilesynchronized
性能volatile是线程同步的轻量级实现,性能更优性能低于volatile
修饰范围只能修饰变量方法,代码块
阻塞不会
特性可以保证可见性,不能保证原子性可以保证原子性,间接保证了可见性
目的解决的是变量在多个线程间的可见性解决的是多个线程之间访问资源的同步性

11 死锁

排查:使用JDK自带工具,执行jps命令得到线程ID,执行jstack+线程ID查看结果。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大豆芽菜儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值