多线程-synchronized

引言

synchronized是Java线程同步中的一个重要的概念,synchronized是独占锁(互斥锁),同时也是可重入锁(可重入锁一定程度上避免了死锁的问题,内部是关联一个计数器,加一次锁计数器值加一,为零时释放锁),也是一种重量级锁。

synchronized是Java中的关键字,是一种同步锁,修饰的情形有以下几种:

(1)修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个方法的对象;

(2)修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。

(3)修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。

(4)修饰一个类,其作用的范围是synchronized后面大括号{}括起来的部分,作用的对象是这个类的所有对象。

 

修饰一个代码块

(1)一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。

package com.huawei.thread;

class SyncThread  implements Runnable {
    private static int count;

    public SyncThread() {
        count =  0 ;
    }

    @Override
    public void run() {
        synchronized ( this ) {
            for ( int i =  0 ; i <  5 ; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() +  ": " + (count++));
                    Thread.sleep( 100 );
                }  catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
            }
        }
    }
}

public class Test38 {

    public static void main(String[] args) {
        SyncThread sync =  new SyncThread();
        Thread t1 =  new Thread(sync,  "A" );
        Thread t2 =  new Thread(sync,  "B" );
        t1.start();
        t2.start();
    }

}  

运行截图:

当两个并发线程t1,t2访问同一个对象sync中的synchronized代码块时,在同一个时刻只能有一个线程得到执行,另一个线程受阻塞。t1和t2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。

main函数做简单修改:

public static void main(String[] args) {
        Thread t1 =  new Thread( new SyncThread(),  "A" );
        Thread t2 =  new Thread( new SyncThread(),  "B" );
        t1.start();
        t2.start();
}

synchronized只锁定对象,每个对象只有一个锁与之相关联。t1和t2分别创建了SyncThread对象与之关联,因此t1和t2锁定的对象是不同的,互不干扰,不形成互斥,所有两个线程可以同时执行。

(2)当一个线程访问对象的一个synchronized同步块时,另一个线程仍然可以访问该对象中的非synchronized不同代码块。

package com.huawei.thread;

class Counter  implements Runnable {
    private int count;

    public Counter() {
        count =  0 ;
    }

