多线程编程——线程同步方法

1、五种方式

1.1 synchronized同步方法

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

如:public synchronized void save(){}

注:当synchronized关键字修饰静态方法时,会锁住整个类

1.2 synchronized同步代码块

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

如:synchronized(object){}

注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

1.3 volatile

  1. volatile关键字为域变量的访问提供了一种免锁机制,
  2. 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
  3. 因此每次使用该域就要重新计算,而不是使用寄存器中的值
  4. volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

注:用来对共享变量的访问进行同步,上一次写入操作的结果对下一次读取操作是肯定可见的。(在写入volatile变量值之后,CPU缓存中的内容会被写回内存;在读取volatile变量时,CPU缓存中的对应内容会被置为失效,重新从主存中进行读取),volatile不使用锁,性能优于synchronized关键词。

1.4 重入锁ReentrantLock

a. 概念及使用

JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力

ReentrantLock类的常用方法有:

  • ReentrantLock(): 创建一个ReentrantLock实例
  • lock(): 获得锁
  • unlock(): 释放锁

注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

b. 关于Lock对象和synchronized关键字的选择:

  • 最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。
  • 如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码
  • 如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁

1.5 局部变量

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

ThreadLocal类的常用方法

  • ThreadLocal(): 创建一个线程本地变量
  • get(): 返回此线程局部变量的当前线程副本中的值
  • initialValue(): 返回此线程局部变量的当前线程的"初始值"
  • set(T value): 将此线程局部变量的当前线程副本中的值设置为value

注:ThreadLocal与同步机制

  • ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。
  • 前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式

2、synchronized

2.1 四种方式

  1. 方法声明时使用,线程获得的是成员锁.
  2. 对某一代码块使用,synchronized后跟括号,括号里是变量,线程获得的是成员锁.
  3. synchronized后面括号里是一对象,此时,线程获得的是对象锁.
  4. synchronized后面括号里是类,此时,线程获得的是对象锁.

2.2 使用特点

  1. synchronized关键定只是用来提供在多线程环境中对java共享资源的互斥访问。它保证了使用相同锁的多个线程的同步问题,
  2. synchronized关键定只能用在synchronized方法或者synchronized块中,不能对变量进行synchronized操作
  3. 当线程进入synchronized时,它必须获得相应的锁,而当它离开时,要释放对应的锁。锁会在线程完成同步或者是出现错误或异常时释放。
  4. 当Java Thread进行一个syncrhonized的Java方法时,会要求获得对象级别的锁;而当进入Static synchronized方法时,需要获得类级别的锁。
  5. Java Synchronized关键字是可以重入的。
  6. 如果java synchronized块同步的对象是null的话,JAVA会抛出NullPointerException异常。
  7. synchronized只能在同一个JVM上实现同步。
  8. 使用synchronized会造成performance的下降。
  9. Java synchronized block要优于synchronized method
  10. 在Java5之后,使用violate的变量都是原子的。
  11. synchronized不能用在构造函数中,它会造成编程的错误。
  12. synchronized不能用变量上
  13. Java类库Calendar和SimpleDataFormat不是线程安全的,所以需要另外的同步机制。

http://blog.csdn.net/php_boy/article/details/6778672

3、volatile

多线程线程并发操作时,保证内存中共享变量时时处于最新修改状态

3.1 volatile关键字的两层语义

  一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

  1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  2. 禁止进行指令重排序。

什么是指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。

3.2 使用场景

synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:

  1. 对变量的写操作不依赖于当前值
  2. 该变量没有包含在具有其他变量的不变式中

  实际上,这些条件表明,可以被写入volatile变量的这些有效值独立于任何程序的状态,包括变量的当前状态。

  事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。

4、ThreadLocal

4.1 使用特点

ThreadLocal使用场合主要解决多线程中数据因并发产生不一致问题。

ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。

4.2 使用步骤

  1. 在多线程的类(如ThreadDemo类)中,创建一个ThreadLocal对象threadXxx,用来保存线程间需要隔离处理的对象xxx。
  2. 在ThreadDemo类中,创建一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型。
  3. 在ThreadDemo类的run()方法中,通过getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。

