Java多线程之初阶

线程安全

① 什么是线程安全?
当多个线程访问某一个类(对象或方法)时,这个类能表现出正确的行为,那么这个类(对象或方法)就是线程安全的
synchronized:就是给对象或方法加锁,而加锁的代码称为“互斥区”或“临界区”
② 示例总结:
当多个线程访问同一个加锁方法时,是以线程排队的形式访问(这个排队的顺序是CPU指定的,不是代码指定的)
当一个线程想要执行synchronized方法时,必须要拿到锁才能执行,拿不到锁,这个线程会一直尝试去拿锁,直到拿到为止;
而且多个线程同时去竞争这把锁(也就会有锁竞争问题)
③ 锁竞争:当有1000个线程,去同时拿一把锁,会导致CPU急速增高,会导致系统挂掉,尽量避免锁竞争问题
如何避免锁竞争?
但作为一套与锁完全无关的线程解决方案,在高并发或竞争激烈的场景,使用ThreadLocal可以在一定程度上减少锁竞争
线程三要素:1、CPU:由OS负责调度 2、Data:堆空间共享,栈空间独立 3、code

实现线程安全的三种方法

① 阻塞同步(互斥同步):加锁,悲观,synchronized、Lock
② 非阻塞同步:CAS,乐观,Atomic
③ 不同步:ThreadLocal

多个线程多个锁

多个线程多个锁:多个线程,每个线程都能拿到指定的锁,分别获得锁之后,执行synchronized方法体的内容
示例总结:
synchronized所修饰的是对象锁,每个线程对象都有自己的锁,互不影响(对象级别锁);
如果是同一线程对象调用对象内不同的同步方法,就需要等待,等第一个方法执行完释放锁之后,第二个方法才能执行;
在synchronized之前加static那就是类锁,多个线程对象都是同一把锁(类级别锁);

对象锁的同步和异步

① 同步:synchronized,同步的概念就是资源共享,如果不是共享资源,就没有必要进行同步,也就没有必要进行加锁;多线程访问同一个方法时,会排队访问
② 异步:asynchronized,异步的概念就是独立,比如浏览页面时,可以点击按钮发送ajax请求;多线程访问同一个方法时,会并发访问
③ 同步的目的:是为了线程安全
线程安全需要满足两个特性:原子性(同步)、可见性

脏读

什么是脏读?就是数据的不一致性
示例总结:setValue加synchronized,getValue没加synchronized,a线程设值,b线程取值,至此出现数据不一致性,要同时加synchronized

synchronized锁重入

synchronized有锁重入的功能,就是当一个线程对象进入此对象的加锁方法后,在此方法内可以再次进入此对象的其他加锁方法(父子继承关系也可以)

出现异常锁会自动释放

当加锁方法出现异常,锁会自动释放,如果不及时处理,此时其他线程会进入加锁方法,会导致严重错误
解决方法:1-当出现异常时,结束此线程,throw new RuntimeException
2-当出现异常时,打断异常,catch (InterruptedException e)
3-当出现异常时,如果是在循环体中,可以continue,执行下一次
当所有的任务是一个整体的时候,应该用1和2方法解决;如果所有任务不是一个整体,不相互影响,可以使用第3个方法解决。

synchronized代码块

比如a线程执行一个很长时间的加锁方法,b线程下就必须要等待很长时间;这时就可以利用synchronized代码块,来优化代码执行的时间。
不要用String常量当做锁对象,会出现死循环,可用new String(”“)的形式;另一种情况是,不要在方法内修改String常量值(如果是一个Person对象,修改其中的age属性,是可以的)

解决指令重排序,而导致程序输出错误结果

1- 就是加锁,加锁后代码就是单线程了,单线程下的指令重排序是不会导致结果错误的(因为as-if-serial语义,所以单线程不会出现错误结果)
2- volatile,禁止指令重排序
3- final,不可修改变量
为什么会有指令重排序问题?
是因为编译器和CPU为了代码的执行效率,可能会进行指令重排序

volatile关键字

作用:修饰变量关键字(就是说变量在多个线程间实时共享)
作用:使变量在多个线程可见,但是没有同步功能,修饰变量关键字,禁止了指令重排序
没有volatile之前,使用synchronized加锁来实现,变量一致性
那synchronized和volatile区别?
volatile是实时共享,不造成阻塞
synchronized需要先获取锁,造成阻塞
volatile只能用于多线程变量可见操作,而不能代替synchronized的同步功能(原子性)
volatile只能应用于变量级别,而synchronized修饰方法或者代码块、
volatile是轻量级的synchronized,性能要比synchronized强很多,不会造成阻塞;
Atomic类系列对象支持原子性,注意:Atomic类系列对象只支持一次方法的原子性操作,不能支持多次方法的原子性操作
synchronized(效率低) = volatile + Atomic类系列对象(效率高)
在java(JDK1.5以后)中,每个线程都有自己的工作内存区,其中存放着,共享内存(所有线程共享的)中变量值得拷贝;也就是当你不加volatile关键字时,只会操作自己工作内存的变量,加了volatile之后,会强制线程去主内存中读取
当线程执行时,他在自己的内存中操作这些拷贝的变量;
一个线程如何存取一个共享变量?
一个线程通常先去获取锁,再去清除自己的内存工作区,把这些共享变量,从共享内存中正确的装入到自己的内存工作区中,当线程解锁时,将值写到共享内存
一个线程可以执行的操作:使用(use)赋值(assign)装载(load)存储(store)锁定(lock)解锁(unlock)
主内存(共享内存)可以执行的操作:读(read)写(write)锁定(lock)解锁(unlock)每个操作都是原子操作volatile作用?
强制线程去主内存(共享内存)中读取变量数据,而不去自己的内存读取,从而实现多线程变量可见,也就满足了线程安全的可见性
还有一种使线程间变量共享的方案,就是给这个变量加锁,但是这种方式效率低。
volatile只具备可见性,不具备原子性;
AtomicInteger具备原子性,Atomic……类
这里写图片描述

线程间通信

1- 什么是线程通信?
线程是操作系统 独立的个体,不能成为一个整体,而线程间通信就是就是成为整体的方式之一
wait和notify实现线程通信,必须配合synchronized
2- 如何实现实时通信?
但是notify没有实现实时通知;
此时需要用到CountDownLatch类,countDownLatch.countDown(通知)、countDownLatch.await(等待);
CountDownLatch类不需要配合synchronized

ThreadLocal

线程局部变量,是一种多线程间,并发访问变量的解决方案;
与synchronized 加锁的方式不同,ThreadLocal完全不提供锁,而使用空间换时间的方式,为每个线程提供变量的独立副本,以保障线程安全。
从性能上说,ThreadLocal不具有绝对优势;在并发不是很高的时候,加锁的性能会更好;
但作为一套与锁完全无关的线程解决方案,在高并发或竞争激烈的场景,使用ThreadLocal可以在一定程度上减少锁竞争
ThreadLocal实现原理:是map实现的,每个线程对应一个key(线程对象)和value(变量副本),每次子线程操作变量,都会与主内存同步
由于ThreadLocal是每个线程对应一个key和value,所以说是高并发时,可以避免锁竞争

单例模式+多线程

static inner class 或者 double check instance
这里写图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值