Java线程的同步与锁

Java多线程同步中常用synchronized、Lock等方法进行同步,但他们之间又有什么区别呢?

为什么要进行线程同步?

下面将用两个实例说明线程同步的作用。实例将演示100个线程分别累加20,如果计算正确,将输出结果2000.

不加锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SyncFunc implements Runnable {
public static int count = 0;

public static void main(String[] args) throws InterruptedException {
SyncFunc syncFunc = new SyncFunc();
for (int i = 0; i < 20; i++) {
new Thread(syncFunc).start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
count++;
}
}
}

异常输出结果:

1
1902

加锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SyncFunc implements Runnable {
public static int count = 0;

public static void main(String[] args) throws InterruptedException {
SyncFunc syncFunc = new SyncFunc();
for (int i = 0; i < 20; i++) {
new Thread(syncFunc).start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}

@Override
public synchronized void run() {
for (int i = 0; i < 100; i++) {
count++;
}
}
}

输出结果:

1
2000

线程同步的作用

线程的同步是为了防止多个线程访问一个数据对象时,对数据造成破坏。

线程同步相关方法

  • synchronized同步方法

将方法使用synchronized修饰,就成了同步方法,多个线程最多只能有一个线程能同时执行该方法,其他线程会调用此方法时处于阻塞状态。

有关synchronized的详细用法请参见:synchronized有趣的总结

1
2
3
public synchronized void run(){

}

  • synchronized同步代码块

synchronized还可以对某一个对象进行加锁,相比同步方法减少同步内容。多个线程在对某个变量加锁时,要保证该变量在所有线程中是同一个对象

有关synchronized的详细用法请参见:synchronized有趣的总结

1
2
synchronized(object){
}

  • volatile关键词

volatile变量保证该变量对所有线程的可见性,这里可见性是指当一条线程修改了这个变量的值,新值对其他线程是可以立即得知的。详细介绍请参见:对Java中volatile关键词的思考

  • ReentrantLock可重入锁

在JDK1.5之后,Java提供了另外一种线程同步机制:通过显示定义同步锁对象来实现同步,同步锁应该使用Lock对象充当。

ReentrantLock与synchronized基本对比

  • 用法不同,Lock显示地指定起始位置和终止位置,比synchronized更精确。
  • 性能不同,资源竞争不激烈时,synchronized>ReentrantLock;资源竞争激烈时,synchronized<ReentrantLock。
  • 锁机制不同,synchronized获取多个锁时,释放顺序相反。Lock需要手动解锁,且最好在finally块中。

ReentrantLock基本用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ReenreantLockDemo implements Runnable {
public static int count = 0;
public final Lock lock = new ReentrantLock();

public static void main(String[] args) throws InterruptedException {
ReenreantLockDemo reenreantLockDemo = new ReenreantLockDemo();
for (int i = 0; i < 20; i++) {
new Thread(reenreantLockDemo).start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}

}
}
}

  • ThreadLocal

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

1
2
3
4
5
6
7
private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
// TODO Auto-generated method stub
return 0;
}
};