    public void addCount() {
        synchronized ( this ) {
            for ( int i =  0 ; i <  5 ; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() +  ": " + (count++));
                    Thread.sleep( 100 );
                }  catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void printCount() {
        for ( int i =  0 ; i <  5 ; i++) {
            try {
                System.out.println(Thread.currentThread().getName() +  " count: " + count);
                Thread.sleep( 100 );
            }  catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void run() {
        String tName = Thread.currentThread().getName();
        if (tName.equals( "A" )) {
            addCount();
        }  else if (tName.equals( "B" )) {
            printCount();
        }
    }
}

public class Test39 {

    public static void main(String[] args) {
        Counter counter =  new Counter();
        Thread t1 =  new Thread(counter,  "A" );
        Thread t2 =  new Thread(counter,  "B" );
        t1.start();
        t2.start();
    }

}  

运行截图:

addCount是一个synchronized方法,printCount是非synchronized。从上面的结果中可以看出一个线程访问一个对象的synchronized代码块时,其他线程可以访问对象的非synchronized代码块而不受阻塞。

(3)指定要给某个对象加锁

package com.huawei.thread;

class Account {
    private String name;
    private float amount;

    public Account(String name,  float amount) {
        this .name = name;
        this .amount = amount;
    }

    public void deposit( float amt) {
        amount += amt;
        try {
            Thread.sleep( 100 );
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void withdraw( float amt) {
        amount -= amt;
        try {
            Thread.sleep( 100 );
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

    public float getAmount() {
        return amount;
    }

    @Override
    public String toString() {
        return "Account [name=" + name +  ", amount=" + amount +  "]" ;
    }
}

class AccountOperator  implements Runnable {
    private Account account;

    public AccountOperator(Account account) {
        this .account = account;
    }

    @Override
    public void run() {
        synchronized (account) {
            account.deposit( 500 );
            account.withdraw( 500 );
            System.out.println(Thread.currentThread().getName() +  ": " + account.getAmount());
        }
    }
}

public class Test40 {
    static final int THREAD_NUM =  5 ;

    public static void main(String[] args) {
        Account account =  new Account( "zhang san" ,  1000 .0f);
        AccountOperator ao =  new AccountOperator(account);
        Thread[] threads =  new Thread[THREAD_NUM];
        for ( int i =  0 ; i < THREAD_NUM; i++) {
            threads[i] =  new Thread(ao,  "Thread" + i);
            threads[i].start();
        }
    }

}

 

synchronized给account对象加了锁。这时,当一个线程访问account对象时,其他试图访问account对象的线程将会阻塞。

当有一个明确的对象作为锁时,就可以用类似下面这样的方式写程序:

public void method3(SomeObject obj)
{
   //obj 锁定的对象
   synchronized (obj) {
      // #TODO
   }
}  

当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:

class Test  implements Runnable
{
   private byte [] lock =  new byte [ 0 ];   // 特殊的instance变量
   public void method()
   {
      synchronized (lock) {
         // #TODO 同步代码块
      }
   }

   public void run() {

   }
}  

注:零长度的byte数组对象创建起来比任何对象都经济。

synchronized不能锁住基本的类型如:int,long,float等非对象类型

修饰一个方法 

synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块时大括号括起来的范围,而修饰方法范围是整个函数。

(1)synchronized关键字不能继承

虽然可以用synchronized来定义方法,但是synchronized不属于方法定义的一部分。因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖这个方法,则在子类中这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。

在子类方法中加上synchronized关键字:

class Parent {
   public synchronized void method() { }
}
class Child  extends Parent {
   public synchronized void method() { }
}

在子类方法中调用父类的同步方法: 

class Parent {
   public synchronized void method() {   }
}
class Child  extends Parent {
   public void method() {  super .method();   }
} 

(2)在定义接口方法时不能使用synchronized关键字

(3)构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步

 

修饰一个静态方法

 (1)静态方法属于类而不属于对象。synchronized修饰的静态方法锁定的是这个类的所有对象。

package com.huawei.thread;

public class Test42 {

    static class SyncThread  implements Runnable {
        private static int count;

        public SyncThread() {
            count =  0 ;
        }

        public synchronized static void m() {
            for ( int i =  0 ; i <  5 ; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() +  ": " + (count++));
                }  catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public synchronized void run() {
            m();
        }
    }

    public static void main(String[] args) {
        Thread t1 =  new Thread( new SyncThread(),  "A" );
        Thread t2 =  new Thread( new SyncThread(),  "B" );
        t1.start();
        t2.start();
    }

}

只要修饰了静态方法,相对于整个类及其产生的对象只有一把锁。

(2)Java类中可以有静态代码块static{},可以在其中设置同步代码块

public class Test40 {
    static final Integer THREAD_NUM =  5 ;
   
    static {
        synchronized (THREAD_NUM) {
          // #TODO  
        }
    }
}  

其中synchronized修饰的必须是static变量或者类

(3)如果一个类中定义了一个synchronized 的 static 函数A,也定义了一个 synchronized 的 instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。B方法的锁是Obj这个对象,而A的锁是Obj所属的那个Class。

package com.huawei.thread;

public class Test43 {

    public static void main(String[] args) {
        My my =  new My();
        Thread t1 =  new Thread(my,  "A" );
        Thread t2 =  new Thread(my,  "B" );
        t1.start();
        t2.start();
    }

}

class My  implements Runnable {

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        if (name.equals( "A" )) {
            test1();
        }  else if (name.equals( "B" )) {
            test2();
        }
    }

    synchronized void test2() {
        try {
            for ( int i =  0 ; i <  5 ; i++) {
                Thread.sleep( 100 );
                System.out.println( "test2" );
            }
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

    synchronized static void test1() {
        try {
            for ( int i =  0 ; i <  5 ; i++) {
                Thread.sleep( 100 );
                System.out.println( "test1" );
            }
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

}  

 

 

修饰一个类  

class ClassName {
   public void method() {
      synchronized (ClassName. class ) {
         // todo
      }
   }
}

效果与修饰一个静态方法类似。

小结

(1)无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。 

(2)每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。 

(3)实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

 

参考资料

 http://blog.csdn.net/luoweifu/article/details/46613015

 http://www.cnblogs.com/beiyetengqing/p/6213437.html

 

posted @ 2017-10-16 15:23  小路不懂2  阅读(234)  评论(0编辑  收藏  举报