`
rainsilence
  • 浏览: 158991 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Java synchronized 关键字和Lock的随笔

    博客分类:
  • Java
 
阅读更多

最近客户300个人同时按下一个按钮,在执行到一个业务模块的时候出现了脏读。

 

package org.test.thread;

public class Worker {

	
	public void executeJob() {
		
		// statement A check()
		....
		
		// statement B
		....
		// 
		dao.save();
		
	}
}
 

 

比如上面的代码,第一个线程走到B了,第二个线程走到了A,check的地方。比如重复性check。如果第一个线程和第二个线程的查询主键相同。那么当线程1走到dao.save的地方,线程2刚好跳过check,也就是check无效化了。

 

于是,考虑用同步来解决。

 

到JDK5为止,java中实现线程安全起码有3种方法,ThreadLocal,synchronized关键字,Lock。 

 

ThreadLocal是一种用空间换时间的策略。即为每一个线程创建副本。不适用于我们现在碰到的问题。所以不展开。

 

synchronized关键字:

在java中,他可以是方法修饰符,也可以成为独立的同步块。而加在方法前时,有两种方法,一般的方法和同步方法。两种效果不同。如:

如下这种,

 

package org.test.thread;

public class Worker {
	public synchronized void executeJob() {
		
	}
}

 

 相当于

 

 

package org.test.thread;

public class Worker {

	
	public void executeJob() {
		
		synchronized(this) {
			
		}
		
	}
}
 

而加在静态方法前

 

 

package org.test.thread;

public class Worker {

	public static synchronized void executeJob() {
		
	}
}

 则相当于

 

package org.test.thread;

public class Worker {

	public void executeJob() {
		
		synchronized(this.getClass()) {
			
		}
		
	}
}

 虽然我不推荐你这么认为。因为静态方法加同步和一般方法中用同步块锁定类,这两种方法的用法是完全不一样的,虽然他们锁定的对象是一样的(Class)。

 

要弄清楚他的锁定对象,我们来做个实验:

 

package org.test.thread;

public class Worker {

	
	public synchronized void executeA(String name) {
		for (int i = 0; i < 10; i++) {
			System.out.println(name + "-executeA-" + i);
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	
	public synchronized void executeB(String name) {
		for (int i = 0; i < 10; i++) {
			System.out.println(name + "-executeB-" + i);
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public synchronized static void executeC(String name) {
		for (int i = 0; i < 10; i++) {
			System.out.println(name + "-executeC-" + i);
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	private class SynchronizerWorkerA extends Thread {
		
		public void run() {
			executeA("thread1");
		}
	}
	
	private class SynchronizerWorkerB extends Thread {
		public void run() {
			executeB("thread2");
		}
	}
	
	private class SynchronizerWorkerC extends Thread {
		public void run() {
			executeC("thread3");
		}
	}
	
	public static void main(String args[]) {
		
		Worker worker = new Worker();
		
		worker.new SynchronizerWorkerA().start();
		worker.new SynchronizerWorkerB().start();
		worker.new SynchronizerWorkerC().start();
	}
}

 

结果:

 

thread1-executeA-0

thread3-executeC-0

thread1-executeA-1

thread3-executeC-1

thread1-executeA-2

thread3-executeC-2

thread1-executeA-3

thread3-executeC-3

thread1-executeA-4

thread3-executeC-4

thread1-executeA-5

thread3-executeC-5

thread1-executeA-6

thread3-executeC-6

thread1-executeA-7

thread3-executeC-7

thread1-executeA-8

thread3-executeC-8

thread1-executeA-9

thread3-executeC-9

thread2-executeB-0

thread2-executeB-1

thread2-executeB-2

thread2-executeB-3

thread2-executeB-4

thread2-executeB-5

thread2-executeB-6

thread2-executeB-7

thread2-executeB-8

thread2-executeB-9


可以看到,A方法和C方法可以同时调用,而B则被锁着,直到A方法的锁释放后才能被调用。原因在于静态方法的锁锁定class,而一般方法的锁锁定对象(效果上可以这么认为)

让我们把代码修改一下:
package org.test.thread;

public class Worker {

	
	public synchronized void executeA(String name) {
		for (int i = 0; i < 10; i++) {
			System.out.println(name + "-" + i);
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public synchronized static void executeB(String name) {
		for (int i = 0; i < 10; i++) {
			System.out.println(name + "-" + i);
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	private class SynchronizerWorkerA extends Thread {
		
		private String name = null;
		
		public SynchronizerWorkerA(String name) {
			this.name = name;
		}
		
		public void run() {
			executeA(name);
		}
	}
	
	private class SynchronizerWorkerB extends Thread {
		
		private String name = null;
		
		public SynchronizerWorkerB(String name) {
			this.name = name;
		}
		
		public void run() {
			executeB(name);
		}
	}
	
	public static void main(String args[]) {
		new Worker().new SynchronizerWorkerA("thread1").start();
		new Worker().new SynchronizerWorkerA("thread2").start();
		new Worker().new SynchronizerWorkerB("thread3").start();
		new Worker().new SynchronizerWorkerB("thread4").start();
	}
}
 知道有什么区别吗?我们在一个类的不同对象之间用了同步。
结果:
thread1-0
thread2-0
thread3-0
thread1-1
thread2-1
thread3-1
thread1-2
thread2-2
thread3-2
thread3-3
thread2-3
thread1-3
thread3-4
thread2-4
thread1-4
thread1-5
thread3-5
thread2-5
thread2-6
thread1-6
thread3-6
thread1-7
thread2-7
thread3-7
thread1-8
thread3-8
thread2-8
thread1-9
thread3-9
thread2-9
thread4-0
thread4-1
thread4-2
thread4-3
thread4-4
thread4-5
thread4-6
thread4-7
thread4-8
thread4-9

可以看到,一般方法的同步在不同对象之间没有效果。而静态方法则有作用。(锁定的方式不一样)

理解了这点,我们来看最后一种,Lock

Lock:
JDK5提供了吞吐量比synchronized更好的方法-----Lock接口。他的实现类有两个

让我们看如下代码:
package org.test.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Worker extends Thread {

	private final Lock reentrantLock = new ReentrantLock();
	
	private String name;
	
	public Worker(String name) {
		this.name = name;
	}
	
	public void executeJob() {
		
		try {
			reentrantLock.lock();
			for (int i = 0; i < 10; i++) {
				System.out.println(name + "-" + i);
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		} finally {
			reentrantLock.unlock();
		}
		
	}
	
	public void run() {
		executeJob();
	}
	
	public static void main(String args[]) {
		
		new Worker("thread-1").start();
		new Worker("thread-2").start();
	}
}
 结果:
thread-1-0
thread-2-0
thread-1-1
thread-2-1
thread-1-2
thread-2-2
thread-1-3
thread-2-3
thread-2-4
thread-1-4
thread-1-5
thread-2-5
thread-1-6
thread-2-6
thread-1-7
thread-2-7
thread-1-8
thread-2-8
thread-1-9
thread-2-9

可以看到,lock没有起作用,这是为啥?
原因在于我们锁定了不同对象。
让我们将代码稍微改改
package org.test.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Worker {

	private final Lock reentrantLock = new ReentrantLock();
	
	public void executeJob(String name) {
		
		try {
			reentrantLock.lock();
			for (int i = 0; i < 10; i++) {
				System.out.println(name + "-" + i);
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		} finally {
			reentrantLock.unlock();
		}
		
	}
	
	private class WorkerThread extends Thread {
		
		private String name = null;
		
		public WorkerThread(String name) {
			this.name = name;
		}
		
		public void run() {
			executeJob(name);
		}
	}
	
	public static void main(String args[]) {
		
		Worker worker = new Worker();
		
		worker.new WorkerThread("thread1").start();
		worker.new WorkerThread("thread2").start();
	}
}
 
结果为:
thread1-0
thread1-1
thread1-2
thread1-3
thread1-4
thread1-5
thread1-6
thread1-7
thread1-8
thread1-9
thread2-0
thread2-1
thread2-2
thread2-3
thread2-4
thread2-5
thread2-6
thread2-7
thread2-8
thread2-9
看起作用了吧。
或者将原代码的lock对象改为static的
package org.test.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Worker extends Thread {

	private final static Lock reentrantLock = new ReentrantLock();
	
	private String name;
	
	public Worker(String name) {
		this.name = name;
	}
	
	public void executeJob() {
		
		try {
			reentrantLock.lock();
			for (int i = 0; i < 10; i++) {
				System.out.println(name + "-" + i);
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		} finally {
			reentrantLock.unlock();
		}
		
	}
	
	public void run() {
		executeJob();
	}
	
	public static void main(String args[]) {
		
		new Worker("thread-1").start();
		new Worker("thread-2").start();
	}
}
 结果同上

当然,我们也可以用synchronized轻松实现Lock接口所带来的功能:
package org.test.thread;


public class Worker extends Thread {

	private static Object reentrantLock = new Object();
	
	private String name;
	
	public Worker(String name) {
		this.name = name;
	}
	
	public void executeJob() {
		
		synchronized (reentrantLock) {
			for (int i = 0; i < 10; i++) {
				System.out.println(name + "-" + i);
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	public void run() {
		executeJob();
	}
	
	public static void main(String args[]) {
		
		new Worker("thread-1").start();
		new Worker("thread-2").start();
	}
}
 
Lock与synchronized的区别(仅列出主要的)
1.当块被同步,如果不想等了,synchronized无法停止等待,也没办法得到锁,而Lock可以用tryLock方法轻松做到.
2.性能上Lock更优
添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics