Java并发系列:synchronized 特性说明

/ JavaSE / 249浏览

保证线程安全

被synchronized修饰的内容,保证了在同一时刻,只能有一个线程访问此内容,从而实现了线程安全。
原理:当多个线程访问同一资源时,每个线程会尝试获得使用该资源的锁,如果拿到锁则执行该内容,执行后释放锁,其它线程继续竞争锁...
例举一个购买火车票的场景,初始化100张票,启动5个线程顺序的来模拟买票,然后打印买票结果:

package cn.zealon.lock;

/**
 * synchronized 锁
 */
public class UseSynchronize1 implements Runnable {

    //初始化100张火车票
    public int tickets = 100;

    /**
     * synchronized 保证线程安全
     * 说明:被synchronized修饰的内容,保证了在同一时刻,只能有一个线程访问此内容,从而实现了线程安全。
     * 原理:当多个线程访问时,每个线程会尝试获得锁,如果拿到锁则执行该内容,执行后释放锁,其它线程继续竞争锁...
     */
    @Override
    public void run() {
        tickets--;
        System.out.println(Thread.currentThread().getName()+":"+tickets);
    }

    public static void main(String[] args){

        UseSynchronize1 myThread = new UseSynchronize1();

        Thread t1 = new Thread(myThread,"t1");
        Thread t2 = new Thread(myThread,"t2");
        Thread t3 = new Thread(myThread,"t3");
        Thread t4 = new Thread(myThread,"t4");
        Thread t5 = new Thread(myThread,"t5");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

在没有使用synchronized时,打印结果每次都不一样,是因为线程执行先后顺序是有CPU随机分配的,所以结果可能没有按票的顺序输出:

t1:98
t3:97
t2:98
t4:96
t5:95

在run方法上增加synchronized修饰,来保证正确结果输出:

@Override
    public synchronized void run() {
        tickets--;
        System.out.println(Thread.currentThread().getName()+":"+tickets);
    }

这是看一下结果,5个线程来模拟购票,每次剩余票量递减,应该输出为,99、98、97、96、95。
如下输出:

t1:99
t3:98
t2:97
t4:96
t5:95

无论运行多少次,无论线程执行顺序的变化是怎么样的,都会以正确的票量结果输出,保证了线程安全。

多个对象多个锁

被synchronized所修饰的内容,虽然保证了线程安全,但是需要在同一个对象上才可以,如果实例化多个对象,那么synchronized的锁在多个对象之间将不起作用。
也就是说,被synchronized修饰的方法,每个实例化的对象都独有一把锁。

看一个例子,一个java类方法,根据到达目的地,设置票价,这里个方法使用synchronized所修饰,那么执行2个线程,分别调用此方法,理论上应该打印目的地1,目的地1的票价,然后打印目的地2,目的地2的票价。
用例代码:

package cn.zealon.lock;

/**
 * 多个对象多个锁的特性
 */
public class UseSynchronize2 {

    //票价
    private static int ticketPrice = 0;

    /**
     * 根据到达目的地,设置票价
     * @param place
     *            目的地
     */
    public synchronized void arrivePlace(String place){
        if(place.equals("拉萨")){
            ticketPrice = 500;
            System.out.println("place:"+place+",set ticketPrice over!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else if(place.equals("漠河")){
            ticketPrice = 350;
            System.out.println("place:漠河,set ticketPrice over!");
        }
        System.out.println("place:"+place+",ticketPrice:"+ticketPrice);
    }

    public static void main(String[] args){
        UseSynchronize2 m1 = new UseSynchronize2();
        UseSynchronize2 m2 = new UseSynchronize2();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                m1.arrivePlace("拉萨");
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                m2.arrivePlace("漠河");
            }
        });
        t1.start();
        t2.start();
    }
}

执行结果:

place:拉萨,set ticketPrice over!
place:漠河,set ticketPrice over!
place:漠河,ticketPrice:350
place:拉萨,ticketPrice:350

发现结果并没有按照预期的显示,说明synchronized并没有起到锁的作用,因为每个对象都有自己的锁,所以才会有此问题。

把锁定义在类级别

那么如果避免这种问题呢?我们可以在方法上,增加 static 修饰,使synchronized的锁定义在class级上,保证了并发访问时,只能有一个线程能访问。
用例代码:

package cn.zealon.lock;

/**
 * 多个对象多个锁的特性
 */
public class UseSynchronize2 {

    //票价
    private static int ticketPrice = 0;

    /**
     * 根据到达目的地,设置票价
     * @param place
     *            目的地
     */
    public static synchronized void arrivePlace(String place){
        if(place.equals("拉萨")){
            ticketPrice = 500;
            System.out.println("place:"+place+",set ticketPrice over!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else if(place.equals("漠河")){
            ticketPrice = 350;
            System.out.println("place:漠河,set ticketPrice over!");
        }
        System.out.println("place:"+place+",ticketPrice:"+ticketPrice);
    }

    public static void main(String[] args){
        UseSynchronize2 m1 = new UseSynchronize2();
        UseSynchronize2 m2 = new UseSynchronize2();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                m1.arrivePlace("拉萨");
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                m2.arrivePlace("漠河");
            }
        });

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

好了,这时看下运行结果:

place:拉萨,set ticketPrice over!
place:拉萨,ticketPrice:500
place:漠河,set ticketPrice over!
place:漠河,ticketPrice:350

已经起到锁的作用了。