Synchornized使用及原理

来源:互联网 发布:911zyz资源网新域名 编辑:程序博客网 时间:2024/06/04 22:47

Synchornized 方法

当一个线程试图访问同步代码块或对象的方法时,它首先必须得到锁,退出或抛出异常时必须释放锁。Java中的每一个对象都可以作为锁,具体表现为:
1. 对于普通同步方法,锁是当前实例对象
2. 对于同步方法块,锁是Synchonized括号里配置的对象
3. 对于静态同步方法,锁是当前类的Class对象

对于普通同步方法,锁是当前实例对象

不同线程对于同一个对象的锁互斥同步

  • 锁是当前实例对象,A线程获得了object对象的锁,B线程想在访问该同步方法必须等A线程将object对象锁释放。
package objective2.action1;public class IncreaseHander {    public static void main(String[] args) {        IncrementRunner incrementRunner1 = new IncrementRunner("a");        new Thread(incrementRunner1).start();        new Thread(incrementRunner1).start();    }}class IncrementRunner implements Runnable {    private int num;    private String name;    public IncrementRunner(String name) {        this.name = name;    }    @Override    public void run() {        add(name);    }    public synchronized void add(String name) {        if (name.equals("a")) {            num = 100;            System.out.println(Thread.currentThread().getName() + " a set over ");            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }        } else {            num = 200;            System.out.println(Thread.currentThread().getName() + " other set over");        }        System.out.println(Thread.currentThread().getName() + " " + name + " num=" + num);    }}
Thread-0 a set over Thread-0 a num=100Thread-1 a set over Thread-1 a num=100

可以看出,上面程序按线程0和线程1是按同步方式运行的。首先线程0获得对象incrementRunner1 的锁,在完成设置值和休眠后释放该对象锁,由线程1获得,继续做相同的操作。

不同线程对于不同对象的锁可以异步

  • synchornized获得的是对象锁,若是多个对象,JVM会创建多个锁
package objective2.action1;public class IncreaseHander {    public static void main(String[] args) {        IncrementRunner r1 = new IncrementRunner("a");        IncrementRunner r2 = new IncrementRunner("a");        new Thread(r1).start();        new Thread(r2).start();    }}class IncrementRunner implements Runnable {    private int num;    private String name;    public IncrementRunner(String name) {        this.name = name;    }    @Override    public void run() {        add(name);    }    public synchronized void add(String name) {        if (name.equals("a")) {            num = 100;            System.out.println(Thread.currentThread().getName() + " a set over ");            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }        } else {            num = 200;            System.out.println(Thread.currentThread().getName() + " other set over");        }        System.out.println(Thread.currentThread().getName() + " " + name + " num=" + num);    }}
Thread-1 a set over Thread-0 a set over Thread-0 a num=100Thread-1 a num=100

上面例子,两个线程分别访问同一个类的两个不同的实例的相同名称的同步方法,效果却是以异步的方式运行的。 由于创建了两个对象,在系统中产生了两个锁,线程1不需要在线程0将锁释放后才能在进入add方法。

对象锁对于非synchrnoized方法异步

  • A线程先持有object对象的锁, B线程可以以异步方式调用object对象中的非synchrnoized类型方法
