学习笔记072——Java中的【JUC 并发编程】

JUC 并发编程

JUC 是指 Java 并发编程工具包

java.util.concurrent JDK 中的一个包,包下全部都是 Java 并发编程相关的类/接口

为什么公司如此看重并发编程的能力,并发编程的目的就是充分利用计算机的资源,把计算机的性能发挥到极致。

1、什么是高并发

并行和并发

并发是指多线程操作同一个资源,但不是同时操作,而是交替操作,单核 CPU。

并行才是真正的多个线程同时执行,多核 CPU,每个线程使用一个 CPU 的资源来运行。

我们所说的并发其实是一种程序的设计标准,在单核 CPU 的情况下,开发出来的代码应该更加充分利用资源,有效提升程序的效率。

编写出来的代码具备处理多个任务在同一时间段内执行的能力。

高并发是互联网分布式系统架构设计中必须要考虑的因素之一,它是指通过设计保证系统在互联网海量用户请求的情况下,能够确保系统正常运行。

互联网架构中,如何实现高并发设计?

垂直扩展和水平扩展

垂直扩展

提升单机的处理能力

1、增强单机的硬件性能,CPU 核数、提升内存、硬盘扩容、网卡升级。。。

2、提升软件架构性能,使用缓存来提高查询效率,使用异步请求来提升服务吞吐量,使用 NoSQL 提升数据访问能力,使用并发框架。。。
垂直扩展有上限,一定会达到某个瓶颈无法再优化。

水平扩展

水平扩展理论上没有上限,所以互联网分布式架构设计最终的解决方案还是水平扩展。

集群和分布式的区别?

集群是指每台服务器所完成的工作一样,通过增加服务器的数量来提高并发能力。

分布式是指将系统拆分成不同的模块,交给不同的服务器来完成。

饭店,一个厨师,多雇几个水平一样的厨师来应对客流量,这就是集群。

不增加厨师的数量,而是把做菜分步骤完成,洗菜,切菜,炒菜,然后给厨师雇两个助手,分别完成洗菜和切菜,厨师只负责炒菜。

2、Java 实现多线程的第三种方式

实现 Callable 接口,与 Runnable 的区别在于 Callable 方法有返回值。

在这里插入图片描述

package com.htl.test2;

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

public class Test {
    public static void main(String[] args) {
        //任务
        MyCallable myCallable = new MyCallable();
        //线程
        FutureTask<String> futureTask = new FutureTask<>(myCallable);
        new Thread(futureTask).start();
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("callable");
        return "success";
    }
}

Callable 和 Runnable 的区别

  • Callable 可以在任务结束后提供一个返回值,Runnable 没有这个功能。
  • Callable 中的 call 方法可以抛出异常,Runnable 的 run 不能抛异常。

3、sleep 和 wait 方法的区别

让线程暂停执行任务

来自不同的类

sleep 是 Thread 类中的方法

wait 是 Object 类中的方法

sleep 是直接让当前线程暂停执行,进入阻塞状态。暂停不是看用哪个线程对象调用,而是在哪调用;在哪调就是让当前线程休眠。

让主线程休眠

package com.htl.test;

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println("A----------------");
            }
        });

        thread.start();
        try {
            //让主线程休眠
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        for (int i = 0; i < 100; i++) {
            System.out.println("=====================B");
        }
    }
}

让子线程休眠

package com.htl.test;

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            try {
                //让子线程休眠
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 100; i++) {
                System.out.println("A----------------");
            }
        });

        thread.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("=====================B");
        }
    }
}

wait 也是让线程休眠,但是不直接作用于线程对象,而是作用于线程对象访问的资源,Thread —> data,调用 data.wait,此时 Thread 就会休眠。

wait 方法有一个前提,当前线程对象必须拥有 data 对象,所以 wait 方法只能在同步方法或者同步代码块中调用,否则抛出异常。

package com.htl.test;

import java.util.concurrent.TimeUnit;

public class Test2 {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.test(i);
            }
        }).start();
    }
}

class Data {
    public synchronized void test(int i){
        if(i == 5){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i + "--------------------------");
    }
}

wait 让线程对象休眠,没有阻塞时间,如果不加处理,线程会永远阻塞下去。

如何解除阻塞?

1、指定 wait 时间,wait(long millis)

if(i == 5){
    try {
        this.wait(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

2、通过 notify 方法唤醒线程 (notify方法跟wait方法一样,必须在同步方法中调用)

package com.htl.test;

import java.util.concurrent.TimeUnit;

public class Test2 {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.test(i);
            }
        }).start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(8);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            data.test2();
        }).start();
    }
}

class Data {
    //在i==5的时候让线程休眠
    public synchronized void test(int i){
        if(i == 5){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i + "--------------------------");
    }

