java多线程(二)之线程安全和线程同步

多线程提高了程序的性能,但是也引发了安全问题。例如多线程交替执行时,执行顺序的不同可能得出不同的结果,这不是我们想得到的结果,因此,如何保证线程安全是程序开发中经常遇到的问题。

1.what is 线程安全?

当多个线程访问一个对象时,如果不考虑这些线程在运行时环境的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。代码本身封装了所有必要的正确性保障手段(互斥同步等),令调用者无需关心多线程的问题,更无需自己实现任何措施来保证多线程的正确调用。


2.如何实现线程安全?

(1)互斥同步,最常见的并发正确性保障手段,同步至多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个线程使用。互斥同步的主要问题就是进行线程的阻塞和唤醒所带来的性能问题,因此这个同步也被称为阻塞同步,阻塞同步属于一种悲观的并发策略,认为只要不去做正确的同步措施,就肯定会出问题,无论共享的数据是否会出现竞争。
(2)非阻塞同步,随着硬件指令的发展,有了另外一个选择,基于冲突检测的乐观并发策略,通俗的讲就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了,如果共享数据有争用,产生了冲突,那就再进行其他的补偿措施(最常见的措施就是不断的重试,直到成功为止),这种策略不需要把线程挂起,所以这种同步也被称为非阻塞同步。
(3)无同步方案,简单的理解就是没有共享变量需要不同的线程去争用,目前有两种方案,一个是“可重入代码”,这种代码可以在执行的任何时刻中断它,转而去执行其他的另外一段代码,当控制权返回时,程序继续执行,不会出现任何错误。一个是“线程本地存储”,如果变量要被多线程访问,可以使用volatile关键字来声明它为“易变的“,以此来实现多线程之间的可见性。同时也可以通过ThreadLocal来实现线程本地存储的功能,一个线程的Thread对象中都有一个ThreadLocalMap对象,来实现KV数据的存储。


3.线程同步

线程同步是线程安全访问竞争资源的一种手段。

线程同步其实就是对线程进行“排队”,当多个线程同时对一个资源进行操作时,应进行排队,而不是同时对资源进行操作。

放到代码中去考虑,多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码。无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。

同步的使用可以保证在多线程运行的环境中,程序不会产生设计之外的错误结果。


4.线程同步的方式

(1)同步方法synchronized

即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

给一个方法增加synchronized修饰符之后就可以使它成为同步方法,这个方法可以是静态方法和非静态方法,但是不能是抽象类的抽象方法,也不能是接口中的接口方法。

(2)同步代码块

即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。

(3)wait和notify

wait():使一个线程处于等待状态,并且释放所持有的对象的lock。

notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

notifyAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

(4)volatile特殊域变量

可见性:对一个volatile变量的读,任意线程对这个volatile变量都是最后写入。

原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。

(5)使用重入锁

ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized关键字具有相同的基本行为和语义,并且扩展了其能力。

(6)使用原子变量实现同步

原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作,即这几种行为要么同时完成,要么都不完成。在java的util.concurrent.atomic包中提供了创建原子类型变量的工具类。例如AtomicInteger等。

(7)使用executor实现同步

Java中excutor只是一个接口,但它为一个强大的同步框架做好了基础,其实现可以用于异步任务执行,支持很多不同类型的任务执行策略。excutor框架适用于生产者-消费者模式,是一个非常成熟的框架

(8)使用JUC包下的同步容器类实现同步

采用java.util.concurrent包下的相关类实现同步,例如LinkedBlockingQueue,ConcurrentHashMap,CopyOnWriteArrayList、LinkedBlockingDeque等。

(9)使用信号量Semaphore实现同步

信号量的意思就是设置一个最大值,来控制有限个对象同时对资源进行访问。因为有的时候有些资源并不是只能由一个线程同时访问的,举个例子,我这儿有5个碗,只能满足5个人同时用餐,那么我可以设置一个最大值5,线程访问时,用acquire() 获取一个许可,如果没有就等待,用完时用release() 释放一个许可。这样就保证了最多5个人同时用餐,不会造成安全问题,这是一种很简单的同步机制。

(10)使用ThreadLocal实现同步

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

ThreadLocal与前面同步机制不同:

a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 

b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式


结束:线程同步是为了保证线程安全,具体每种线程方式的具体使用和底层原理将在后续章节进行详细学习和总结。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值