Java并发之synchronized使用

654 阅读7分钟
原文链接: my.oschina.net

synchronized,是Java语言的关键字,读['siŋkrənaizd],当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

一、Java为何要使用synchronized?

线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题。所以需要牢牢记住“共享”这两个字,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么就没有同步的必要。

二、synchronized修饰范围

  1. 修饰实例方法 > 多个线程访问同一个实例的加锁方法时,会出现锁的竞争

  2. 修饰静态方法 > 多个线程访问类的加锁方法时,会出现锁的竞争

  3. 修饰代码块 > 多线程访问到同一个代码块时,会出现竞争的问题

三、synchronized同步锁对象

synchronized可以用来修饰方法或代码块,我们可以把获取的同步锁归为以下3种:

  1. 实例对象锁:修饰在普通方法上(非静态方法);在代码块中修饰this即synchronized(this)代码块
  2. 类对象锁:修饰在静态方法上;在代码块中修饰class即synchronized(X.class)代码块
  3. 同步块非当前实例对象锁:在代码块中修饰非当前实例对象,比如在X类中synchronized(对象a)的代码

对这3种不同的锁,使用相互之间不受影响,对于同一种锁,会出现锁的竞态条件。

四、synchronized同步锁使用

对于1.实例对象锁的使用:

  • 所有的非静态同步方法用的都是同一把锁—实例对象本身,也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁。
  • 实例对象锁存在于当前实例,不同的实例对象的锁相互之间不受影响。
  • synchronized(this)代码块获取的是该实例对应的锁,与非静态的synchronized同步方法使用的是同一把锁,会出现锁的竞争。
  • 实例锁和类对象锁没有影响,不会造成彼此阻塞。

对于2.类对象锁的使用:

  • 对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。
  • 而所有的静态同步方法用的也是同一把锁——类对象本身,如果一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。
  • 不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,他们都使用的是同一个类的对象锁,会出现锁的竞争。
  • synchronized(X.class)代码块使用的是类Class的对象锁,与类中静态同步方法会出现锁的竞争。多个类中的synchronized(X.class)代码块也会出现锁的竞态条件。

对于3.同步块非当前实例对象锁的使用:

  • 对于同步块,由于其对象锁是可以选择的,只有使用同一把锁的同步块之间才有着竞态条件。
  • 同步锁的对象是基于实际对象而不是对象引用的,所以使用时特别注意,在锁的作用域中因改变实际对象引用从而引起锁的对象改变导致同步锁失去竞太条件。

五、synchronized使用小结

关于锁和同步的使用,汇总以下几个要点:

  1. 同步锁只能通过同步方法去保证共享变量安全,而不是同步变量和类。
  2. 当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
  3. 不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
  4. 对于出现竞态条件锁的线程,一个线程获取了锁,其他线程会阻塞等待该锁的释放去获取该锁。
  5. 如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
  6. 线程睡眠时,它所持的任何锁都不会释放。
  7. 线程可以获得多个重进入(synchronized )锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
  8. 同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
  9. 在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁
  10. 编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问。

测试

public class Service {

    public Service() {
        System.out.println("当前线程:" + Thread.currentThread().getName() + " 构造方法");
    }

    static {
        System.out.println("当前线程:" + Thread.currentThread().getName() + " 静态代码块");
    }

    private Object object1 = new Object();
    private Object object2 = new Object();

    synchronized public static void printA() {
        try {
            System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                    + "在 " + System.currentTimeMillis() + " 进入方法A");
            Thread.sleep(3000);
            System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                    + "在 " + System.currentTimeMillis() + " 退出方法A");
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    synchronized public void printB() {
        try {
            System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                    + "在 " + System.currentTimeMillis() + " 进入方法B");
            Thread.sleep(3000);
            System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                    + "在 " + System.currentTimeMillis() + " 退出方法B");
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    public void printC() {
        System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                + "在 " + System.currentTimeMillis() + " 进入方法C");
        try {
            synchronized (object1) {
                System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                        + "在 " + System.currentTimeMillis() + "进入方法C--synchronized{X}");
                Thread.sleep(3000);
                System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                        + "在 " + System.currentTimeMillis() + " 退出方法C-synchronized{X}");
            }
            System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                    + "在 " + System.currentTimeMillis() + " 退出方法C");
        } catch (Exception e) {
            System.out.println(e);
        }
    }


    public void printD() {
        System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                + "在 " + System.currentTimeMillis() + " 进入方法D");
        try {
            synchronized (object2) {
                System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                        + "在 " + System.currentTimeMillis() + "进入方法D--synchronized{X}");
                Thread.sleep(3000);
                System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                        + "在 " + System.currentTimeMillis() + " 退出方法D-synchronized{X}");
            }
            System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                    + "在 " + System.currentTimeMillis() + " 退出方法D");
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    public void printE() {
        System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                + "在 " + System.currentTimeMillis() + " 进入方法E");
        try {
            synchronized (this) {
                System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                        + "在 " + System.currentTimeMillis() + " 进入方法E--synchronized{this}");
                Thread.sleep(3000);
                System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                        + "在 " + System.currentTimeMillis() + " 退出方法E-synchronized{this}");
            }
            System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                    + "在 " + System.currentTimeMillis() + " 退出方法E");
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    public static void printF() {
        System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                + "在 " + System.currentTimeMillis() + " 进入方法E");
        try {
            synchronized (Service.class) {
                System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                        + "在 " + System.currentTimeMillis() + " 进入方法F--synchronized{class}");
                Thread.sleep(3000);
                System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                        + "在 " + System.currentTimeMillis() + " 退出方法F-synchronized{class}");
            }
            System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                    + "在 " + System.currentTimeMillis() + " 退出方法F");
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    public void printG() {
        System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                + "在 " + System.currentTimeMillis() + " 进入方法G");
        try {
            synchronized (Service.class) {
                System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                        + "在 " + System.currentTimeMillis() + " 进入方法G--synchronized{class}");
                Thread.sleep(3000);
                System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                        + "在 " + System.currentTimeMillis() + " 退出方法G-synchronized{class}");
            }
            System.out.println("当前线程:" + Thread.currentThread().getName() + Thread.currentThread().getId() 
                    + "在 " + System.currentTimeMillis() + " 退出方法G");
        } catch (Exception e) {
            System.out.println(e);
        }
    }

}

