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

来源:互联网 发布:txt转mobi for mac 编辑:程序博客网 时间:2024/06/04 19:45
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

0 0
原创粉丝点击