线程同步--synchronized详解

在Java的多线程中有两种编程模型:异步编程模型和同步编程模型。
假设t1和t2为两个线程,则
异步编程模型: t1线程执行t1的,t2线程执行t2的,两个线程之间谁也不等谁
同步编程模型:t1线程和t2线程执行,当t1线程必须等t2线程之行结束之后,t1线程才能执行,这是同步编程模型。

为什么要引入同步呢?
为了数据的安全。尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制。 线程同步机制使程序变成了(等同于)单线程。

什么时候要同步呢?
第一、必须是多线程环境
第二、共享同一个数据 [ 注意:该数据必须是类中的变量(也即实例变量)对应的数据]
第三、共享的数据涉及到修改操作
必须同时满足上面的三个条件才用同步模型。

以下示例演示采用异步编程对共享数据进行修改操作,看会出现什么问题?

/*
 * 以下程序演示取款例子,在不使用线程同步机制,
 * 多线程同时对同一个账户进行操作,会出现什么问题?
 * 
 */
public class Test {

    public static void main(String[] args) throws InterruptedException {
        // 创建一个公共账户
        Account act = new Account("num-001", 1000.0);
        MyRunnable myRunnable = new MyRunnable(act);
        // 创建两个线程,对同一个账户取款
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);

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

    }
}

class MyRunnable implements Runnable {
    Account act;

    public MyRunnable(Account act) {
        this.act = act;
    }

    @Override
    public void run() {
        act.withdraw(100.0);
        System.out.println("取款100.0成功,余额为:" + act.getBalance());

    }

}

class Account {
    private String actnum;
    private Double balance;

    public Account(String actnum, double balance) {
        this.actnum = actnum;
        this.balance = balance;
    }

    public String getActnum() {
        return actnum;
    }

    public void setActnum(String actnum) {
        this.actnum = actnum;
    }

    public Double getBalance() {
        return balance;
    }

    public void setBalance(Double balance) {
        this.balance = balance;
    }

    // 对外提供一个取款的方法
    public void withdraw(double money) {
        // 计算余额
        double after = balance - money;
        // 延迟
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 更新
        this.setBalance(after);

    }

}

运行结果如图 6-1 所示:
这里写图片描述

图 6-1 不使用同步对共享数据进行修改操作会出现“脏读”

使用同步可以解决上述问题。同步的方式大概可以分为两种:同步方法和同步语句块。下面分别用这两种方式解决上述问题。
先用同步语句块解决,对上面的代码做如下修改

//把需要同步的代码放到同步语句块中
        synchronized (this) {
            // 计算余额
            double after = balance - money;
            // 延迟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // 更新
            this.setBalance(after);
        }

运行结果如图 6-2 所示
这里写图片描述

图 6-2 使用同步语句块解决

然后用同步方法解决,对上面的代码做如下修改

// 对外提供一个取款的方法,在方法上加上synchronized关键词
    public synchronized void withdraw(double money) {
            // 计算余额
            double after = balance - money;
            // 延迟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // 更新
            this.setBalance(after);



    }

运行结果如图 6-3 所示
这里写图片描述

图 6-3 使用同步方法解决

使用synchronized(this)语句块同步的原理:t1线程和t2线程
t1线程在执行过程中,遇到了synchronized关键字,就会去找this的对象锁, 如果找到this对象锁,则进入同步语句块中执行程序。当同步语句块中的代码执行结束后, t1线程归还this的对象锁。

在t1线程执行同步语句块的过程中,如果t2线程也过来执行以下代码,也遇到synchronized 关键字,所以也去找this的对象锁,但是该对象锁被t1线程持有,只能在这等待this对象的归还。

使用synchronized方法同步的原理:t1线程和t2线程
每个对象都有一个锁(也称监控器monitor),它是对象生来就有的东西(因此你不必为此写任何代码)。
t1线程在执行过程中,遇到了synchronized方法,就会去找this的对象锁, 如果找到this对象锁,这个对象就被锁住了。当同步方法执行结束后,t1线程归还这个对象的对象锁,在方法返回并且解锁之前,谁也不能调用同一个对象的其它synchronized方法。

在t1线程执行同步方法中的代码过程中,如果t2线程也过来执行以下代码,也遇到synchronized 关键字,所以也去找this的对象锁,但是该对象锁被t1线程持有,只能在这等待this对象的归还。


同步方法又能细分为:普通同步方法和静态同步方法。虽然这两种方法同步效果一样,但是本质却是不同的:synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到普通方法上是给对象上锁。下面用一个示例验证不是同一个锁

