在 Java 语言的并发编程中,由于我们不知道线程实际上在何时运行,所以在实际多线程编程中,如果两个线程访问相同的资源,那么由于线程运行的不确定性便会在这种多线程中产生访问错误。所以为了避免这一情况的发生,我们在编程的时候需要把并发执行的线程中用于访问这一共享资源的方法进行同步处理,以避免并发对于共享资源产生的影响。
并发模式在解决线程冲突的问题时,基本上都是采用序列化访问共享资源的方案。这在我的理解中,就是我们要控制同一时刻只能让一个线程对这一共享资源进行访问。
Java 语言中,每一个对象都含有单一的锁(监视器)。而 synchronized 的作用之一就是修饰使用了共享资源的成员方法,这样在线程通过对象调用该方法时,该对象都会被加锁。这时候如果需要调用该对象的另一个 synchronized 方法,则需要在第一个方法调用完毕后再进行,这就实现了最基本的同步。
例 1:使用 synchronized 修饰方法和未修饰方法的区别
(1)使用 synchronized 修饰过的方法,在多线程执行的过程中,程序依次输出递增 3 的数字
- 1 import java.util.concurrent.ExecutorService;
- 2 import java.util.concurrent.Executors;
- 3 4 public class Synchronization implements Runnable {
- 5 private static int currentCount = 0;
- 6 synchronized void printAdd() {
- 7 currentCount++;
- 8 Thread.yield();
- 9 currentCount++;
- 10 Thread.yield();
- 11 currentCount++;
- 12 System.out.println(currentCount);
- 13
- }
- 14@Override 15 public void run() {
- 16 printAdd();
- 17
- }
- 18 public static void main(String[] args) {
- 19 ExecutorService exec = Executors.newCachedThreadPool();
- 20 Synchronization test = new Synchronization();
- 21
- for (int i = 0; i < 100; i++) {
- 22 exec.execute(test);
- 23
- }
- 24 exec.shutdown();
- 25
- }
- 26
- }
(2)与之相对应的未用 synchronized 修饰过的方法,在多线程执行的过程中,程序会输出没有规律的数字
- 1 import java.util.concurrent.ExecutorService;
- 2 import java.util.concurrent.Executors;
- 3 4 public class Synchronization implements Runnable {
- 5 private static int currentCount = 0;
- 6 void printAdd() {
- 7 currentCount++;
- 8 Thread.yield();
- 9 currentCount++;
- 10 Thread.yield();
- 11 currentCount++;
- 12 System.out.println(currentCount);
- 13
- }
- 14@Override 15 public void run() {
- 16 printAdd();
- 17
- }
- 18 public static void main(String[] args) {
- 19 ExecutorService exec = Executors.newCachedThreadPool();
- 20 Synchronization test = new Synchronization();
- 21
- for (int i = 0; i < 100; i++) {
- 22 exec.execute(test);
- 23
- }
- 24 exec.shutdown();
- 25
- }
- 26
- }
与对象相同,Java 的每个类也有一个锁,所以我们可以通过将静态方法用 synchronized 修饰来控制其对于静态共享资源的访问。
在上面的利用 synchronized 进行同步的描述中,我们都是利用方法所在对象自身的锁来进行同步。除了这种方法之外,我们还可以用 Java 语言中内置的锁对象来进行显式的加锁。
Lock 接口,便是 Java 语言在 java.util.concurrent.locks 包中为我们提供的显式锁。目前在该包中有三个 Lock 的实现(基于 JDK 1.7),分别为,,。
Lock 对象必须在程序中被显式的创建、锁定和释放。
例 3:使用 Lock 实现多线程之间的同步
- 1 import java.util.concurrent.ExecutorService;
- 2 import java.util.concurrent.Executors;
- 3 import java.util.concurrent.locks.Lock;
- 4 import java.util.concurrent.locks.ReentrantLock;
- 5 6 public class LockTest implements Runnable {
- 7 8 private static int currentCount = 0;
- 9 Lock lock = new ReentrantLock();
- 10 void addCount() {
- 11 lock.lock();
- 12
- try {
- 13 currentCount++;
- 14 Thread.yield();
- 15 currentCount++;
- 16 Thread.yield();
- 17 currentCount++;
- 18 System.out.println(currentCount);
- 19
- } finally {
- 20 lock.unlock();
- 21
- }
- 22
- }
- 23@Override 24 public void run() {
- 25 addCount();
- 26
- }
- 27 public static void main(String[] args) {
- 28 ExecutorService exec = Executors.newCachedThreadPool();
- 29 LockTest test = new LockTest();
- 30
- for (int i = 0; i < 100; i++) {
- 31 exec.execute(test);
- 32
- }
- 33 exec.shutdown();
- 34
- }
- 35
- }
在我的理解中,synchronized 修饰的方法,在检查到对象已经被加锁的情况后,会等待到该对象锁被释放;之后对对象进行加锁,进行自身方法的执行。
但是 Lock 则不是如此,Lock 可以尝试获取锁一段时间,或者尝试获取锁最后失败,而 synchronized 方式则不可以。综合来说,采用 Lock 显式锁可以完成更多并发控制功能,但是其较 synchronized 麻烦许多,所以根据自身程序的需要可以视情况选择这两种同步方法。
本篇文章是简单的介绍了 synchronized 及 Lock 的使用,Lock 的高级使用将在下一篇文章进行介绍。小弟才疏学浅,如有错误,请多指出。
来源: http://www.bubuko.com/infodetail-1963631.html