在多线程并发的情况下,有时就涉及到对于同一资源的读写,如果不进行一些处理,容易出现数据混乱,结果和实际不一致等问题。java中可以使用synchronized关键字对资源锁定。
synchronized的用法
synchronized有2种用法:
1.修饰代码块,以某个对象为锁,锁的范围是指定的代码块。2.修饰方法,其实也可以等价于修饰代码块,比如修饰普通方法:synchronized void doxx(){//.........}
等价于
void doxx(){ synchronized (this){ //......... }}
以当前实体为锁对象,整个方法都是在代码块中。也能修饰静态方法:
public class Test{ static synchronized void doxx(){ //......... }}
等价于
public class Test{ static void doxx(){ synchronized (Test.class){ //......... } }}
synchronized 修饰代码块
先举一个反例demo:
import java.util.List;import java.util.Random;public class Thread1 extends Thread{ private List list; public Thread1(List list) { this.list=list; } @Override public void run() { try { for(int i=0;i<100;i++){ Thread.sleep(10);//模拟处理一些业务,这样也更容易重现问题 int randon = new Random().nextInt(); list.add(randon); } } catch (InterruptedException e) { e.printStackTrace(); } }}
public static void main(String[] args) throws InterruptedException { List list = new ArrayList<>(1000); Thread1 t1 = new Thread1(list); t1.start(); Thread1 t2 = new Thread1(list); t2.start(); t1.join(); t2.join(); System.out.println("size="+list.size()); }
执行结果:
size=162
这个结果是不确定,每次执行都可能不一样。demo里启动了2个线程,每个执行100次,按理list的数据量应该会是200个。这个就是本文开始提到的,多个线程读写同一个对象时,发生了数据异常。那么我们再用synchronized对demo进行小小的改造。
import java.util.List;import java.util.Random;public class Thread1 extends Thread{ private List list; public Thread1(List list) { this.list=list; } @Override public void run() { try { for(int i=0;i<100;i++){ Thread.sleep(10);//模拟处理一些业务,这样也更容易重现问题 int randon = new Random().nextInt(); synchronized (list) {//就只改这个地方 list.add(randon); } } } catch (InterruptedException e) { e.printStackTrace(); } }}
main方法保持不变,结果如下:
size=200
可见使用synchronized关键字后,代码的执行结果恢复了正常。
synchronized修饰方法
import java.util.List;import java.util.Random;public class Thread1 extends Thread{ private List list; public Thread1(List list) { this.list=list; } public synchronized void run() { try { for(int i=0;i<100;i++){ Thread.sleep(10);//模拟处理一些业务,这样也更容易重现问题 int randon = new Random().nextInt(); list.add(randon); } } catch (InterruptedException e) { e.printStackTrace(); } }}
main方法不变,执行结果:
size=150
这是很多人开始接触多线程时,会出现的错误,明明对run方法用了synchronized 关键字怎么出来的结果是不对的。根据上面提到的我们把代码转变一下:
import java.util.List;import java.util.Random;public class Thread1 extends Thread{ private List list; public Thread1(List list) { this.list=list; } public void run() { try { synchronized(this){//把synchronized改到这个地方 for(int i=0;i<100;i++){ Thread.sleep(10);//模拟处理一些业务,这样也更容易重现问题 int randon = new Random().nextInt(); list.add(randon); } } } catch (InterruptedException e) { e.printStackTrace(); } }}
synchronized用在this上,所以t1.start()锁定的是t1,t2.start()锁定的是t2,2者锁定的对象不同自然就没有相应的效果。
那是不是synchronized用在方法上就没有作用呢?当然不会,先看下面的例子:
import java.util.Random;public class Thread1 extends Thread{ private SyncList list; public Thread1(SyncList list) { this.list=list; } public void run() { try { for(int i=0;i<100;i++){ Thread.sleep(10);//模拟处理一些业务,这样也更容易重现问题 int randon = new Random().nextInt(); list.addList(randon);//注意这里 } } catch (InterruptedException e) { e.printStackTrace(); } }}
import java.util.ArrayList;import java.util.List;public class SyncList{ private List list; public SyncList(List list) { this.list = list; } public void addList(E obj){ list.add(obj); } public List getList() { return list; }}
public static void main(String[] args) throws InterruptedException { SyncList list = new SyncList(new ArrayList<>(1000)); Thread1 t1 = new Thread1(list); t1.start(); Thread1 t2 = new Thread1(list); t2.start(); t1.join(); t2.join(); System.out.println("size="+list.getList().size());}
执行结果:
size=161
修改一下SyncList:
import java.util.ArrayList;import java.util.List;public class SyncList{ private List list; public SyncList(List list) { this.list = list; } public synchronized void addList(E obj){//仅在这里加上synchronized list.add(obj); } public List getList() { return list; }}
执行结果:
size=200
这个就是synchronized用在方法上的一个例子,锁定的对象都是同一个SyncList,所以最终结果是正确的。因此synchronized使用上很重要的一点,是保障多个线程锁定的对象要一致。
异常释放锁
public class Thread1 extends Thread{ private Object lock; public Thread1(Object lock) { this.lock=lock; } public void run() { try { synchronized (lock) { for(int i=5;i>-1;i--){ System.out.println(Thread.currentThread().getName()+":"+i); Thread.sleep(200); int j= 100/i; } } } catch (InterruptedException e) { e.printStackTrace(); } }}
public static void main(String[] args) throws InterruptedException { Object lock = new Object(); for(int i=0;i<2;i++){ Thread1 t1 = new Thread1(lock); t1.start(); t1.join(); Thread.sleep(10); }}
执行结果:
Thread-0:5Thread-0:4Thread-0:3Thread-0:2Thread-0:1Thread-0:0Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero at Thread1.run(Thread1.java:12)Thread-1:5Thread-1:4Thread-1:3Thread-1:2Thread-1:1Thread-1:0Exception in thread "Thread-1" java.lang.ArithmeticException: / by zero at Thread1.run(Thread1.java:12)
可以看到由于Thread-0先获取锁,Thread-1一直处于等待状态,Thread-0一直执行到i=0时,程序发生异常,锁被释放。Thread-1就获得了锁开始执行。
总结
1.synchronized可以修饰方法或者代码块。
2.尽量使用代码块以及减少代码块的范围,有利于提高程序运行效率。3.要注意锁定的对象是否一致。