Java复习-并发编程之synchronized

来源:互联网 发布:mplayerx mac dmg 编辑:程序博客网 时间:2024/05/20 11:34

在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

1、先看一个简单的例子

package BasicConcept;import java.util.ArrayList;public class SynchrnoizedTest {public static void main(String[] args) {// TODO Auto-generated method stubInsertData insertData = new InsertData() ;Thread t1 = new Thread(insertData);Thread t2 = new Thread(insertData);t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}class InsertData implements Runnable{public ArrayList<Integer> arrayList = new ArrayList<Integer>();public  void insert() {// TODO Auto-generated method stubfor (int i = 0; i < 500; i++) {if(!arrayList.contains(new Integer(i)) ) {System.out.println(Thread.currentThread().getName()+"在插入数据"+i);arrayList.add(i);}}}@Overridepublic void run() {// TODO Auto-generated method stubinsert();}}

我们可以看看运行结果:


我们明明希望得到的是新建两个线程往一个arrayList里装0-499个不同的数字,但是新建线程调用方法后发现,数字存入的很明显有重复的数字。为什么呢?正式因为线程安全问题,可能线程0执行到 

if(!arrayList.contains(new Integer(i)) ) {System.out.println(Thread.currentThread().getName()+"在插入数据"+i);

的时候,发生了阻塞,这时候进入了线程1,此时i数据并未写入到arrayList中去,所以线程1依然可以通过if语句,想里插入i,进而导致重复。而在insert函数前加入synchronized关键字之后:

public  synchronized void insert() {// TODO Auto-generated method stubfor (int i = 0; i < 500; i++) {if(!arrayList.contains(new Integer(i)) ) {System.out.println(Thread.currentThread().getName()+"在插入数据"+i);arrayList.add(i);}}}


我们看到结果是:


一直在执行线程0而并未进入线程1,且arryList最后存入的数据就为0-499;我们可以发现

synchronized关键字相当于对方法进行上锁了,只要在执行了insert方法后,别的线程只有在不再用insert

方法后才可能调用此方法。

而在insert函数里加入synchronized代码块之后:

public  void insert() {// TODO Auto-generated method stubfor (int i = 0; i < 500; i++) {synchronized(arrayList) {if(!arrayList.contains(new Integer(i)) ) {System.out.println(Thread.currentThread().getName()+"在插入数据"+i);arrayList.add(i);}}}}

我们得到的结果是:


通过synchronized代码块对arraylist对象进行了上锁,只要此对象被使用,则知道完成插入一直都是使用当前线程才能使用这个arrayList对象!

总结下,关于一个对象的synchronized方法

1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。

2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的

3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。

关于synchronized代码块:

     此外,例子代码中为

synchronized(arrayList) {
还可以为:

synchronized(this) {
甚至还可以在类中new一个额外对象来上锁实现 synchronized代码块。
      synchronized代码块使用起来比synchronized方法要灵活得多。因为也许一个方法中只有一部分代码只需要同步,如果此时对整个方法用synchronized进行同步,会影响程序执行效率。而使用synchronized代码块就可以避免这个问题,synchronized代码块可以实现只对需要同步的地方进行同步


同时有一点要注意:对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。

如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:

  1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;

  2)线程执行发生异常,此时JVM会让线程自动释放锁。








0 0