5、volatile与synchronized

  1. volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
  2. volatile仅能使用在变量级别;synchronized则可以使用在变量、方法.
  3. volatile仅能实现变量的修改可见性,但不具备原子特性;而synchronized则可以保证变量的修改可见性和原子性.
  4. volatile不会造成线程的阻塞;而synchronized可能会造成线程的阻塞.
  5. volatile标记的变量不会被编译器优化;而synchronized标记的变量可以被编译器优化.

  总结:volatile本质是在告诉JVM当前变量在寄存器中的值是不确定的,需要从主存中读取。可以实现synchronized的部分效果,但当n=n+1,n++等时,volatile关键字将失效,不能起到像synchronized一样的线程同步的效果。

6、ReentrantLock与synchronized

6.1 异同点

相同:ReentrantLock提供了synchronized类似的功能和内存语义。

不同:

  1. ReentrantLock功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等,因此更有扩展性。在多个条件变量和高度竞争锁的地方,用ReentrantLock更合适,ReentrantLock还提供了Condition,对线程的等待和唤醒等操作更加灵活,一个ReentrantLock可以有多个Condition实例,所以更有扩展性。
  2. ReentrantLock必须在finally中释放锁,否则后果很严重,编码角度来说使用synchronized更加简单,不容易遗漏或者出错。
  3. ReentrantLock 的性能比synchronized会好点。
  4. ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁。

6.2 使用特点:

  1. Lock的某些方法可以决定多长时间内尝试获取锁,如果获取不到就抛异常,这样就可以一定程度上减轻死锁的可能性。
  2. 如果锁被另一个线程占据了,synchronized只会一直等待,很容易错序死锁
  3. synchronized的话,锁的范围是整个方法或synchronized块部分;而Lock因为是方法调用,可以跨方法,灵活性更大 
  4. 便于测试,单元测试时,可以模拟Lock,确定是否获得了锁,而synchronized就没办法了

6.3 ReentrantLock比synchronized 强大在哪儿?

简单说:

1、ReentrantLock可以实现fair lock

1 public ReentrantLock(boolean fair) {
2    sync = (fair)? new FairSync() : new NonfairSync();
3 }

所谓fair lock就是看获得锁的顺序是不是和申请锁的时间的顺序是一致的

2、ReentrantLock支持中断处理

 1 public final void acquireInterruptibly(int arg) throws InterruptedException {
 2     if (Thread.interrupted())
 3         throw new InterruptedException();
 4     if (!tryAcquire(arg))
 5         doAcquireInterruptibly(arg);
 6 } 

就是说那些持有锁的线程一直不释放,正在等待的线程可以放弃等待。

3、ReentrantLock可以和condition结合使用

 1 public boolean hasWaiters(Condition condition) {
 2     if (condition == null)
 3         throw new NullPointerException();
 4     if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
 5         throw new IllegalArgumentException("not owner");
 6     return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject) condition);
 7 }
 8
 9 public int getWaitQueueLength(Condition condition) {
10     if (condition == null)
11         throw new NullPointerException();
12     if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
13         throw new IllegalArgumentException("not owner");
14     return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject) condition);
15 }

7、ThreadLocal与synchronized:

  • synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
  • Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。

http://www.2cto.com/kf/201408/324061.html

http://www.cnblogs.com/dolphin0520/p/3920373.html

http://my.oschina.net/songhongxu/blog/197925

时间: 2024-10-14 13:01:49

多线程编程——线程同步方法的相关文章

Linux程序设计学习笔记----多线程编程线程同步机制之互斥量(锁)与读写锁

互斥锁通信机制 基本原理 互斥锁以排他方式防止共享数据被并发访问,互斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个互斥锁逻辑上绑定之后,对该资源的访问操作如下: (1)在访问该资源之前需要首先申请互斥锁,如果锁处于开状态,则申请得到锁并立即上锁(关),防止其他进程访问资源,如果锁处于关,则默认阻塞等待. (2)只有锁定该互斥锁的进程才能释放该互斥锁. 互斥量类型声明为pthread_mutex_t数据类型,在<bits/pthreadtypes.h>中有具体的定义. 互斥量

多线程编程-----线程同步

同步,永远是多线程编程中最核心和最重要的话题.同步相关的概念比如:临界区,原子操作,以及互斥量等等 总的来说,在多个线程之间采取同步措施,无非是为了让他们更好的协同工作或者维持共享数据的一致性. 1.共享数据的一致性: 多线程程序多以共享数据作为在线程之间传递数据的手段,由于一个进程所拥有的相当一部分虚拟内存地址都可以被该线程中的所有线程所共享.因此这些被共享的数据大多也已内存空间作为载体. 实际上,保证共享数据一致性的最简单且最好的方法,就是使得该数据成为一个常量,但是如果把一个计数器做成常量

