线程同步synchronized用法

多个线程共享一个变量的时候,会出现线程安全的问题,这是大家都知道的问题,那么是为什么呢?每一个线程都有自己的工作空间,当创建一个线程的时候,系统会为这个线程分配相应的空间,共享的变量也会在这个空间里。然后每个线程在各自的空间执行,并修改共享的变量。当多个线程同时修改一个变量的时候,这时候就可能会出现数据不一致的问题。下面举例说明:

public class Test {
    private static int count;
    public static void main(String[] args) {
        for (int i = 0; i < 10000;i++) {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    count++;
                    System.out.println(count);
                }
            };
            thread.start();
        }
    }

上面代码为,开启1000个线程,每个线程对count进行加1操作。按照预期,最后一个输出的count应该是10000才对。但事实上并不是,看下面输出结果:

9992
9993
9994
9995
9996
9997

由此可见,最后一个输出的是9997并不是10000。这就是一个典型的线程安全问题,导致结果达不到我们的预期。

为了解决线程安全问题,java提供了锁的机制。当线程访问被锁到的对象时,同一时间只有一个线程能够得到锁,其他线程被阻塞,只有锁被释放时,其他线程才有机会去获得锁。java是通过synchronized关键字为对象加锁的,synchronized可以修饰代码块,方法,类,静态方法。接下来分别举例说明下用法:

1.synchronized修饰代码块:同一时间只有一个线程能够执行代码块中的内容。

package volatile1;

/**
 * Created by sxb-gt on 2018/1/21.
 * 辅助线程
 */
public class AssistantThread extends Thread {
    private MainThread mainThread;
    public AssistantThread(MainThread mainThread) {
        this.mainThread = mainThread;
    }
    @Override
    public void run() {
        mainThread.add();
        mainThread.getCount().countDown();
    }
}

package volatile1;

import java.util.concurrent.CountDownLatch;

/**
 * Created by sxb-gt on 2018/1/21.
 * “主线程类”注意:该类并没有集成Thread。只是为了形象,才取的这个名字。
 */
public class MainThread {
    private Integer i = 0;
    /** 线程计数器 **/
    private CountDownLatch count;
    public CountDownLatch getCount() {
        return count;
    }
    public void setCount(CountDownLatch count) {
        this.count = count;
    }
    public  void add() {
        System.out.println("线程名称:" + Thread.currentThread().getName() + "执行开始");
        synchronized(i) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            i++;
            System.out.println(i);
        }
        System.out.println("线程名称:" + Thread.currentThread().getName() + "执行结束");
    }

    public static void main(String[] args) {
        System.out.println("主线程执行开始");
        MainThread mainThread = new MainThread();
        int length = 3;
        mainThread.setCount(new CountDownLatch(length));
        for (int i = 0; i < length; i++ ) {
            Thread thread = new AssistantThread(mainThread);
            thread.start();
        }
        try {
            mainThread.getCount().await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("i===" + mainThread.i);
        System.out.println("主线程执行结束");
    }
}

输出结果:
主线程执行开始
线程名称:Thread-2执行开始
线程名称:Thread-1执行开始
线程名称:Thread-0执行开始
1
线程名称:Thread-2执行结束
2
线程名称:Thread-0执行结束
3
线程名称:Thread-1执行结束
i===3
主线程执行结束

结论:三个线程同时开启,都执行到方法代码块的时候,只有一个线程可以进入方法代码块。

2.synchronized修饰方法:同一时间只有一个线程能够进入该方法。

上面例子中的代码不变,只改变add()方法。将代码块中的synchronized拿走,在方法声明上加上synchronized。


    public  synchronized void add() {
        System.out.println("线程名称:" + Thread.currentThread().getName() + "执行开始");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        i++;
        System.out.println(i);
        System.out.println("线程名称:" + Thread.currentThread().getName() + "执行结束");
    }


执行结果:

主线程执行开始
线程名称:Thread-0执行开始
1
线程名称:Thread-0执行结束
线程名称:Thread-1执行开始
2
线程名称:Thread-1执行结束
线程名称:Thread-2执行开始
3
线程名称:Thread-2执行结束
i===3
主线程执行结束
结论:从执行结果可以看出同一时间只有一个线程进入了同步方法。相对于修饰代码块,线程阻塞的更早。因此,个人建议,需要同步的时候优先使用阻塞代码块的方式。

3.synchronized修饰类:该类的所有实例,只有一个对象能够执行同步方法。

首先,我们调整下代码,如下:

package volatile1;

import java.util.concurrent.CountDownLatch;

/**
 * Created by sxb-gt on 2018/1/21.
 * “主线程类”注意:该类并没有集成Thread。只是为了形象,才取的这个名字。
 */
public class MainThread1 extends Thread{
    /** 线程计数器 **/
    private static CountDownLatch count;
    private static Integer i = 0;

    @Override
    public void run() {
        add();
        count.countDown();
    }

    public synchronized void add() {
        System.out.println("线程名称:" + Thread.currentThread().getName() + "执行开始");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        i++;
        System.out.println(i);
        System.out.println("线程名称:" + Thread.currentThread().getName() + "执行结束");
    }

    public static void main(String[] args) {
        System.out.println("主线程执行开始");
        int length = 3;
        count = new CountDownLatch(length);
        for (int i = 0; i < length; i++ ) {
            Thread thread = new MainThread1();
            thread.start();
        }
        try {
            count.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("i===" + i);
        System.out.println("主线程执行结束");
    }
}

此时,MainThread1就是一个线程,创建3个MainThread1的实例。然后执行,结果如下:

主线程执行开始
线程名称:Thread-1执行开始
线程名称:Thread-2执行开始
线程名称:Thread-0执行开始
1
线程名称:Thread-1执行结束
2
2
线程名称:Thread-0执行结束
线程名称:Thread-2执行结束
i===2
主线程执行结束
问题来了,线程并没有同步执行。为什么呢? 因为我们创建了三个对象,虽然在add()方法上加了synchronized,但是这个锁是对象级的,多个对象之间是不共享这个锁的。如何才能让所有的对象共享一个锁呢?接下来,再修改下代码,如下:

public void add() {
        synchronized (MainThread1.class) {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "执行开始");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            i++;
            System.out.println(i);
            System.out.println("线程名称:" + Thread.currentThread().getName() + "执行结束");
        }
    }
输出结果:

主线程执行开始
线程名称:Thread-1执行开始
1
线程名称:Thread-1执行结束
线程名称:Thread-0执行开始
2
线程名称:Thread-0执行结束
线程名称:Thread-2执行开始
3
线程名称:Thread-2执行结束
i===3
主线程执行结束
线程之间再次同步。

结论:synchronized修饰类时,作用的是该类所有的实例。所有的实例,共享一个锁。但是不建议这样做,对系统性能会有影响。

4.synchronized修饰静态方法:与修饰类效果相同,代表所有实例共享这一个方法的锁。也可用与多个对象之间的同步。

调整代码如下:

public synchronized static void add() {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "执行开始");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            i++;
            System.out.println(i);
            System.out.println("线程名称:" + Thread.currentThread().getName() + "执行结束");
    }

输出结果:

主线程执行开始
线程名称:Thread-1执行开始
1
线程名称:Thread-1执行结束
线程名称:Thread-0执行开始
2
线程名称:Thread-0执行结束
线程名称:Thread-2执行开始
3
线程名称:Thread-2执行结束
i===3
主线程执行结束

结论:静态方法也叫类方法,只在类第一次实例化的时候加载一次。因此,使用synchronized修饰静态方法也可以保证多个对象之间的同步。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值