线程同步--synchronized详解

来源:互联网 发布:淘宝大学的vip课靠谱吗 编辑:程序博客网 时间:2024/05/23 18:33

在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 也是同步效果

原创粉丝点击