    //唤醒线程
    public synchronized void test2(){
        this.notify();
    }
}

是否释放锁

wait 会释放锁,sleep 不会释放锁

4、synchronized 锁定的是谁?

一、如果 synchronized 修饰非静态方法,则锁定的就是方法调用者

package com.htl.test2;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            data.func1();
        },"A").start();

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

        new Thread(()->{
            data.func2();
        },"B").start();
    }
}

class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

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

在这里插入图片描述

synchronized 修饰的是 func1,func2,所以谁调用 func1、func2 就锁谁。

对代码进行修改:

package com.htl.test2;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data data1 = new Data();
        Data data2 = new Data();
        new Thread(()->{
            data1.func1();
        },"A").start();

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

        new Thread(()->{
            data2.func2();
        },"B").start();
    }
}

class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

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

在这里插入图片描述

不会排队,继续修改:

package com.htl.test2;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            data.func1();
        },"A").start();

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

        new Thread(()->{
            data.func3();
        },"B").start();
    }
}

class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

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

    public void func3(){
        System.out.println("3...");
    }
}

在这里插入图片描述

不会排队,因为 func3 的调用不需要锁定资源,无需等待 func3 的执行完毕即可执行。

如果方法添加了 synchronized ,不是说不能调,只是调用的是需要锁定当前对象,如果没有添加 synchronized,不需要考虑任何锁定的问题,直接调。

二、如果 synchronized 修饰静态方法,则锁定的就是,无论多少个对象调用,都会同步。

package com.htl.test2;

import java.util.concurrent.TimeUnit;

public class Test2 {
    public static void main(String[] args) {
        Data2 data2 = new Data2();
        Data2 data1 = new Data2();
        new Thread(()->{
            data1.func1();
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            data2.func2();
        }).start();
    }
}

class Data2 {
    public synchronized static void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

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

在这里插入图片描述

三、如果 synchronized 静态方法和 synchronized 非静态方法同时存在,静态方法锁类,实例方法锁对象

package com.htl.test3;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data3 data = new Data3();
        Data3 data2 = new Data3();
        new Thread(()->{
            data.func1();
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            data2.func2();
        }).start();
    }
}

class Data3{
    public synchronized static void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

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

在这里插入图片描述

四、如果 synchronized 修饰的是代码块,则锁定的是传入的对象

能否实现同步,就看锁定的对象是否位同一个对象

package com.htl.test4;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data4 data = new Data4();
        for (int i = 0; i < 5; i++) {
            Integer num = Integer.valueOf(1);
            new Thread(()->{
                data.func(num);
            }).start();
        }
    }
}

class Data4{
    public void func(Integer num){
        synchronized (num){
            System.out.println("start...");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

在这里插入图片描述

此时会同步,因为 num = 1 在内存中只有一份,5 个线程争夺同一个资源。

package com.htl.test4;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Data4 data = new Data4();
        for (int i = 0; i < 5; i++) {
            Integer num = Integer.valueOf(i);
            new Thread(()->{
                data.func(num);
            }).start();
        }
    }
}

class Data4{
    public void func(Integer num){
        synchronized (num){
            System.out.println("start...");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

在这里插入图片描述

不会同步,因为 5 个线程有 5 个不同的 num,不会产生争夺,而是各用各的。

public class Test {
    public static void main(String[] args) {
        Account account = new Account();
        new Thread(()->{
            account.count();
        },"A").start();
        new Thread(()->{
            account.count();
        },"B").start();
    }
}
class Account{
    private Integer num = 0;
    private Integer id = 0;
    public void count(){
        synchronized (id){
            num++;
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "是第" + num + "位访客");
        }
    }
}

5、ConcurrentModificationException

并发修改异常,集合 List Set Map

package com.htl.test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

当多个线程同时对集合对象进行读写操作的时候,就会抛出 ConcurrentModificationException 异常。

ArrayList 是线程不安全的

在这里插入图片描述

如何解决?使用线程安全的类来完成数据的装载。

1、直接将 ArrayList 替换为 Vector

package com.htl.test;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        List<String> list = new Vector<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

2、Collections.synchronizedList

package com.htl.test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

3、JUC CopyOnWriteArrayList

package com.htl.test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

CopyOnWrite 写时复制,当我们往一个容器中添加元素的时候,不是直接操作这个容器,而是将原来的容器先复制一份,往复制出来的新容器中添加元素,添加完毕,再将原容器的引用指向新容器,以此来解决并发修改异常,实际上就是实现了读写分离。

在这里插入图片描述

在这里插入图片描述

6、JUC 工具类

CountDownLatch

减法计数器

Java Memory Model Java 内存模型 JMM

工作内存,当一个线程对数据进行操作的时候,不会直接操作主内存,而是会将

主内存中的数据进行复制,复制到工作内存中,线程操作的是工作内存中的数据,操作

完成之后,再将工作内存中的数据同步到主内存中。

package com.htl.test;

import java.util.concurrent.CountDownLatch;

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

        CountDownLatch countDownLatch = new CountDownLatch(100);

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println("+++++++++++++++++++++++++Thread");
                countDownLatch.countDown();
            }
        }).start();

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (int i = 0; i < 100; i++) {
            System.out.println("main-------------------------");
        }
    }
}

