在多线程编程当中对于共享变量的控制非常重要,平常的程序当中由于是单线程去处理的,因此不会出现变量资源同时被多个线程同时访问修改,程序的运行是顺序的。然而多线程的环境中就会出现资源同时被多个线程获取,同时去做修改的状况,可以看下面一段程序:
public class MyThread implements Runnable{
private int i=0;
public void run() {
try{
i++;
System.out.println(Thread.currentThread().getId() +":i is " + i);
Thread.sleep(1000);
} catch(Exception e){
System.out.println(e.getMessage());
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
MyThread thread = new MyThread ();
for(int i=0; i<6; i++){
executorService.submit(thread);
}
}
}
运行这段程序后显示的一个可能结果如下:
由线程池产生的ID为10,11,12的三个线程开始工作,在第一次的时候就同时拿到i并进行自增操作,最后同时打印输出,因此i的值显示均为3,后续三个线程分别是陆续拿到i并做修改,因此输出的是4,5,6。
如果想对共享资源变量i的访问采用顺序的方式进行,就需要加锁,这时大家首先想到的是用synchronized来进行锁的控制。
public synchronized void run() {
try{
i++;
System.out.println(Thread.currentThread().getId() +":i is " + i);
Thread.sleep(1000);
} catch(Exception e){
System.out.println(e.getMessage());
}
}
加锁后执行结果:
从结果中可以看出 i 是顺序加1
还有一种方式就是采用重入锁
public class ThreadWithReentrantLock implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
private int i=0;
public void run() {
try{
lock.lock();
i++;
System.out.println(Thread.currentThread().getId() +":i is " + i);
Thread.sleep(1000);
} catch(Exception e){
System.out.println(e.getMessage());
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
ThreadWithReentrantLock thread = new ThreadWithReentrantLock();
for(int i=0; i<6; i++){
executorService.submit(thread);
}
}
}
运行结果:
无论是采用关键字Sychronized还是采用重入锁都不会造成死锁,Sychronized会有默认的计数器实现锁的释放,而重入锁在finaiily代码块都会将锁释放。重入锁有什么好处呢,请看下面的示例。
public class ThreadWithReentrantLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
private int i=0;
public void run() {
System.out.println(Thread.currentThread().getId()+":begin...");
try{
if(lock.tryLock())
{
System.out.println(Thread.currentThread().getId() + ":get lock successfully...");
} else {
System.out.println(Thread.currentThread().getId() + ":get lock failed...");
lock.lockInterruptibly();
}
lock.lock();
i++;
System.out.println(Thread.currentThread().getId() +":i is " + i);
Thread.sleep(3000);
} catch(Exception e){
System.out.println(e.getMessage());
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
ThreadWithReentrantLock thread = new ThreadWithReentrantLock ();
for(int i=0; i<6; i++){
executorService.submit(thread);
}
}
}
运行结果:
可以看到线程11一直占据着锁,而12和10在尝试获取锁的时候由于是在并发状况下,线程11持有锁,因此对他们采取了中断,让他们退出锁的竞争,保证了程序的继续进行。
从中可以得出重入锁比Sychronized好的地方是能发现锁是否被占有,并且能进行中断控制。从中可以看到,Sychronized虽然是JDK提供的同步关键字,但它同样实现了锁的功
能,本质Sychronized是一种内置锁,是由JVM对于并发对象设置了内置锁,保证了并发,然而内置锁的弊病就是看不见摸不着,无法对其进行控制。开发中还是建议采用可重
入锁,能够显示的控制。