首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

多线程调优经验-并发控制方法

写在前面

◆ ◆ ◆ ◆

并行程序开发将不可避免地要涉及多线程、多任务间的写作和数据共享等问题。在JDK中,提供了多种途径实现多线程间的并发控制。常用的方法有:内部锁、重入锁、读写锁、信号量等

Java内存模型与volatile

◆ ◆ ◆ ◆

Java中每一个线程有一块工作内存区,其中存放着被所有线程共享的主内存中的变量的值的拷贝。当线程执行时,它在自己的工作内存中操作着这些变量。为了存取一个共享的变量,一个线程通常先获取锁定并且清除它的工作内存区,这保证该共享变量从所有线程的共享内存区正确地装入到线程的工作内存区,当线程解锁时保证该工作内存区中变量的值写回到共享内存中。

一个线程可以执行的操作有使用(user),赋值(assign),装载(load),存储(store),锁定(lock),解锁(unlock)。而主内存可以执行的操作有读(read),写(write),锁定(lock),解锁(unlock),每一个操作都是原子的。如下图:

当一个线程使用某一个变量时,不论程序是否正确地使用线程同步操作,它获取的值一定时由它本身或者其他线程存储到变量中的值。例如,如果两个线程把不同值或者对象引用存储到同一个共享变量中,那么该变量的值要么是这个线程的,要么是另一个线程的,共享变量的值不会由两个线程的引用值组合而成(除long,double外)。

一个变量是Java程序可以存取的一个地址,它不仅包括基本类型变量、引用类型变量,还包括数据类型变量。保存在主内存区的变量可以被所有线程共享,但一个线程存取另一个 线程的参数或者局部变量是不可能的。

由于每个线程都有自己的工作内存区,因此当一个线程改变自己的工作内存中的数据时,对其他线程来说,可能是不可见的。因此,可以使用volatile关键字迫使所有线程均读写主内存中的对应变量,从而使得volatile变量在多线程间可见。

声明volatile的变量可以做到如下保证:

其他线程对变量的修改,可以即时反应在当前线程中。

确保当前线程对volatile变量的修改,能即时写回共享主内存中,并被其他线程所见。

使用volatile声明的变量,编译器会保证其有序性。

同步关键字synchronized

◆ ◆ ◆ ◆

同步关键字synchronized使用简洁,代码可维护性好。在JDK6中,性能也比早期的JDK由很大改进,如果可以满足程序要求,可以首先考虑这种同步方式。

synchronized最常用的方法是锁定一个对象的方法;也可以同步块,与同步方法相比,可以更为精确地控制代码的范围,有利于锁的快进快出,提高吞吐量。

当synchronized用于static函数时,相当于将锁加到当前Class对象上,因此,所有对该方法的调用,都必须获得Class对象的锁。

ReentrantLock重入锁

◆ ◆ ◆ ◆

ReentrantLock比内部锁synchronized拥有更强大的功能,它可以中断,可定时。JDK5中,在高并发的情况下,它比synchronized有明显的性能优势。在JDK6中,由于JVM的优化,两者差别不是很大。

ReentrantLock还提供了公平和非公平的两种锁。公平锁可以保证锁的等待队列中的各个线程是公平的,不会出现插队的情况,对锁的获取总是先进先出,而非公平的就不做这个保证,申请锁的线程可以插队。公平锁的实现代价比非公平的大,因此从性能上,非公平锁的性能要好得多。因此若无特殊的需求,应该优先考虑非公平锁。

使用ReentrantLock时,一定要牢记,在程序最后释放锁。一般释放锁的代码要写在finally里,否则如果程序出现异常,锁将无法释放了。相比synchronized,JVM总是会在最后自动释放synchronized锁。

ReadWriteLock读写锁

◆ ◆ ◆ ◆

ReadWriteLock读写锁是JDK5中提供的读写分离锁。读写分离锁可以有效地帮助减少锁竞争,以提高系统性能。

比如线程A1,A2,A3进行写操作,B1,B2,B3进行读操作,如果市容重入锁或者内部锁,则理论上所有读之间、读写之间、写写之间都是串行操作。当A1进行读取时,A2,A3则需要等待锁。由于读操作并不对数据的完整性造成破坏,这种等待显然是不合理的。因此读写锁就有发挥的余地。这种情况下,读写锁允许多个线程同时读,使得B1,B2,B3之间真正并行。但是考虑到数据完整性,写写操作和读写操作间依然需要互相等待和持有锁。

如果在系统中,读操作的次数远远大于写操作,则读写锁可以发挥最大的功效。

Condition对象

◆ ◆ ◆ ◆

线程间的协调工作光有锁是不够的,在业务层,可能会有复杂的线程间写作的逻辑。Conditon对象就可以用于协调多线程的复杂协作。

Conditon是与锁相关联的。通过Lock接口的Conditon newConditon()方法可以生成一个与锁绑定的Conditon实例。Conditon对象和锁的关系,就如同Object.wait()、notify()两个函数和synchronized关键字一样,它们可以配合使用以完成对多线程的协调控制。

Semaphore信号量

◆ ◆ ◆ ◆

信号量为多线程写作提供了更多强大的控制方法。广义上说,信号量是对锁的扩展。无论是内部锁synchronized还是重入锁ReentrantLock,一次都只允许一个线程访问一个资源,而信号量却可以指定多个线程同时访问某一个资源。

ThreadLocal线程局部变量

◆ ◆ ◆ ◆

ThreadLocal线程局部变量是一种多线程间并发访问变量的解决方案。与synchronized等加锁的方法不同,ThreadLocal完全不提供锁,而使用以空间换时间的手段,为每个线程提供变量的独立副本,以保证线程安全,因此它不是一种数据共享的解决方案。

从性能上看,ThreadLocal并不具有绝对的优势,在并发量不是很高时,也许加锁的性能可能会更好。但是作为一套与锁无关的线程安全按解决方案,在高并发量或者锁竞争激烈的场合,使用ThreadLocal可以在一定程度上减少竞争。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200222A004XQ00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券