java synchronized关键字的用法

0.先导的问题代码

    下面的代码演示了一个计数器,两个线程同时对i进行累加的操做,各执行1000000次.咱们指望的结果确定是i=2000000.可是咱们屡次执行之后,会发现i的值永远小于2000000.这是由于,两个线程同时对i进行写入的时候,其中一个线程的结果会覆盖另一个.java

public class AccountingSync implements Runnable {
    static int i = 0;
    public void increase() {
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        AccountingSync accountingSync = new AccountingSync();

        Thread t1 = new Thread(accountingSync);
        Thread t2 = new Thread(accountingSync);

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(i);
    }
}

    要从根本上解决这个问题,咱们必须保证多个线程在对i进行操做的时候,要彻底的同步.也就是说到A线程对i进行写入的时候,B线程不只不能够写入,连读取都不能够.
安全


1.synchronized关键字的做用

    关键字synchronized的做用其实就是实现线程间的同步.它的工做就是对同步的代码进行加锁,使得每一次,只能有一个线程进入同步块,从而保证线程间的安全性.就像上面的代码中,i++的操做只能同时又一个线程在执行.
ide

2.synchronized关键字的用法

  • 指定对象加锁:对给定的对象进行加锁,进入同步代码块要得到给定对象的锁spa

  • 直接做用于实例方法:至关于对当前实例加锁,进入同步代码块要得到当前实例的锁(这要求建立Thread的时候,要用同一个Runnable的实例才能够)线程

  • 直接做用于静态方法:至关于给当前类加锁,进入同步代码块前要得到当前类的锁code

2.1指定对象加锁

    下面的代码,将synchronized做用于一个给定的对象.这里有一个注意的,给定对象必定要是static的,不然咱们每次new一个线程出来,彼此并不共享该对象,加锁的意义也就不存在了.
对象

public class AccountingSync implements Runnable {
    final static Object OBJECT = new Object();

    static int i = 0;
    public void increase() {
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            synchronized (OBJECT) {
                increase();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new AccountingSync());
        Thread t2 = new Thread(new AccountingSync());

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(i);
    }
}

2.2直接做用于实例方法

    synchronized关键字做用于实例方法,就是说在进入increase()方法以前,线程必须得到当前实例的锁.这就要求咱们,在建立Thread实例的时候,要使用同一个Runnable的对象实例.不然,线程的锁都不在同一个实例上面,无从去谈加锁/同步的问题了.
同步

public class AccountingSync implements Runnable {
    static int i = 0;
    public synchronized void increase() {
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        AccountingSync accountingSync = new AccountingSync();

        Thread t1 = new Thread(accountingSync);
        Thread t2 = new Thread(accountingSync);

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(i);
    }
}

    请注意main方法的前三行,说明关键字做用于实例方法上的正确用法.
源码

2.3直接做用于静态方法

    将synchronized关键字做用在static方法上,就不用像上面的例子中,两个线程要指向同一个Runnable方法.由于方法块须要请求的是当前类的锁,而不是当前实例,线程间仍是能够正确同步的.io

public class AccountingSync implements Runnable {
    static int i = 0;
    public static synchronized void increase() {
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new AccountingSync());
        Thread t2 = new Thread(new AccountingSync());

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(i);
    }
}

 


3.错误的加锁

    从上面的例子里,咱们知道,若是咱们须要一个计数器应用,为了保证数据的正确性,咱们天然会须要对计数器加锁,所以,咱们可能会写出下面的代码:

public class BadLockOnInteger implements Runnable {
    static Integer i = 0;
    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            synchronized (i) {
                i++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        BadLockOnInteger badLockOnInteger = new BadLockOnInteger();

        Thread t1 = new Thread(badLockOnInteger);
        Thread t2 = new Thread(badLockOnInteger);

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(i);
    }
}

    当咱们运行上面代码的时候,会发现输出的i很小.这说明线程并无安全.

    要解释这个问题,要从Integer提及:在Java中,Integer属于不变对象,和String同样,对象一旦被建立,就不能被修改了.若是你有一个Integer=1,那么它就永远都是1.若是你想让这个对象=2呢?只能从新建立一个Integer.每次i++以后,至关于调用了Integer的valueOf方法,咱们看一下Integer的valueOf方法的源码:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

    Integer.valueOf()其实是一个工厂方法,他会倾向于返回一个新的Integer对象,并把值从新复制给i;

    因此,咱们就知道问题所在的缘由,因为在多个线程之间,因为i++以后,i都指向了一个新的对象,因此线程每次加锁可能都加载了不一样的对象实例上面.解决方法很简单,使用上面的3种synchronize的方法之一就能够解决了.

相关文章
相关标签/搜索