java多线程系列(一)--synchronized同步方法

来源:互联网 发布:cms管理系统是什么 编辑:程序博客网 时间:2024/06/02 07:17

简介

synchronzied是用来保证共享数据的同步性的,用synchronized修饰一个方法或者代码块时,能保证同一时刻最多只能有一个线程运行该代码块。

一、synchronized同步方法

1.1方法内部的变量与实例域的线程安全
上面讲到共享数据,那么什么是共享数据呢?比如,类中的实例域,就是类中方法的共享数据,而方法内的临时变量就不是其他方法的共享数据,它是这个方法的私有变量,所以不存在非线程安全问题。下面看一个简单的例子
public class HasSelfPrivateNum {    public void addI(String userName) {        int num = 0;        try {            if (userName.equals("a")) {                num = 100;                System.out.println("a set over");                Thread.sleep(2000);            } else {                num = 200;                System.out.println("b set over");            }        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println(userName + " num = " + num);    }}``` javapublic class ThreadA extends Thread {    private HasSelfPrivateNum numRef;    public ThreadA(HasSelfPrivateNum numRef) {        this.numRef = numRef;    }    public void run() {        numRef.addI("a");    }}<div class="se-preview-section-delimiter"></div>``` javapublic class ThreadB extends Thread {    private HasSelfPrivateNum numRef;    public ThreadB(HasSelfPrivateNum numRef) {        this.numRef = numRef;    }    public void run() {        numRef.addI("b");    }}``` javapublic class Run {    public static void main(String[] args) {        HasSelfPrivateNum ref1 = new HasSelfPrivateNum();        ThreadA a = new ThreadA(ref1);        a.start();        ThreadB b = new ThreadB(ref1);        b.start();    }}<div class="se-preview-section-delimiter"></div>

运行结果如下,可见线程是安全的
a set over
b set over
b num = 200
a num = 100
那么我们改一下HasSlefPrivateNum类,修改如下

public class HasSelfPrivateNum {    private int num = 0;    public void addI(String userName) {        try {            if (userName.equals("a")) {                num = 100;                System.out.println("a set over");                Thread.sleep(2000);            } else {                num = 200;                System.out.println("b set over");            }        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println(userName + " num = " + num);    }}<div class="se-preview-section-delimiter"></div>

将num变量设为类的实例域,这样num变量对于方法来说就是共享的,运行结果如下
a set over
b set over
b num = 200
a num = 200
当线程a调用addI方法时,赋予num值为100,但是此时有Thread.sleep(2000),a线程阻塞2000毫秒,于此同时,线程b也在调用addI方法,
当线程a阻塞结束时线程b已经num更新为了200,所以这样线程是不安全的。那么我们在addI方法前加上synchronized,即synchronized public void addI(String userName),再运行一下
a set over
a num = 100
b set over
b num = 200
可以看到线程a和b是排队运行的,线程a执行完addI方法后,线程b才执行。所以此时是线程安全的,即两个线程访问同一个对象的同步方法(即通synchronized修饰的方法)时一定是线程安全的。

1.2不同的对象拥有不同的锁

继续修改上面的代码,将Run类修改为
public class Run {    public static void main(String[] args) {        HasSelfPrivateNum ref1 = new HasSelfPrivateNum();        HasSelfPrivateNum ref2 = new HasSelfPrivateNum();        ThreadA a = new ThreadA(ref1);        a.start();        ThreadB b = new ThreadB(ref2);        b.start();    }}<div class="se-preview-section-delimiter"></div>

运行结果如下
a set over
b set over
b num = 200
a num = 100
可见两个线程是同时运行的,即异步运行方式。原因是什么呢?synchronized关键字获取的是对象锁,而不是把一段代码或方法当作锁,并且不同的对象所拥有的锁不是同一把,一把锁只能监控本对象的synchronized修饰方法,所以上面即使是同一个类,运行方式也是异步运行,因为是两个不同的对象。

1.4理解对象锁

 前面我们看到不同线程调用同一个对象中synchronized修饰方法时是排队运行的,那么我们调用非synchronized方法呢?新建一个例子
public class MyObject {    synchronized public void methodA() {        try {            System.out.println("begin methodA threadName = " +                     Thread.currentThread().getName());            Thread.sleep(5000);            System.out.println("end methodA endTime = " + System.currentTimeMillis());        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }    public void methodB() {        try {            System.out.println("begin methodB threadName = " +                     Thread.currentThread().getName() + "  begin Time = " +                    System.currentTimeMillis());            Thread.sleep(5000);            System.out.println("end methodB endTime = " + System.currentTimeMillis());        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }}<div class="se-preview-section-delimiter"></div>``` javapublic class MyThread1 extends Thread {    private MyObject object;    public MyThread1(MyObject object) {        this.object = object;    }    public void run() {        object.methodA();    }}```javapublic class MyThread2 extends Thread {    private MyObject object;    public MyThread2(MyObject object) {        this.object = object;    }    public void run() {        object.methodB();    }}<div class="se-preview-section-delimiter"></div>``` javapublic class RunMyObject {    public static void main(String[] args) {        MyObject object1 = new MyObject();        MyThread1 t1 = new MyThread1(object1);        t1.setName("Thread 1");        t1.start();        MyThread2 t2 = new MyThread2(object1);        t2.setName("Thread 2");        t2.start();    }}

运行结果如下
begin methodA threadName = Thread 1
begin methodB threadName = Thread 2 begin Time = 1508774763367
end methodA endTime = 1508774768367
end methodB endTime = 1508774768373
线程t1调用synchronized 修饰的methodA方法,线程t2调用普通的methodB方法,两个方法是同时运行的,这就说明不同线程可以同时(异步)调用已拥有锁的非Synchronized方法,t1先持有object1对象的锁,但是t2可以异步调用methodB方法。那同时调用synchronized修饰的方法呢?将methodB方法前加上synchronized,即synchronized public void methodB(),看一下运行结果
begin methodA threadName = Thread 1
end methodA endTime = 1508775090810
begin methodB threadName = Thread 2 begin Time = 1508775090810
end methodB endTime = 1508775095815
线程t1调用methodA方法结束后,线程t2才调用methodB,可见两个线程是排队运行的(即同步运行)。所以得出结论:对象锁监控的是此对象synchronized方法,并不监控非synchronized方法。

1.5出现异常时,锁自动释放

当一个线程执行的代码出现异常时,它锁持有的锁会自动释放
public class ServiceException {    synchronized public void testMethod() {        if (Thread.currentThread().getName().equals("c")) {            System.out.println("ThreadName = " + Thread.currentThread().getName() +                         "  rum begin time = " + System.currentTimeMillis());            int i = 1;            while (i == 1) {                if (("" + Math.random()).substring(0, 8).equals("0.123456")) {                    System.out.println("ThreadName = " + Thread.currentThread().getName() +                             "  run  exceptionTime = " + System.currentTimeMillis());                    Integer.parseInt("c");//这里将会抛出一个异常                }            }        } else {            System.out.println("Thread D run time = " +                     System.currentTimeMillis());        }    }}``` javapublic class ThreadC extends Thread {    private ServiceException service;    public ThreadC(ServiceException service) {        this.service = service;    }    public void run() {        service.testMethod();    }}<div class="se-preview-section-delimiter"></div>``` javapublic class ThreadD extends Thread {    private ServiceException service;    public ThreadD(ServiceException service) {        this.service = service;    }    public void run() {        service.testMethod();    }}``` javapublic class RunServiceException {    public static void main(String[] args) {        try {            ServiceException service = new ServiceException();            ThreadC c = new ThreadC(service);            ThreadD d = new ThreadD(service);            c.setName("c");            d.setName("d");            c.start();            Thread.sleep(1000);            d.start();        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }}//出现异常,将会释放对象锁

运行结果如下
ThreadName = c rum begin time = 1508834369920
ThreadName = c run exceptionTime = 1508834369997
Exception in thread “c” java.lang.NumberFormatException: For input string: “c”
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at _2_1.ServiceException.testMethod(ServiceException.java:14)
at _2_1.ThreadC.run(ThreadC.java:12)
Thread D run time = 1508834370920
在testMethod方法中是用一个while语句写的死循环,也就是说当线程C调用testMethod方法时,会一直循环,那么线程C就会一直持有service对象的锁,而不释放,线程D就无法获得service对象的锁。按照这种理解,那么线程就不可能调用testMethod方法,但是在运行结果可以看到,线程D确实调用了testMethod方法,这是什么原因呢?
原因就在于Integer.parseInt(“c”);这句代码,这句代码会抛出一个NumberFormatException异常,当异常抛出之后,线程C会释放service对象的锁,接着线程D争抢到了这把锁,所以线程D就会执行testMethod方法。

1.6 同步不具有继承性

比如,一个父类Parent类,Parent类中有一个用synchronized public void A()方法,Child类继承了Parent类,同时Child类重写了A方法,但是并没有用synchronized修饰,此时Child类中的A方法并不具有同步性。
我们可以这样理解,一般来说,父类中的public方法,子类继承之后就变成了子类的私有方法(这是在不重写这些方法的前提下)。子类重写了这些方法之后(比如Child 类重写了Parent类中的A方法),子类中的方法就与父类中方法没有关系了(Child 中的A和Parent中的A方法),既然都没有关系了,父类中的方法是否synchronized与子类有何相干呢。但是如果子类并没有重写父类中的方法(比如Child类没有重写Parent的A方法),此时A方法仍然具有同步性,因为可以理解成变成子类的私有方法了嘛。所以这里的继承性指的是子类是否重写父类的方法。

总结

1.synchronized是为了同步共享数据的,因为不共享的数据也没有同步的必要。
2.synchronized获取的是对象锁,这个对象锁监控对象的synchronized方法
3.出现异常当前线程会释放锁

附:本文是基于《java多线程编程核心技术》写的,文中示例代码很多是使用的书上的。

原创粉丝点击