多线程基础(多线程内存,安全,通信,线程池和阻塞队列)

3:多线程内存以及线程安全,线程通信

  • 静态变量(static)在方法区上,变量的值在堆上
  • 静态常量(static final)在方法区上,常量的值在堆上
  • 实例变量(属于对象的属性,需要在对象的基础上建立),变量的值在堆上
  • 局部变量在线程对应的栈上,直接赋值的值在栈上,如果是创建的对象赋值则在堆上

线程私有内存:每一个线程都有自己对应的栈,这些局部变量都属于线程私有的,其它线程是相互隔离的,JVM允许满足某些条件的线程使用。

线程共享内存:各个线程共享的内存是方法区和堆,至于线程是否能使用方法区和堆上的数据,取决于这些数据的可见性。
在这里插入图片描述

线程安全:如果多线程环境下代码运行的结果符合我们的预期(即单线程环境应该的结果),则说这个程序是线程安全的。

线程不安全:多个线程执行共享变量的操作:同为读操作则不会发生安全问题,至少有一个线程处于写操作则会发生安全问题。

导致线程不安全的原因:

  • 不具有原子性:原子性是多行指令是不可拆分的最小执行单位,但一条Java语句不一定是原子的(可以拆分为多条语句)。

  • 不满足可见性:一个线程修改共享变量后,能够及时的被其它线程看到称为可见性,而共享变量处于主内存中,线程修改共享变量的过程是:

    • 从主内存拷贝一份共享变量到工作内存
    • 在工作内存对共享变量进行修改
    • 将修改后的共享变量写回主内存

    所以,当线程A修改后,还未写回主内存时,线程B从主内存取到的数据是未修改的数据。

  • 代码顺序性:指令重排序是单线程环境下JVM和CPU会优化代码执行顺序,而多线程环境下修改执行顺序可能导致线程安全问题的发生。

解决方式1:Volatile关键字

使用Volatile修饰某个变量(实例变量,静态变量)

Volatile关键字作用

  1. 保证可见性:多个线程对同一个变量具有可见性
  2. 禁止指令重排序:建立内存屏障
  3. 不保证原子性操作:只能保证共享变量的读读操作的安全和多读一写操作的安全,如果存在多个写进程则不可以保证线程安全。

解决方式2:Synchronized关键字

Synchronized作用:基于对象头加锁操作,满足原子性,可见性,有序性

  1. 互斥:满足原子性(某个线程执行同一个对象加锁的同步代码,排斥另一个线程加锁,满足最小执行单位)
  2. 刷新内存:synchronized结束释放锁会把工作内存的数据刷新到主存,其它线程申请锁始终得到最新数据(满足可见性)
  3. 有序性:某个线程执行一段同步代码,不管如何重排序,过程中不可能有其它线程执行指令
  4. 可重入:Synchronized为可重入锁,不会出现将自己锁死的问题,同一个线程可以多次申请同一个对象锁

同步代码块:使用相同的对象进行加锁

public class 同步代码块 {

    private static int count=0;
	//这个object就充当锁的作用,所有线程都使用此对象加锁
    private static Object object=new Object();
    
    public static void main(String[] args) throws InterruptedException {
        for(int i=0 ;i <10 ;i ++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (object){
                        for(int i=0 ;i <100 ; i++){
                            count++;
                        }
                    }
                }
            }).start();
        }
        Thread.sleep(10000);
        System.out.println(count);
    }
}

同步方法:

  • 实例方法的同步监视器是当前类的实例对象

    public class 实例同步方法 {
    
        public static int count=0;
    
        public static void main(String[] args) throws InterruptedException {
            Runnable runnable=new Runnable() {
                @Override
                public void run() {
                    add();
                }
                //实例同步方法,同步监视器为当前类的实例对象 就是runnable对象
                public synchronized void add(){
                        for(int i=0;i<100;i++){
                            count++;
                        }
                }
            };
    
            for(int i=0;i<10;i++){
                //线程共用一个runnable保证同步监视器相同
                new Thread(runnable).start();
            }
    
            Thread.sleep(1000);
            System.out.println(count);
        }
    }
    
  • 静态方法的同步监视器是当前类对象

public class 静态同步方法 {

    public static int count=0;
	//静态同步方法,同步监视器为当前类对象 静态同步方法.class
    public static synchronized void add(){
        for(int i=0; i <100 ; i++){
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for(int i=0 ;i <10 ;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    add();
                }
            }).start();
        }
        Thread.sleep(10000);
        System.out.println(count);
    }
}

读操作使用volatile关键字修饰,写操作加锁-满足线程安全,效率高

多线程通信:一个线程以通知的方式唤醒某些等待线程,也可以在某些条件下让当前线程等待,这样就能让线程通过通信的方式达到一定顺序性。

