并发编程中一个最主要的场景和挑战就是对共享资源对同步访问控制,同步访问控制是保证程序线程安全的最重要手段。
在jdk1.5+中,主要提供了两种同步控制方式:
synchronized
关键字Lock
接口
其中,synchronized由可以分为:同步代码块/普通同步方法和静态同步方法三种,下面分别来看一下这集中同步控制机制的使用方法。
普通同步方法
这种方式是最常用的同步方式,通过在方法上增加synchronized
修饰符达到在统一对象上访问该方法的同步效果
此时,锁对象即为当前对象。
package com.learn.concurrency.test;
import lombok.Data;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Data
public class SynchronizedDemo {
private int counter = 0;
public synchronized void increment(){
counter ++;
}
public static void main(String[] args) throws Exception{
SynchronizedDemo demo1 = new SynchronizedDemo();
//启动两个线程操作同一个对象,看最终结果是否为2000
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new IncrementTask(demo1));
executorService.execute(new IncrementTask(demo1));
TimeUnit.SECONDS.sleep(5);
executorService.shutdown();
System.out.println(demo1.getCounter());
}
}
class IncrementTask implements Runnable {
private SynchronizedDemo synchronizedDemo;
public IncrementTask(SynchronizedDemo synchronizedDemo) {
this.synchronizedDemo = synchronizedDemo;
}
@Override
public void run() {
for(int i = 0;i < 1000;i ++) {
synchronizedDemo.increment();
}
}
}
输出结果为:2000。
同步代码块
有时,如果方法中包含很多计算,但只有一小部分需要同步处理,这是,如果在方法上直接增加synchronized
,势必会增大同步范围,影响程序性能,这时,就可以通过同步代码块来缩小同步范围,同时,还可以指定一个具体但锁对象。
将上述代码块中但increment方法改为如下:
public void increment(){
//用当前对象作为锁对象,也可以用其他对象来充当锁对象
synchronized (this) {
counter++;
}
}
计算结果也是2000。
静态方法同步
将上述代码修改如下所示:
package com.learn.concurrency.test;
import lombok.Data;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Data
public class SynchronizedDemo {
//静态域
public static int counter = 0;
//静态同步方法
public static synchronized void increment(){
System.out.println(Thread.currentThread().getName());
counter ++;
}
public static void main(String[] args) throws Exception{
//创建两个对象
SynchronizedDemo demo1 = new SynchronizedDemo();
SynchronizedDemo demo2 = new SynchronizedDemo();
//启动两个线程操作同一个对象,看最终结果是否为10
ExecutorService executorService = Executors.newCachedThreadPool();
//启动两个线程同时操作counter对象
executorService.execute(new IncrementTask(demo1));
executorService.execute(new IncrementTask(demo2));
TimeUnit.SECONDS.sleep(5);
executorService.shutdown();
System.out.println(SynchronizedDemo.counter);
}
}
class IncrementTask implements Runnable {
private SynchronizedDemo synchronizedDemo;
public IncrementTask(SynchronizedDemo synchronizedDemo) {
this.synchronizedDemo = synchronizedDemo;
}
@Override
public void run() {
for(int i = 0;i < 5;i ++) {
synchronizedDemo.increment();
}
}
}
最终输出结果为:
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
10
从结果中可以看到,虽然两个线程操作的是不同对象,但线程2是在线程1执行完成后,才开始执行的,说明两个线程使用的是同一把锁对象,即,此时锁对象类对应的Class对象,全局唯一,所以才能保证两个线程被同步执行。
Lock锁
Lock锁是jdk1.5引入的,这种锁是用java实现的一种基于cas的锁,语义更加清除,功能更加强大(比如超时获取锁,公平锁,非公平锁等),而synchronized是基于jvm内部机制实现的。
在使用上,与synchronized相比,略显复杂。
package com.learn.concurrency.test;
import lombok.Data;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@Data
public class SynchronizedDemo {
public int counter = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment(){
//加锁
lock.lock();
try {
counter++;
}finally {
//释放锁
lock.unlock();
}
}
public static void main(String[] args) throws Exception{
SynchronizedDemo demo1 = new SynchronizedDemo();
//SynchronizedDemo demo2 = new SynchronizedDemo();
//启动两个线程操作同一个对象,看最终结果是否为2000
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new IncrementTask(demo1));
executorService.execute(new IncrementTask(demo1));
TimeUnit.SECONDS.sleep(5);
executorService.shutdown();
System.out.println(demo1.getCounter());
}
}
class IncrementTask implements Runnable {
private SynchronizedDemo synchronizedDemo;
public IncrementTask(SynchronizedDemo synchronizedDemo) {
this.synchronizedDemo = synchronizedDemo;
}
@Override
public void run() {
for(int i = 0;i < 5;i ++) {
synchronizedDemo.increment();
}
}
}
上述代码最终的输出结果也为10。
在jdk1.6以后,jvm对synchronized进行来优化,在性能上并不必Lock要差,所以,只有在实现复杂逻辑对时候才考虑使用Lock,一般同步需求使用synchronized足矣。
以上只简要介绍来同步控制对方式,后面会继续深入其原理。