多线程同步的两大方式synchronized 与 lock
先谈一下使用synchronized关键字方式处理同步问题
1、使用同步代码块
以卖票举例
class MyThread implements Runnable {
private int ticket = 1000 ; // 一共十张票
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
// 在同一时刻,只允许一个线程进入代码块处理
synchronized(this) { // 表示为程序逻辑上锁
if(this.ticket>0) { // 还有票
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} // 模拟网络延迟
System.out.println(Thread.currentThread().getName()+",还有" +this.ticket --
+" 张票");
}
}
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt = new MyThread() ;
Thread t1 = new Thread(mt,"黄牛A");
Thread t2 = new Thread(mt,"黄牛B");
Thread t3 = new Thread(mt,"黄牛C");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
t3.start();
}
}
注意:
上面例子使用的是同步代码块:锁定的是一个要锁定的对象,上面锁的是this,是在方法里面拦截的,也就是进入到方法中的线程依然可能会有多个。
2、使用同步方法
这里进入到卖票这个方法中的线程只会有一个,sychonized关键字锁的是sale()方法。
class MyThread implements Runnable {
private int ticket = 1000; // 一共十张票
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
this.sale();
}
}
public synchronized void sale() {
if (this.ticket > 0) { // 还有票
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} // 模拟网络延迟
System.out.println(Thread.currentThread().getName() + ",还有" + this.ticket-- + " 张
票");
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt, "黄牛A");
Thread t2 = new Thread(mt, "黄牛B");
Thread t3 = new Thread(mt, "黄牛C");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
t3.start();
}
}
以上是synchronized关键字的简单使用。
再增加一点synchronized的额外说明
实际上,synchronized(this)以及非static的synchronized方法,只能防止多个线程同时执行同一个对象的同步代码段。即synchronized锁住的是括号里的对象,而不是代码。对于非static的synchronized方法,锁的就是对象本身也就是this。
当synchronized锁住一个对象后,别的线程如果也想拿到这个对象的锁,就必须等待这个线程执行完成释放锁,才能再次给对象加锁,这样才达到线程同步的目的。即使两个不同的代码段,都要锁同一个对象,那么这两个代码段也不能在多线程环境下同时运行。
这里举一个反例:
class Sync {
public synchronized void test() {
System.out.println("test方法开始,当前线程为 "+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test方法结束,当前线程为 "+Thread.currentThread().getName());
}
}
class MyThread extends Thread {
@Override
public void run() {
Sync sync = new Sync() ;
sync.test();
}
}
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 3 ; i++) {
Thread thread = new MyThread() ;
thread.start();
}
}
}
运行上述代码我们可以发现,没有看到synchronized起到作用,三个线程同时运行test()方法。是因为这里的synchonized锁住的不是同一个对象,虽然在主方法中创建了同一个类的三个线程,但是在调用test()时各自new了各自的对象进行调用,而并不是同一个对象。
上述改进
锁住同一个对象
class Sync {
public void test() {
synchronized(this) {
System.out.println("test方法开始,当前线程为 " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test方法结束,当前线程为 " + Thread.currentThread().getName());
}
}
}
class MyThread extends Thread {
private Sync sync ;
public MyThread(Sync sync) {
this.sync = sync ;
}
@Override
public void run() {
this.sync.test();
}
}
public class Test {
public static void main(String[] args) {
Sync sync = new Sync() ;
for (int i = 0; i < 3 ; i++) {
Thread thread = new MyThread(sync) ;
thread.start();
}
}
}
以上说的是使用synchronized关键字锁对象,称为对象锁。
接下来谈一谈使用synchronized关键字锁住类,称为全局锁。
例:
class Sync {
public void test() {
synchronized(Sync.class) {
System.out.println("test方法开始,当前线程为 " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test方法结束,当前线程为 " + Thread.currentThread().getName());
}
}
}
class MyThread extends Thread {
@Override
public void run() {
Sync sync = new Sync() ;
sync.test();
}
}
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 3 ; i++) {
Thread thread = new MyThread() ;
thread.start();
}
}
}
上面代码用synchronized(Sync.class)实现了全局锁的效果。因此,如果要想锁的是代码段,锁住多个对象的同一方法,使用这种全局锁,锁的是类而不是this。
static synchronized方法,static方法可以直接类名加方法名调用,方法中无法使用this,所以它锁的不是this,而是类的Class对象,所以,static synchronized方法也相当于全局锁,相当于锁住了代码段。
接下来说一说JDK1.5提供的Lock锁
例:使用ReentrantLock进行同步处理
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyThread implements Runnable {
private int ticket = 500;
private Lock ticketLock = new ReentrantLock() ;
@Override
public void run() {
for (int i = 0; i < 500; i++) {
ticketLock.lock();
try {
if (this.ticket > 0) { // 还有票
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
} // 模拟网络延迟
System.out.println(Thread.currentThread().getName() + ",还有" + this.ticket-
- + " 张票");
}
} finally {
ticketLock.unlock();
}
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt, "黄牛A");
Thread t2 = new Thread(mt, "黄牛B");
Thread t3 = new Thread(mt, "黄牛C");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
t3.start();
}
}
在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。相比之下使用Java提供的Lock对象,性能更高一些。
到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。