线程安全,锁,线程池简单分析

线程安全简单分析

成员变量和静态变量是否线程安全?

1.如果没有共享,线程安全

2.如果被共享,根据他们是否能够改变

如果只有读,线程安全

如果有读写操作,则这段代码是临界值,需要考虑线程安全

局部变量是否线程安全?

是安全的

但是局部变量引用未必安全

  • 如果该对象没有逃出方法的作用访问,是线程安全的

  • 如果该对象逃出方法的作用范围,需要考虑线程安全

分析i++线程是否安全?

结论:

很显然不是线程安全的

分析:

使用 javac xxxxxx.java 指令来编译

使用 javap -c xxxxx.class 指令来反编译刚才得到的字节码文件

分析i++的过程

	   0: aload_0
       1: dup
       2: getfield      #2                  
       5: iconst_1
       6: iadd
       7: putfield      #2                  
       10: return
  • getfield:从内存中获取变量 count 的值
  • iadd:将 count 加 1
  • putfield:将加 1 后的结果赋值给 count 变量

可见i++不是由一条指令执行所,这也就是线程不安全的原因所在,因为 count++ 操作不具备原子性。

解决:

使用volatile 和 synchronized

volatile 在并发编程中保证了共享变量的 “可见性”,可见性的意思是当一个线程修改一个共享变量时,另外一个线程立即能读到这个修改的值。

使用 synchronized 进行加锁可以保证该计数器线程安全,因为只有一个线程能取得锁去执行 count++,其他线程必须等待锁的获取。

常见的线程安全的类
  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • concurrent包下面

这里所说的线程安全是多个线程同时使用一个实例的某个方法时,线程是安全的。也可以理解为

  • 它们每个方法都是原子的

  • 注意他们的很多个方法组合不是原子的

Hashtable table = new Hashtable();
//线程1 线程2
if(table.get("key") == null){
	table.put("key",value);
}
线程1 线程2 table get("key") == null get("key") == null put("key",vi) put("key",v2) 线程1 线程2 table
线程是否安全案例
例1.

在这里插入图片描述

1.不安全 Map线程不安全

2.安全 线程安全类

3.安全 线程安全类

4.不安全 date不属于线程安全类

5.不安全 final只是保证了引用唯一,但是不能保证年月日的修改

例2

在这里插入图片描述

Servlet是单例的只有一份,则userService被多个线程共享,所以其中的count是被共享的。如果需要count++,需要对于临界代码块的保护

例3

在这里插入图片描述

这是一个用来记录一个方法执行多久,使用前后通知时间来得差值。很明显是线程不安全的如果不加@Scope默认都是单例的,既然单例则被共享,对象对于成员变量的修改就会出现问题。

解决办法,使用环绕通知,将开始和结束时间做成环绕通知的局部变量,如此保证线程安全

例4

在这里插入图片描述

此处都是线程安全的,Dao中没有成员变量,没有成员变量的类一般都是线程安全的,Connection属于局部变量,每个线程都创建在自己的栈内存。所以线程安全

ServiceImpl属于无状态,没有成员变量

UserService虽然有成员变量但是他是私有的,不可变所以我们注入的时候就需要使用private

例5

懒汉式,饿汉式哪个是线程安全的

线程活跃性
活锁

活锁出现在两个线程相互改变对方的结束条件,最后谁也无法结束

在这里插入图片描述

死锁

线程一拥有对象A的锁,线程二拥有对象B的锁,线程相互等待对方释放资源,此时线程阻塞出现假死状态,形成死锁。

饿锁

一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。多线程中优先级高的会优先执行,并且抢占优先级低的资源,导致优先级低的线程无法得到执行。

与死锁不同的是,饥饿锁在一段时间内,优先级低的线程最终还是会执行的,比如高优先级的线程执行完之后释放了资源。

ReentranLock与synchronize区别
  • 可中断

  • 可以设置超时时间

  • 可以支持多个条件变量

    都是可重入锁

可打断锁

在这里插入图片描述

使用interrupt打断

synchronize,ReentrantLock都是非公平锁。

ReentrantLock可以设置公平非公平,阻塞队列中顺序进行,一般不会设置成公平锁这样会降低并发。

ReentrantLock可以使用TryLock尝试获取锁。

应用方面

  • 互斥:使用synchronized或者Lock达到共享资源互斥的效果
  • 同步:使用wait/notify或者Lock条件变量来达到线程通信效果
线程池:

线程池状态ThreadPoolExecutor使用int的高3位来表示线程池状态,低29位表示线程数量

为什么要存储在一个原子变量中?目的是将线程池状态与线程个数合二为一,这样就可以用一次cas原子进行赋值

线程池参数

在这里插入图片描述

线程池刚开始是没有线程的,有任务后才会创建

救急线程创建条件:

​ 必须选择有界队列,才会有救急线程

执行流程:

首先Task进入创建核心线程 -----核心线程满了

其次Task进入阻塞队列 -----阻塞队列满了

再次Task被救急线程处理 ----救急线程不够用

最后执行拒绝策略

如果救急线程使用结束后,过期时间是专门针对救急线程的。

newFixedThreadPool

在这里插入图片描述

  • 核心线程数=最大线程数
  • 使用的是无界队列
使用场景

任务量已知,比较耗时的任务

newCachedThreadPool

在这里插入图片描述

  • 全部创建的都是救急线程,时间为60s后可以回收,无界队列
  • 队列采用了SyncjronousQueue实现特点是,没有容量,没有线程来取是不放进去的
使用场景

适合任务数比较密集,但是每个任务时间比较短

newSignleThreadExecutor

在这里插入图片描述

使用场景

执行多个任务是串行的

newFixedThreadPool(1)newSignleThreadExecutor有什么区别
  • 自己创建一个单线程串行执行任务,如果任务执行失败而终止呢么没有任何的补救措施,而线程池会新建一个线程,保证其正常工作
  • sign的线程数始终为1不能被改变。返回值不同,sign包装了一下返回,这样不可以调线程的其他方法
  • fixed初始值为1之后是可以修改的,对外暴露ThreadPool对象,强转后调用setCorePoolSize方法修改
线程池提交任务

在这里插入图片描述

线程池创建多大合适

过小导致不能充分发挥系统资源,容易导致饥饿

过大导致线程上下文切换,占更多内存

cpu密集型运算

数据分析类 cpu+1个线程。保障操作系统的页确实故障

io密集型运算

在这里插入图片描述

Timer任务调度线程

简单易用但是都是由一个线程来调度,因此所有任务都是串行执行的,同一时间只有一个任务执行,有一个任务延时或者异常都会影响后面的任务

任务调度线程池ScheduledThreadPoll
 public static void main(String[] args) {

        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        pool.schedule(()->{
            System.out.println("aaaa");
            int a = 1/0;
        },1, TimeUnit.SECONDS);


        pool.schedule(()->{
            System.out.println("bbb");
        },1, TimeUnit.SECONDS);
    }

解决了之前的问题,而且遇到了异常还能执行剩下的任务

循环执行
        System.out.println("start");
        pool.scheduleAtFixedRate(()->{
            System.out.println("开始执行");
         try {
                sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },1,1,TimeUnit.SECONDS);

四个参数分别是:执行方法体,第一次间隔多久执行,执行间隔,时间单位。

执行方法时只会间隔两秒

System.out.println("start");
        pool.scheduleWithFixedDelay(()->{
            System.out.println("开始执行");
            try {
                sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },1,1,TimeUnit.SECONDS);

四个参数分别是:执行方法体,第一次间隔多久执行,执行间隔,时间单位

执行方法时会间隔三秒,以第一次执行结束时开始

如何捕捉异常

自己捕捉异常try catch。返回future对象当get()方法的时候就会将异常打印

指定时间执行
/**
     *   每周四 14:35执行
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {

        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        long time = 1000 * 60 * 60 * 24 * 7;
        System.out.println("now"+now);
        // 时间修改为指定时间
        LocalDateTime localDateTime = now.withHour(14).withMinute(35).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
        System.out.println("localDateTime"+localDateTime);
        if(now.compareTo(localDateTime)>0){
            // 如果周五则加一周
            localDateTime.plusWeeks(1);
        }
        // 时间相减
        long l = Duration.between(now, localDateTime).toMillis();
        System.out.println("l"+l);
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        pool.scheduleAtFixedRate(()->{
            System.out.println("执行");
        },l,time,TimeUnit.MILLISECONDS);

    }
AQS
     localDateTime.plusWeeks(1);
    }
    // 时间相减
    long l = Duration.between(now, localDateTime).toMillis();
    System.out.println("l"+l);
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
    pool.scheduleAtFixedRate(()->{
        System.out.println("执行");
    },l,time,TimeUnit.MILLISECONDS);

}

##### AQS

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值