VC++多线程编程-线程间的通信和线程同步

引用:http://blog.csdn.net/zjc0888/article/details/7372258 线程间通讯 一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程和次要线程间要进行通信.这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的,下面将进行说明. 使用全局变量进行通信 由于属于同一个进程的各个线程共享操作系统分配该进程的资源,故解决线程间通信最简单的一种方法是使用全局变量.对于标准类型

Linux多线程编程——线程的同步与互斥

前言:无论是多线程编程还是多进程编程,控制好不同线程或不同进程之间同步和互斥问题是非常有必要的.同步是多个进程或线程共同完成某个任务,举例说,一个缓冲区的生产者和消费者问题,当生产者生产了一个商品时,等待的消费者就获得了一个消息知道可以去取走商品了,当消费者取走一个商品后,生产者就知道可以继续生产一个商品了,这是同步问题,所谓互斥问题,是指某个共享资源在一次操作中,只能被一个线程或进程占有,其他的线程或进程不能对它进行操作,比如对一个共享内存的读写操作,当一个进程对它写的时候,另一个进程就不能对

多线程编程—线程池的实现

执行与任务分离的组件- 线程池 多线程技术主要解决了处理器单元内多个线程执行的问题,它可以显著的减少处理器单元的闲置时间,增加处理器单元的吞吐能力.线程池是多线程编程的一个必要组件,并且对于很多编程人员都是透明的,更是神秘的.有幸能为大家解析其中缘由,尚有不妥之处,欢迎大家抛砖. 线程池的概念,是一个用来管理一组执行任务线程的工具.既然是管理工具,那么该工具管理是用来管理任务与执行的.如图一线程池组件拓扑图,执行队列(Workers),任务队列(Jobs)和池管理(Pool Manager)三部

Linux下简单的多线程编程--线程池的实现

/* 写在前面的话: 今天刚“开原”,选择了一篇关于线程池的文件与大家分享,希望能对您学习有所帮助,也希望能与大家共同学习! 选择在这个特殊的时候注册并发文章也是有一些我个人特殊的意义的,看我的id(西游小学生.45)就知道了,哈哈.在这里也很感谢博客园的员工,刚发申请两分钟就同意了. */ 最近由于要写一个类似于QQ的程序,所以想到要用到多线程.既然要用多线程,那何不写一个线程池?于是上网搜了搜多线程的代码,发现大多都不是很完善,或者有些小bug.所以,在这里贴出一个完整的,经过我多重测试的,

多线程编程——线程同步与异步

1.多线程和异步操作的异同 多线程和异步操作两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性.甚至有些时候我们就认为多线程和异步操作是等同的概念.但是,多线程和异步操作还是有一些区别的.而这些区别造成了使用多线程和异步操作的时机的区别. 2.异步操作的本质 所有的程序最终都会由计算机硬件来执行,所以为了更好的理解异步操作的本质,我们有必要了解一下它的硬件基础. 熟悉电脑硬件的朋友肯定对DMA这个词不陌生,硬盘.光驱的技术规格中都有明确DMA的模式指标,其实网卡.声卡.显卡也是有DMA

java多线程编程——线程同步之同步函数

如何找出线程安全问题: 1.明确那些代码块是多线程运行代码 2.明确共享数据 3.明确多线程运行代码中哪些语句是操作共享数据的 同步函数示例: class Save{ private int sum; public synchronized void add(int n){ sum+=n; System.out.println("sum="+sum); } } class Cus implements Runnable{ private Save b=new Save(); publi

Linux 多线程编程--线程退出

今天分析项目中进程中虚存一直增长问题,运行10个小时虚存涨到121G ,RSS占用为16G 非常恐怖. 顺便查了下Linux单进程能创建线程的上限,以及相关内容.内存32G 64bit系统信息如下: Linux线程使用方式是主进程依据请求的多少动态创建和退出线程.通过pmap -x pid查看进程内个部分内存分配情况: 发现大量如下占用: 通过查询可知[anon]为未实际分配的内存,即虚存:通过如下命令计算出一共有11946个,每个对应10M总大小为 119460≍119G 1 cat proc