测试:

/**
 * 0.synchronized同步方法、synchronized静态同步方法分别是用到的是实例锁,类锁,一个线程获取到synchronized同步方法的锁时,
 * 另一线程依然可以进入synchronized静态同步方法(实例锁,类锁两者不同,相互不影响
 * )
 * 1.synchronized同步方法,synchronized(this)都是对象锁,对于其他线程调用synchronized同步方法,synchronized(this)呈阻塞状态 </br>
 * 2.同一时间同一线程只有一个线程获取对象锁执行 </br>
 * <p>
 * 1.synchronized(非this)对象锁,对于非this如果是同一对象,两个线程同时只有一个可以获取该锁 </br>
 * 2.对象锁(synchronized同步方法 或 synchronized(this))、synchronized(非this)对象锁 两个线程同时执行,都可获得各自的锁 </br>
 * <p>
 * 1.synchronized修饰static方法与synchronized(X.class)作用一样
 *
 * @author fugaoyang
 */
public class TestRun {

    public static void main(String[] args) throws Exception {
        Service service = new Service();
        Thread threadA = new Thread("A") {
            @Override
            public void run() {
                service.printA();
            }
        };

        Thread threadB = new Thread("B") {
            @Override
            public void run() {
                service.printB();
            }
        };

        Thread threadC = new Thread("C") {
            @Override
            public void run() {
                service.printC();
            }
        };

        Thread threadD = new Thread("D") {
            @Override
            public void run() {
                service.printD();
            }
        };

        Thread threadE = new Thread("E") {
            @Override
            public void run() {
                service.printE();
            }
        };

        Thread threadF = new Thread("F") {
            @Override
            public void run() {
                service.printF();
            }
        };

        Thread threadG = new Thread("G") {
            @Override
            public void run() {
                service.printG();
            }
        };

        threadA.start();
        //threadB.start();
        //threadC.start();
        //threadD.start();
        //threadE.start();
        threadF.start();
        threadG.start();

        threadA.join();
        threadF.join();
        threadG.join();
    }
}