/*
 * 验证静态同步方法和普通同步方法的本质区别:
 * 静态同步方法是给Class类上锁,普通同步方法是给对象上锁。
 */
public class Test {

    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        MyRunnableA myRunnableA = new MyRunnableA(service);
        Thread a = new Thread(myRunnableA);
        a.setName("A");
        a.start();

        MyRunnableB myRunnableB = new MyRunnableB(service);
        Thread b = new Thread(myRunnableB);
        b.setName("B");
        b.start();

        MyRunnableC myRunnableC = new MyRunnableC(service);
        Thread c = new Thread(myRunnableC);
        c.setName("C");
        c.start();

    }
}

class MyRunnableA implements Runnable {
    private Service service;

    public MyRunnableA(Service service) {
        this.service = service;

    }

    @Override
    public void run() {
        service.printA();
    }

}

class MyRunnableB implements Runnable {
    private Service service;

    public MyRunnableB(Service service) {
        this.service = service;

    }

    @Override
    public void run() {
        service.printB();
    }

}

class MyRunnableC implements Runnable {
    private Service service;

    public MyRunnableC(Service service) {
        this.service = service;

    }

    @Override
    public void run() {
        service.printC();
    }

}

/*
 * 提供两个静态同步方法和一个普通同步方法,通过输出结果(异步或同步)来判断是否是不同的锁
 */
