线程安全的概念和Synchronized(读书笔记)

     并行程序开发的一大关注重点就是线程安全,一般来说,程序并行化为了获取更多的执行效率,但前提是,高效率不能以牺牲正确性为代价,线程安全就是并行程序的根本和根基.volatile并不能真正保证线程安全,他只能确保一个线程修改了数据后,其他线程能够看到这个改动!
public class AccountingVol implements Runnable {
    static AccountingVol instance = new AccountingVol();
    static volatile int i = 0;

    public static void increase() {
        i++;
    }

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println("i = " + i);
    }
}
 
上面代码显示了一个计数器,两个线程同时对i进行累加操作,各执行10000000次.我们希望得到的结果是20000000,但事实并非总是如此,得到的i总是小于预期结果!这就是线程不安全的恶果.
     为了解决这个问题Java提供了 synchronized来实现这个功能. 
  • synchronized的作用是实现线程间的同步问题,他的工作时对同步的代码加锁.使得每一次,只能有一个线程进入同步块,从而保证线程间的安全性,
关键字synchronized可以有很多用法,
  • 指定加锁对象:对给定加锁.进入同步代码前要获得给定对象的锁.
  • 直接作用于实例方法,相当于对当前实例加锁,进入同步代码前要获得当前实例的锁
  • 直接作用于静态方法,.相当于对当前类加锁,进入同步代码前要获得当前类的锁
我们队上边的例子修改,让他线程安全:
public class AccountingSync implements Runnable {
    static AccountingSync instance = new AccountingSync();
    static int i = 0;

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            synchronized (instance) {
                i++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println("i = " + i);
    }
}

//上述代码还可以写成下面的形式:
public class AccountingSync2 implements Runnable {
    static AccountingSync2 instance = new AccountingSync2();
    static int i = 0;

    public synchronized void increase() {
        i++;
    }

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

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println("i = " + i);
    }
}

//一种错误的同步方式如下:
public class AccountingSyncBad implements Runnable {

    static int i = 0;

    public synchronized void increase() {
        i++;
    }

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new AccountingSyncBad());
        Thread t2 = new Thread(new AccountingSyncBad());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("i = " + i);
    }
}
 
虽然我们对increase()方法 做了同步处理,但是2个线程指向的是不同的实例.换言之就是.两个线程使用的是两把不同的锁.因此无法保证线程安全
修改如下:

public static synchronized void increase() {
    i++;
}
 
这样increase()方式就是类方法,而不是实例方法,因此线程还是可以同步的.
 
     除了用于线程同步,确保线程安全之外,synchronized还可以确保线程间的可见性和有序性.从可见性角度上讲,synchronized可以完全替代volatile的功能,只是使用上没有那么方便,就有序性而言,由于synchronized限制每一次只能有一个线程可以访问同步快,.因此 无论同步块内代码如何被乱序执行,只要确保串行语义一致,那么执行结果总是一样的.而其他访问线程.又必须在获得锁后方能进入代码块读取数据,因此,它们看到的最终结果并不取决于代码的执行过程,从而有序性问题自然得到了解决,换言之,被synchronized限制了多个线程是串行执行的.
原文地址:https://www.cnblogs.com/ten951/p/6171031.html