前提条件:必须在加锁的条件下使用

线程通信API说明
wait()释放锁,当前线程等待
notify()通知的方式唤醒一个线程
notifyAll()通知的方式唤醒全部线程

通信例子:

public class 线程通信 {

    //库存
    public static volatile int COUNT=0;

    //锁
    public static Object lock=new Object();

    public static void main(String[] args) {


        //生产任务,当库存达到100时停止生产
        Runnable runnable1=new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (lock){
                        if(COUNT<100){
                            COUNT++;
                            System.out.println("生产一个产品,产品数量为"+COUNT);
                            lock.notifyAll();
                        }else {
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }

            }
        };

        //5个生产者进程
        for(int i=0 ; i <5 ;i++){
            new Thread(runnable1).start();
        }

        //消费任务,库存小于等于0停止消费
        Runnable runnable2=new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (lock){
                        if(COUNT>0){
                            COUNT--;
                            System.out.println("消费一个产品,产品数量为"+COUNT);
                            lock.notifyAll();
                        }else{
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                   
                }
            }
        };
		//10个消费者进程
        for(int i=0 ;i <10 ;i++){
            new Thread(runnable2).start();
        }
    }
}

4:阻塞队列和线程池

阻塞队列:线程安全的数据结构,满足队列的特性先进先出

  1. 队列满的时候继续入队会阻塞,直到有线程从队列中取走元素
  2. 队列空的时候继续出队会阻塞,直到有线程往队列中放入元素
  3. 阻塞队列经典的应用场景就是生产者消费者模型

阻塞队列用于生产者消费者模型:

  • 生产者和消费者不直接通讯,而是通过阻塞队列进行通讯,生产者生产的产品放到阻塞队列中,消费者从阻塞队列中取产品。(削峰处理)
  • 阻塞队列相当于一个缓冲区,平衡生产者和消费者的平衡,并使得生产者和消费者解耦。

Java提供的阻塞队列:BlockingQueue

常用实现类:

  • LinkedBlockingQueue
  • ArrayBlockingQueue

线程池:初始化线程池的时候,就创建一定数量的线程(从线程池的阻塞队列中取任务)

线程池优势:线程的创建和销毁会有一定开销,使用线程池可以重复使用线程执行多组任务。

Java原生线程池:

	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
    
    	//参数解释
        int corePoolSize:核心线程数
		int maximumPoolSize:线程总数
		long keepAliveTime: 允许空闲的时间
		TimeUnit unit:keepAliveTime的单位秒,分等
		BlockingQueue<Runnable> workQueue:传递任务的阻塞队列
		ThreadFactory threadFactory:创建线程工厂,参与具体线程创建工作
		RejectedExecutionHandler handler:拒绝策略,如果任务超过负荷该如何处理
    

拒绝策略:

CallerRunsPolicy:调用者负责处理(那个线程提交的任务,该线程自己处理)
AbortPolicy:超过负荷抛出异常(默认拒绝策略)
DiscardPolicy:丢弃新来的任务
DiscardOldestPolicy:丢弃最老的任务(丢弃阻塞队列中最老的任务)

自定义拒绝策略:实现RejectedExecutionHandler接口即可。

public class 自定义拒绝策略 implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        try {
            executor.getQueue().put(r);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程池工作流程:

在这里插入图片描述

public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor=
                new ThreadPoolExecutor(
                        5,//核心线程数5
                   10,//总线程数10
                      60,//线程过期时间60s
                                    TimeUnit.SECONDS,
                                    //阻塞队列最多放10个任务
                                    new ArrayBlockingQueue<Runnable>(10),
            						//不写线程工厂
                                    //拒绝策略是谁提交谁处理
                                    new ThreadPoolExecutor.CallerRunsPolicy());

        //提交25个任务,当所有线程以及阻塞队列都满了,由main线程执行后面提交被拒绝的任务
        for(int i=0 ; i<25;i++){
           threadPoolExecutor.execute(new Runnable() {
               @Override
               public void run() {
                   System.out.println("提交任务");
                   try {
                       Thread.sleep(100000000000L);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
           });
        }

    }

自定义简单线程池:

public class MyPool {

    private BlockingQueue<Runnable> blockingQueue;

    /**
     *
     * @param count 线程总数
     * @param capacity 阻塞队列容量
     */
    public MyPool(int count,int capacity) {
        this.blockingQueue = new ArrayBlockingQueue<>(capacity);
        for(int i=0;i<count;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        try {
                            //一直从阻塞队列获取任务
                            Runnable take = blockingQueue.take();
                            //执行任务
                            take.run();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                    }
                }
            }).start();
        }
    }

    //提交任务
    public void execute(Runnable runnable){
        try {
            blockingQueue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值