class Service {
    public synchronized static void printA() {
        try {
            System.out.println(
                    "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
            // 设置延迟
            Thread.sleep(3000);
            System.out.println(
                    "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized static void printB() {

        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
    }

    public synchronized void printC() {
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printC");
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printC");

    }

}

运行结果如图 6-4 所示:
这里写图片描述

图 6-4 方法printC()为异步执行

异步的原因是持有不同的锁,printC()方法是对象锁,printA()方法是Class锁,而Class锁可以对类的所有对象实例起作用。下面用一个示例进行验证

/*
 * 验证Class锁可以对类的所有对象实例起作用
 */
public class Test {

    public static void main(String[] args) throws InterruptedException {
        // 设置两个不同的对象实例
        Service service1 = new Service();
        Service service2 = new Service();
        MyRunnableA myRunnableA = new MyRunnableA(service1);
        Thread a = new Thread(myRunnableA);
        a.setName("A");
        a.start();

        MyRunnableB myRunnableB = new MyRunnableB(service2);
        Thread b = new Thread(myRunnableB);
        b.setName("B");
        b.start();
    }
}

class MyRunnableA implements Runnable {
    private Service service;

    public MyRunnableA(Service service) {
        this.service = service;

    }

    @Override
    public void run() {
        service.printA();
    }

}

class MyRunnableB implements Runnable {
    private Service service;

    public MyRunnableB(Service service) {
        this.service = service;

    }

    @Override
    public void run() {
        service.printB();
    }

}

/*
 * 提供两个静态同步方法
 */
class Service {
    public synchronized static void printA() {
        try {
            System.out.println(
                    "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
            // 设置延迟
            Thread.sleep(3000);
            System.out.println(
                    "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized static void printB() {

        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
    }

}

运行结果如同 6-5 所示
这里写图片描述

图 6-5 虽然是不同对象,但静态同步方法还是同步执行


同步语句块又能细分为:同步synchronized(class)语句块、同步synchronized(this)语句块、同步synchronized(非this对象x)语句块和同步synchronized(string)语句块
同步synchronized(this)语句块在上面已经演示过,下面不再演示。
同步synchronized(class)语句块的作用其实和synchronized static方法的作用一样,下面用一个示例进行演示
对图 6-5 上面的代码做以下更改

class Service {
    public static void printA() {
        synchronized (Service.class) {
            try {
                System.out.println(
                        "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
                // 设置延迟
                Thread.sleep(3000);
                System.out.println(
                        "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public synchronized static void printB() {
        synchronized (Service.class) {
            System.out.println(
                    "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
            System.out.println(
                    "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
        }
    }

}

运行结果如同 6-6 所示
这里写图片描述

图 6-6 和静态同步方法的作用一样,也是同步执行

同步synchronized(string)语句块会因为String常量池的特性而带来问题,因此同步synchronized 语句块都不使用String作为锁对象,而改用其它,比如new Object()实例化一个Object对象,但它并不放入缓存中。

在JVM中具有String常量池缓存的功能,所以如图 6-7 所示的结果为true。
这里写图片描述

图 6-7 Sting常量池缓存

同步synchronized(非this对象x)语句块具有一定的优点:如果在一个类中有很多个synchronized 方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步语句块非this对象,则synchronized (非this对象x)语句块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可以大大提高运行效率。
“synchronized (非this对象x)”格式的写法是将x对象本身作为“对象监视器”,这样就可以得出以下3个结论:

  1. 当多个线程同时执行synchronized (x){}同步语句块时呈同步效果。
  2. 当其他线程执行x对象中synchronized 同步方法时呈同步效果。
  3. 当其他线程执行x对象方法里面的synchronized (this)语句块时也呈同步效果。

注意:如果其他线程调用不加synchronized 关键字的方法时,还是异步调用。

验证第一个结论:当多个线程同时执行synchronized (x){}同步语句块时呈同步效果,示例如下

/*验证当多个线程同时执行synchronized (x){}同步语句块时呈同步效果
*/
public class Test {

    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        Object object = new Object();
        ThreadA a = new ThreadA(service, object);
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(service, object);
        b.setName("B");
        b.start();
    }
}

class ThreadA extends Thread {
    private Service service;
    private Object object;

    public ThreadA(Service service, Object object) {
        super();
        this.service = service;
        this.object = object;

    }

    @Override
    public void run() {
        super.run();
        service.testMethod1(object);
    }

}

class ThreadB extends Thread {
    private Service service;
    private Object object;

    public ThreadB(Service service, Object object) {
        super();
        this.service = service;
        this.object = object;

    }

    @Override
    public void run() {
        super.run();
        service.testMethod1(object);
    }

}

class Service {

    public void testMethod1(Object object) {
        synchronized (object) {
            try {
                System.out.println("testMethod1--getLock     time:" + System.currentTimeMillis() + "   run ThreadName:"
                        + Thread.currentThread().getName());
                Thread.sleep(2000);
                System.out.println("testMethod1--releaseLock time:" + System.currentTimeMillis() + "   run ThreadName:"
                        + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

运行结果如图 6-8 所示
这里写图片描述

图 6-8 同步调用

同步的原因是使用了同一个“对象监视器”,如果使用不同的“对象监视器”就会异步执行。
对上面的代码作以下更改:

public class Test {

    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        ThreadA a = new ThreadA(service, new Object());
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(service, new Object());
        b.setName("B");
        b.start();
    }
}

运行结果如图 6-9 所示
这里写图片描述

图 6-9 异步调用

验证第二个结论:当其他线程执行x对象中synchronized 同步方法时呈同步效果,示例如下

public class Test {

    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        MyObject object = new MyObject();
        ThreadA a = new ThreadA(service, object);
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(service, object);
        b.setName("B");
        b.start();
    }
}

class ThreadA extends Thread {
    private Service service;
    private MyObject object;

    public ThreadA(Service service, MyObject object) {
        super();
        this.service = service;
        this.object = object;

    }

    @Override
    public void run() {
        super.run();
        service.testMethod1(object);
    }

}

class ThreadB extends Thread {
    private Service service;
    private MyObject object;

    public ThreadB(Service service, MyObject object) {
        super();
        this.service = service;
        this.object = object;

    }

    @Override
    public void run() {
        super.run();
        object.speedPrintString();
    }

}

class Service {

    public void testMethod1(MyObject object) {
        synchronized (object) {
            try {
                System.out.println("testMethod1--getLock     time:" + System.currentTimeMillis() + "   run ThreadName:"
                        + Thread.currentThread().getName());
                Thread.sleep(5000);
                System.out.println("testMethod1--releaseLock time:" + System.currentTimeMillis() + "   run ThreadName:"
                        + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class MyObject {
    public synchronized void speedPrintString() {
        System.out.println("speedPrintString--getLock     time:" + System.currentTimeMillis() + "   run ThreadName:"
                + Thread.currentThread().getName());
        System.out.println("-------------------");
        System.out.println("speedPrintString--releaseLock time:" + System.currentTimeMillis() + "   run ThreadName:"
                + Thread.currentThread().getName());
    }
}

运行结果如图 6-10 所示
这里写图片描述

图 6-10 同步效果

验证第三个结论:当其他线程执行x对象方法里面的synchronized (this)语句块时也呈同步效果,示例如下
对上面的代码作以下更改

class MyObject {
    public  void speedPrintString() {
        synchronized (this) {
            System.out.println("speedPrintString--getLock     time:" + System.currentTimeMillis() + "   run ThreadName:"
                    + Thread.currentThread().getName());
            System.out.println("-------------------");
            System.out.println("speedPrintString--releaseLock time:" + System.currentTimeMillis() + "   run ThreadName:"
                    + Thread.currentThread().getName());
        }

    }
}

运行结果如图 6-11 所示
这里写图片描述

图 6-11 也是同步效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值