countDown():计数器减一

await():计数器停止,唤醒其他线程

CyclicBarrier

加法计数器

package com.htl.test;

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

public class CyclicBarrierTest {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(30, ()->{
            System.out.println("放行");
        });

        for (int i = 0; i < 90; i++) {
            final int temp = i;
            new Thread(()->{
                //lambda表达式中只能访问final修饰的变量
                System.out.println("-->" + temp);
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

Semaphore

计数信号量,实现限流操作,限制可以访问某些资源的线程数量。

Semaphore 只有 3 个操作

  • 初始化
  • 获得许可
  • 释放
package com.htl.test;

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

public class SemaphoreTest {
    public static void main(String[] args) {
        //初始化
        Semaphore semaphore = new Semaphore(5);
        for (int i = 0; i < 15; 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) {
                    e.printStackTrace();
                } finally {
                    //释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

7、读写锁

ReadWriteLock 接口,实现类 ReentrantReadWriteLock,可以多线程同时读,但是同一时间只能有一个线程进行写入。

读锁是一个共享锁,写锁是一个独占锁。

package com.htl.test;

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

public class ReadWriteLockTest {
    public static void main(String[] args) {
        Cache2 cache = new Cache2();
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                cache.write(temp,String.valueOf(temp));
            }).start();
        }

        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                cache.read(temp);
            }).start();
        }
    }
}

class Cache2{

    private Map<Integer,String> map = new HashMap<>();
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    /**
     * 写操作
     * @param key
     * @param value
     */
    public void write(Integer key,String value){
        readWriteLock.writeLock().lock();
        System.out.println(key + "开始写入");
        map.put(key, value);
        System.out.println(key + "写入完毕");
        readWriteLock.writeLock().unlock();
    }

    /**
     * 读操作
     * @param key
     */
    public void read(Integer key){
        readWriteLock.readLock().lock();
        System.out.println(key + "开始读取");
        map.get(key);
        System.out.println(key + "读取完毕");
        readWriteLock.readLock().unlock();
    }

}

8、线程池

存放线程对象的缓冲池,为了节约资源。

预先创建一定数量的线程对象,存入缓冲池中,需要用的时候直接从缓冲池中取出,用完不销毁,重新放回到缓冲池中,供下一次请求使用。

优势:

  • 提高线程的利用率
  • 提高响应速度
  • 便于统一管理线程对象
  • 可控制最大并发数

7 大核心参数:

1、corePoolSize 核心池的大小,目前上班的柜台数

2、maximumPoolSize 线程池最大线程数,所有的柜台数(包括上班和不上班的)

3、keepAliveTime:空闲线程的存活时间

4、unit:keepAliveTime 时间单位

5、workQueue:阻塞队列,等候区

6、threadFactory:线程工厂,生成线程对象

7、handler:拒绝任务策略

package com.htl.test;

import java.util.concurrent.*;

public class Test2 {
    public static void main(String[] args) {
        //线程池对象
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                3,
                1L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        for (int i = 0; i < 6; i++) {
            executorService.execute(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "===>办理业务");
            });
        }

        executorService.shutdown();
    }
}

Executors 提供了三种快速创建线程池的方式

newSingleThreadExecutor():线程池中只有一个线程对象

newFixedThreadPool(5):线程池中有 5 个线程对象

newCachedThreadPool():线程池中线程对象随机分配,由电脑配置决定

package com.htl.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test3 {
    public static void main(String[] args) {
        //单例线程池,只有一个线程对象
//        ExecutorService executorService = Executors.newSingleThreadExecutor();
//        ExecutorService executorService = Executors.newFixedThreadPool(5);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10000; i++) {
            final int temp = i;
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName() + ":" + temp);
            });
        }
        executorService.shutdown();
    }
}

handler 提供了四种拒绝策略

AbortPolicy          直接抛出异常
DiscardPolicy  		 放弃任务,不抛出异常
DiscardOldestPolicy	 尝试与阻塞队列最前面的任务去争夺,不抛出异常
CallerRunsPolicy     谁调用谁处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值