package objective2.action1;public class Run {    public static void main(String[] args) {        MyObject object = new MyObject();        ObjectRunner A = new ObjectRunner("A", object);        ObjectRunner B = new ObjectRunner("B", object);        new Thread(A).start();        new Thread(B).start();    }}class ObjectRunner implements Runnable {    private String methodName;    private MyObject object;    public ObjectRunner(String methodName, MyObject object) {        this.methodName = methodName;        this.object = object;    }    @Override    public void run() {        if (methodName.equals("A")) {            System.out.println("start call method A");            object.methodA();        } else {            System.out.println("start call method B");            object.methodB();        }    }}class MyObject {    public synchronized void methodA() {        try {            String threadName = Thread.currentThread().getName();            System.out.println("begin method A ThreadName" + threadName);            Thread.sleep(2000);            System.out.println("end method A ThreadName" + threadName);        } catch (InterruptedException e) {            e.printStackTrace();        }    }    public void methodB() {        try {            String threadName = Thread.currentThread().getName();            System.out.println("begin method B ThreadName=" + threadName);            Thread.sleep(2000);            System.out.println("end method B ThreadName=" + threadName);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}
start call method Astart call method Bbegin method B ThreadName=Thread-1begin method A ThreadNameThread-0end method B ThreadName=Thread-1end method A ThreadNameThread-0

将上面程序做一个简单改动,运行结果完全不一样:程序运行的顺序是异步的。

对象锁对于synchrnoized方法同步

  • A线程先持有object对象的锁,B线程如果这时调用object对象中的synchrnoized类型方法需要等待
package objective2.action1;public class Run {    public static void main(String[] args) {        MyObject object = new MyObject();        ObjectRunner A = new ObjectRunner("A", object);        ObjectRunner B = new ObjectRunner("B", object);        new Thread(A).start();        new Thread(B).start();    }}class ObjectRunner implements Runnable {    private String methodName;    private MyObject object;    public ObjectRunner(String methodName, MyObject object) {        this.methodName = methodName;        this.object = object;    }    @Override    public void run() {        if (methodName.equals("A")) {            System.out.println("start call method A");            object.methodA();        } else {            System.out.println("start call method B");            object.methodB();        }    }}class MyObject {    public synchronized void methodA() {        try {            String threadName = Thread.currentThread().getName();            System.out.println("begin method A ThreadName" + threadName);            Thread.sleep(2000);            System.out.println("end method A ThreadName" + threadName);        } catch (InterruptedException e) {            e.printStackTrace();        }    }    public synchronized void methodB() {        try {            String threadName = Thread.currentThread().getName();            System.out.println("begin method B ThreadName=" + threadName);            Thread.sleep(2000);            System.out.println("end method B ThreadName=" + threadName);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}
start call method Astart call method Bbegin method A ThreadNameThread-0end method A ThreadNameThread-0begin method B ThreadName=Thread-1end method B ThreadName=Thread-1

线程0先持有object对象的锁,线程1如果这时调用object对象中的synchrnoized类型方法需要等待,即使调用的是不同的方法。
也就是说,对象的锁能锁住对象所有的synchrnoized方法。
总之还是那句话,对于普通同步方法,锁是当前实例对象。方法A,方法B的锁都是object对象。一个线程访问该方法(拿到锁),其它线程必须等待。

synchrnoized锁可重入

  • synchrnoized锁可重入
    当某个线程请求一个由其它线程持有的锁时,发出请求的线程就会堵塞。然而,由于内置锁是可入的,因此如果某个线程试图获得一个已经由自己或自己父类持有的锁,那么这个请求就会成功。
package objective2.action1;public class RunService {    public static void main(String[] args) {        MyThread thread = new MyThread();        thread.start();    }}class MyThread extends Thread {    @Override    public void run() {        Service service = new Service();        service.service1();    }}class Service {    public synchronized void service1() {        System.out.println(Thread.currentThread().getName() + " call Service 1");        service2();    }    public synchronized void service2() {        System.out.println(Thread.currentThread().getName() + " call Service 2");    }}
Thread-0 call Service 1Thread-0 call Service 2

“可重入锁”的概念是:自己可以再次获取自己的内部锁。 如上面程序运行,在调用方法service1时,线程0获取service对象的锁,此时锁还没有释放,调用方法service2时,又一次获取到了service对象的锁。如果同一线程不可重入锁,就会造成死锁。

  • 可重入锁支持在父子继承环境中
package objective2.action1;public class RunService {    public static void main(String[] args) {        MyThread thread = new MyThread();        thread.start();    }}class MyThread extends Thread {    @Override    public void run() {        Service service = new SubService();        service.service2();    }}class Service {    public synchronized void service1() {        System.out.println(Thread.currentThread().getName() + " call Service 1");    }    public synchronized void service2() {        System.out.println(Thread.currentThread().getName() + " call Service 2");    }}class SubService extends Service {    public synchronized void service2() {        System.out.println(Thread.currentThread().getName() + " call Sub Service 2");        super.service1();    }}
Thread-0 call Sub Service 2Thread-0 call Service 1

同步不能继承

  • 同步不具有继承性
package objective2.action1;public class TestService {    public static void main(String[] args) {        Sub sub = new Sub();        ServiceRunner serviceRunner = new ServiceRunner(sub);        new Thread(serviceRunner, "A").start();        new Thread(serviceRunner, "B").start();    }}class ServiceRunner implements Runnable {    private Sub sub;    public ServiceRunner(Sub sub) {        this.sub = sub;    }    @Override    public void run() {        sub.service();    }}class Main {    public synchronized void service() {        String threadName = Thread.currentThread().getName();        System.out.println("in main, ThreadName=" + threadName + " beginTime=" + System.currentTimeMillis());        sleep(1000);        System.out.println("in main, ThreadName=" + threadName + " endTime=" + System.currentTimeMillis());    }    protected void sleep(long time) {        try {            Thread.sleep(time);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }}class Sub extends Main {    public void service() {        String threadName = Thread.currentThread().getName();        System.out.println("in Sub, ThreadName=" + threadName + " beginTime=" + System.currentTimeMillis());        sleep(1000);        System.out.println("in Sub, ThreadName=" + threadName + " endTime=" + System.currentTimeMillis());        super.service();    }}
in Sub, ThreadName=B beginTime=1496618308264in Sub, ThreadName=A beginTime=1496618308264in Sub, ThreadName=B endTime=1496618309266in Sub, ThreadName=A endTime=1496618309266in main, ThreadName=B beginTime=1496618309266in main, ThreadName=B endTime=1496618310267in main, ThreadName=A beginTime=1496618310267in main, ThreadName=A endTime=1496618311268

父类Main的方法加synchronized,子类Sub继承该方法但是不加synchronized,从运行结果看:A,B两个线程几乎同时进入子类非同步方法,是异步执行的。但是在主类中由于加了锁,必须B释放锁A才能进入。

  • 子类与父类是同一个锁
package objective2.action1;public class TestService {    public static void main(String[] args) {        Sub sub = new Sub();        ServiceRunner serviceRunner = new ServiceRunner(sub);        new Thread(serviceRunner, "A").start();        new Thread(serviceRunner, "B").start();    }}class ServiceRunner implements Runnable {    private Sub sub;    public ServiceRunner(Sub sub) {        this.sub = sub;    }    @Override    public void run() {        sub.service();    }}class Main {    public synchronized void service() {        String threadName = Thread.currentThread().getName();        System.out.println("in main, ThreadName=" + threadName + " beginTime=" + System.currentTimeMillis());        sleep(1000);        System.out.println("in main, ThreadName=" + threadName + " endTime=" + System.currentTimeMillis());    }    protected void sleep(long time) {        try {            Thread.sleep(time);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }}class Sub extends Main {    public synchronized void service() {        String threadName = Thread.currentThread().getName();        System.out.println("in Sub, ThreadName=" + threadName + " beginTime=" + System.currentTimeMillis());        sleep(1000);        System.out.println("in Sub, ThreadName=" + threadName + " endTime=" + System.currentTimeMillis());        super.service();    }}
in Sub, ThreadName=A beginTime=1496618631925in Sub, ThreadName=A endTime=1496618632926in main, ThreadName=A beginTime=1496618632926in main, ThreadName=A endTime=1496618633926in Sub, ThreadName=B beginTime=1496618633926in Sub, ThreadName=B endTime=1496618634927in main, ThreadName=B beginTime=1496618634927in main, ThreadName=B endTime=1496618635927

在子类中也加上锁,A,B两个线程运行同步,只有A释放了锁,B才能拿到锁进入方法。
不管子类还是父类,在这个例子中,锁是sub对象,一个线程拿到了该对象的锁,其它线程必须等待该线程释放。

这么多例子,其实就是一句话, 对于普通同步方法,锁是当前实例对象。

对于同步方法块,锁是Synchonized括号里配置的对象

synchornized 方法的弊端

对于普通同步方法,锁是当前实例对象,一个线程拿到了该对象的锁,其它线程必须等待该线程释放。虽然解决了线程安全的问题,但是,很大程度上也牺牲了响应速度,性能。
还是用【买票例子】:5个售票员出售10张票,不能不同售票员出售同一张票,每买一张耗时1s,直到票出售完为止。
如下程序使用synchornized 方法实现该功能,虽然解决了线程安全的问题,但弊端很明显,任何一个线程拿到该对象的锁,其他线程都得等它释放锁才能进入该方法。10张票卖完要10s,性能很差。

package objective2.action1;import java.util.concurrent.CountDownLatch;public class Counter {    private static int tickets = 10;    private static int threadNum = 5;    private static CountDownLatch timeLatch = new CountDownLatch(threadNum);    public static void main(String[] args) {        long startTime = System.currentTimeMillis();        Runnable counter = new CounterRunner(tickets, timeLatch);        Thread[] threads = new Thread[threadNum];        for (int i = 0; i < threadNum; i++) {            threads[i] = new Thread(counter, "售票员" + i);        }        for (Thread thread : threads) {            thread.start();        }        try {            timeLatch.await();        } catch (InterruptedException e) {            e.printStackTrace();        }        long takeTime = System.currentTimeMillis() - startTime;        System.out.println("takeTime=" + takeTime + "ms");    }}class CounterRunner implements Runnable {    private int tickets;    private CountDownLatch timeLatch;    public CounterRunner(int tickets, CountDownLatch timeLatch) {        this.tickets = tickets;        this.timeLatch = timeLatch;    }    @Override    public void run() {        while (tickets > -1) {            if (saleTickets()) {                break;            }        }    }    /**     *      * 买票实现     *      * @return 卖完返回true,没卖完返回false     */    private synchronized boolean saleTickets() {        String threadName = Thread.currentThread().getName();        if (tickets == 0) {            System.out.println(threadName + " 没票了");            timeLatch.countDown();            return true;        } else {            System.out.println(threadName + " 出售票号" + tickets--);            sleep(1000);        }        return false;    }    public static void sleep(long millis) {        try {            Thread.sleep(millis);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}
售票员0 出售票号10售票员0 出售票号9售票员0 出售票号8售票员3 出售票号7售票员4 出售票号6售票员4 出售票号5售票员2 出售票号4售票员1 出售票号3售票员2 出售票号2售票员2 出售票号1售票员4 没票了售票员3 没票了售票员0 没票了售票员2 没票了售票员1 没票了takeTime=10009ms

使用同步代码块解决弊端

package objective2.action1;import java.util.concurrent.CountDownLatch;public class Counter {    private static int tickets = 10;    private static int threadNum = 5;    private static CountDownLatch timeLatch = new CountDownLatch(threadNum);    public static void main(String[] args) {        long startTime = System.currentTimeMillis();        Runnable counter = new CounterRunner(tickets, timeLatch);        Thread[] threads = new Thread[threadNum];        for (int i = 0; i < threadNum; i++) {            threads[i] = new Thread(counter, "售票员" + i);        }        for (Thread thread : threads) {            thread.start();        }        try {            timeLatch.await();        } catch (InterruptedException e) {            e.printStackTrace();        }        long takeTime = System.currentTimeMillis() - startTime;        System.out.println("takeTime=" + takeTime + "ms");    }}class CounterRunner implements Runnable {    private int tickets;    private CountDownLatch timeLatch;    public CounterRunner(int tickets, CountDownLatch timeLatch) {        this.tickets = tickets;        this.timeLatch = timeLatch;    }    @Override    public void run() {        while (tickets > -1) {            if (saleTickets()) {                break;            }        }    }    /**     *      * 买票实现     *      * @return 卖完返回true,没卖完返回false     */    private boolean saleTickets() {        String threadName = Thread.currentThread().getName();        if (tickets == 0) {            System.out.println(threadName + " 没票了");            timeLatch.countDown();            return true;        } else {            synchronized (this) {                System.out.println(threadName + " 出售票号" + tickets--);            }            sleep(1000);        }        return false;    }    public static void sleep(long millis) {        try {            Thread.sleep(millis);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}
售票员0 出售票号10售票员3 出售票号9售票员4 出售票号8售票员1 出售票号7售票员2 出售票号6售票员1 出售票号5售票员0 出售票号4售票员3 出售票号3售票员4 出售票号2售票员2 出售票号1售票员3 没票了售票员0 没票了售票员1 没票了售票员4 没票了售票员2 没票了takeTime=2004ms

从运行结果看,使用Synchornized代码块,在线程安全情况下,运行时间缩短,性能改善。

将任意对象作为对象监视器

synchronized(非this对象x) 格式的写法是将x对象本身作为“对象监视1器”(和synchronized(this)类似,只不过将当前类
对象改成x对象),也有如下结论:
1、当多个线程同时执行synchroized(x){}同步代码块时呈现同步效果。
2、当其他线程执行x对象中synchronized同步方法呈现同步效果
3、当其他线程执行x对象里面的synchroized(this)代码块或方法,也呈现同步效果。
总之一句话,对于同步方法块,锁是Synchonized括号里配置的对象,对象相同就同步,不同就异步。
eg.创建一个只能加入一个元素的List.

package objective2.action1;import java.util.ArrayList;import java.util.List;public class RunOneList {    public static void main(String[] args) {        MyOneList myOneList = new MyOneList();        MyService myService1 = new MyService(myOneList);        new Thread(myService1, "A").start();        new Thread(myService1, "B").start();        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("myOneList Size = " + myOneList.getSize());    }}class MyService implements Runnable {    private MyOneList myOneList;    public MyService(MyOneList myOneList) {        this.myOneList = myOneList;    }    @Override    public void run() {        addService();    }    private void addService() {        if (myOneList.getSize() < 1) {            String data = getDataFromRemote();            myOneList.add(data);        }    }    private String getDataFromRemote() {        try {            Thread.sleep(100);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        return "data";    }}class MyOneList {    private List<String> list = new ArrayList<String>();    public synchronized void add(String data) {        list.add(data);    }    public synchronized int getSize() {        return list.size();    }}
myOneList Size = 2

从运行结果看,虽然MyOneList的方法都使用synchronized保证同步,但是add和getSize非原子操作,两线程以异步方式返回了MyOneList Size大小,导致脏读。

package objective2.action1;import java.util.ArrayList;import java.util.List;public class RunOneList {    public static void main(String[] args) {        MyOneList myOneList = new MyOneList();        MyService myService1 = new MyService(myOneList);        new Thread(myService1, "A").start();        new Thread(myService1, "B").start();        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("myOneList Size = " + myOneList.getSize());    }}class MyService implements Runnable {    private MyOneList myOneList;    public MyService(MyOneList myOneList) {        this.myOneList = myOneList;    }    @Override    public void run() {        addService();    }    private void addService() {        String data = getDataFromRemote();        synchronized (myOneList) {            if (myOneList.getSize() < 1) {                myOneList.add(data);            }        }    }    private String getDataFromRemote() {        try {            Thread.sleep(100);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        return "data";    }}class MyOneList {    private List<String> list = new ArrayList<String>();    public synchronized void add(String data) {        list.add(data);    }    public synchronized int getSize() {        return list.size();    }}
myOneList Size = 1

Synchornized 的实现原理