synchronized同步方法

来源:互联网 发布:tomcat连不上mysql 编辑:程序博客网 时间:2024/06/05 00:32

  前面已经介绍了“线程安全”与”非线程安全“的相关概念。“非线程安全”其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是读取到的数据其实是被更改的。而“线程安全”就是以获得的实例变量的值是进过同步处理的,不会出现脏读的现象。

方法内的变量为线程安全的

  “非线程安全”问题存在与“实例变量”中,如果是方法内的私有变量,则不存在“线程安全”问题,所以结果也就是“线程安全”的。创建如下示例代码:

public class HasSelfPrivateNum {    public void addI(String username){        try{            int num = 0;            if("a".equals(username)){                num = 100;                System.out.println("a set over");                Thread.sleep(2000);            }else{                num = 200;                System.out.println("b set over");            }            System.out.println(username+" num="+num);        }catch(Exception e){            e.printStackTrace();        }    }}
public class MyThreadA extends Thread{    private HasSelfPrivateNum num;    public MyThreadA(HasSelfPrivateNum num){        this.num = num;    }    @Override    public void run() {        num.addI("a");    }}
public class MyThreadB extends Thread{    private HasSelfPrivateNum num;    public MyThreadB(HasSelfPrivateNum num){        this.num = num;    }    @Override    public void run() {        num.addI("b");    }}
public class Run {    public static void main(String[] args) {        HasSelfPrivateNum num = new HasSelfPrivateNum();        MyThreadA a = new MyThreadA(num);        a.start();        MyThreadB b = new MyThreadB(num);        b.start();    }}

执行结果如下:
这里写图片描述
可见,方法中的变量不存在非线程安全问题,永远是线程安全的。这是方法内部变量是私有特性造成的。

实例变量非线程安全

  如果多个线程共同访问1个对象中的实例变量,则有可能出现“非线程安全”问题。创建如下代码:
  

public class HasSelfPrivateNum {    private int num = 0;    public void addI(String username){        if("a".equals(username)){            num = 100;            System.out.println("a set over!");            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }else{            num = 200;            System.out.println("b set over!");        }        System.out.println(username + " num="+num);    }}
public class ThreadA extends Thread{    private HasSelfPrivateNum num;    public ThreadA(HasSelfPrivateNum num){        this.num = num;    }    @Override    public void run() {        num.addI("a");    }}
public class ThreadB extends Thread{    private HasSelfPrivateNum num;    public ThreadB(HasSelfPrivateNum num) {        this.num = num;    }    @Override    public void run() {        num.addI("b");    }}
public class Run {    public static void main(String[] args) {        HasSelfPrivateNum num = new HasSelfPrivateNum();        ThreadA ta = new ThreadA(num);        ta.start();        ThreadB tb = new ThreadB(num);        tb.start();    }}

执行结果如下:
这里写图片描述
本程序是两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则有可能会出现“非线程安全”问题。只需在方法前面加上synchronized即可,代码改为如下:

public class HasSelfPrivateNum {    private int num = 0;    synchronized public void addI(String username){        if("a".equals(username)){            num = 100;            System.out.println("a set over!");            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }else{            num = 200;            System.out.println("b set over!");        }        System.out.println(username + " num="+num);    }}

执行结果如下:
这里写图片描述

多个对象多个锁:

创建如下代码:

public class HasSelfPrivateNum {    private int num = 0;    synchronized public void addI(String username){        if("a".equals(username)){            num = 100;            System.out.println("a set over");            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }else{            num = 200;            System.out.println("b set over");        }        System.out.println(username+" num="+num);    }}
public class ThreadA extends Thread{    private HasSelfPrivateNum num;    public ThreadA(HasSelfPrivateNum num){        this.num = num;    }    @Override    public void run() {        num.addI("a");    }}
public class ThreadB extends Thread{    private HasSelfPrivateNum num;    public ThreadB(HasSelfPrivateNum num){        this.num = num;    }    @Override    public void run() {        num.addI("b");    }}
public class Run {    public static void main(String[] args) {        HasSelfPrivateNum num1 = new HasSelfPrivateNum();        HasSelfPrivateNum num2 = new HasSelfPrivateNum();        ThreadA t1 = new ThreadA(num1);        t1.start();        ThreadB t2 = new ThreadB(num2);        t2.start();    }}

执行结果如下:
这里写图片描述
  上面示例是两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果确是以异步的方式运行的。本示例由于创建了2个业务对象,在系统中产生出2个锁,所以运行结果是异步的,打印结果就是先打印b,再打印a。
 关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法当作锁。所以在上面的示例中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是一个对象。
 但是如果多个线程访问多个对象,则JVM会创建多个锁。上面的示例就是创建了2个HasSelfPrivateNum类的对象,所以就会产生2个锁。

synchronized方法与锁对象:

为了证明线程锁的是对象,创建如下示例代码:

public class MyObject {    public void methodA(){        System.out.println("begin methodA threadName="+Thread.currentThread().getName());        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("end");    }}
public class ThreadA extends Thread{    private MyObject object;    public ThreadA(MyObject object){        this.object = object;    }    @Override    public void run() {        object.methodA();    }}
public class ThreadB extends Thread{    private MyObject object;    public ThreadB(MyObject object){        this.object = object;    }    @Override    public void run() {        object.methodA();    }}
public class Run {    public static void main(String[] args) {        MyObject object = new MyObject();        ThreadA a = new ThreadA(object);        a.setName("A");        ThreadB b= new ThreadB(object);        b.setName("B");        a.start();        b.start();    }}

执行结果如下:
这里写图片描述
修改MyObject类的代码如下:

public class MyObject {    synchronized public void methodA(){        System.out.println("begin methodA threadName="+Thread.currentThread().getName());        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("end");    }}

加上synchronized关键字后再次运行Run,结果如下:
这里写图片描述
通过上面运行的示例结果如下,调用关键字synchronized申明的方法一定是排队运行的。另外还需要资源共享,只有共享资源的读写访问才需要同步,如果不是共享资源,没有必要同步。
那么其他的方法被调用时会是什么效果。创建如下代码:

public class MyObject {    synchronized public void methodA(){        System.out.println("begin methodA threadName="+Thread.currentThread().getName());        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("end");    }    public void methodB(){        System.out.println("begin methodB threadName="+Thread.currentThread().getName());        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("end");    }}
public class ThreadA extends Thread{    private MyObject object;    public ThreadA(MyObject object){        this.object = object;    }    @Override    public void run() {        object.methodA();    }}
public class ThreadB extends Thread{    private MyObject object;    public ThreadB(MyObject object){        this.object = object;    }    @Override    public void run() {        object.methodB();    }}
public class Run {    public static void main(String[] args) {        MyObject object = new MyObject();        ThreadA a = new ThreadA(object);        a.setName("A");        ThreadB b= new ThreadB(object);        b.setName("B");        a.start();        b.start();    }}

执行结果如下:
这里写图片描述
通过上面运行结果可以得知,虽然线程A先持有了MyObject对象的锁,但是线程B完全可以异步调用非synchronized类型的方法。
继续修改代码,将MyObject文件中的methodB方法加上synchronized关键字,代码如下:

public class MyObject {    synchronized public void methodA(){        System.out.println("begin methodA threadName="+Thread.currentThread().getName());        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("end");    }    synchronized public void methodB(){        System.out.println("begin methodB threadName="+Thread.currentThread().getName());        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("end");    }}

在执行Run类的main方法,执行结果如下:
这里写图片描述
此实验的结论如下:

  1. A线程先持有MyObject对象的Lock锁,B线程可以以异步的方式调用MyObject对象的非synchronized类型的方法。
  2. 2、A线程先持有MyObject对象的Lock锁,B线程如果这个时候调用MyObject对象中的synchronized类型的方法则需要等待,也就是同步。

脏读

  前面已经实现了多线程调用一个方法时,为了避免数据出现交叉的情况,使用synchronized关键字进行同步。
  虽然进行了同步,但在取值时可能出现一些意想不到的意外,这种情况就是脏读。发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。创建如下代码:

public class PublicVar {    private String username = "A";    private String password = "AA";    synchronized public void setValue(String username, String password) {        this.username = username;        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        this.password = password;        System.out.println("setValue method thread name=" + Thread.currentThread().getName() + " username=" + username                + " password=" + password);    }    public void getValue(){        System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username                + " password=" + password);    }}
public class ThreadA extends Thread{    private PublicVar publicVar;    public ThreadA(PublicVar publicVar){        this.publicVar = publicVar;    }    @Override    public void run() {        publicVar.setValue("B", "BB");    }}
public class Run {    public static void main(String[] args) throws InterruptedException {        PublicVar publicVar = new PublicVar();        ThreadA threadA = new ThreadA(publicVar);        threadA.start();        Thread.sleep(200);        publicVar.getValue();    }}

运行结果如下:
这里写图片描述
出现脏读情况是因为getValue()方法不是同步的,可以在任意时候调用。解决方法给getValue()方法加上synchronized关键字。代码如下:

synchronized public void getValue(){        System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username                + " password=" + password);    }

程序执行结果如下:
这里写图片描述
可见,setValue()和getValue()被依次执行。通过这个案例可以知道脏读可以通过synchronized关键字解决。应该还清楚如下内如:
当线程A调用anyObject对象加入synchronized关键字的A方法时,A线程就获得了X方法锁,更确切的讲是获得了对象锁,所有其他线程必须要等A线程执行完毕才可以调用X方法,但其他线程可以调用非synchronized同步方法。
当A线程调用anyObject对象加了synchronized关键字的X方法时,A线程就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕才可以执行X方法,而B线程如果调用了声明了synchronized关键字的非X方法,必须等A线程将X方法执行完,也就是必须等释放对象锁后才可以调用。
脏读一定是在出现操作实例变量的情况下,这就是不同线程争抢实例变量的结果。

synchronized锁重入:

关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到了对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。创建如下代码:

public class Service {    synchronized public void service1(){        System.out.println("service1");        service2();    }    synchronized public void service2(){        System.out.println("service2");        service3();    }    synchronized public void service3(){        System.out.println("service3");    }}
public class MyThread extends Thread{    @Override    public void run() {        Service service = new Service();        service.service1();    }}
public class Run {    public static void main(String[] args) {        MyThread m = new MyThread();        m.start();    }}

执行的结果如下:
这里写图片描述
可重入锁的概念是:自己可以再次获取自己的内部的锁。比如有线程A获得了某对象的锁,此时这个时候锁还没有释放,当其再次想获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
可重入锁也支持在父子类继承的环境中。创建如下代码:

public class Main {    protected int i=10;    synchronized public void operateMainMethod(){        i--;        System.out.println("main print i="+i);        try {            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}
public class Sub extends Main{    synchronized public void operateISubMethod(){        while(i>0){            i--;            System.out.println("sub print i="+i);            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            this.operateMainMethod();        }    }}
public class MyThread2 extends Thread{    public void run() {        Sub sub = new Sub();        sub.operateISubMethod();    };}
public class Run2 {    public static void main(String[] args) {        MyThread2 m2 = new MyThread2();        m2.start();    }}

执行结果如下:
这里写图片描述
这也说明,子类继承父类时,子类完全可以通过可重入锁调用父类的同步方法。

出现异常,锁自动释放

  当一个线程执行的代码出现异常时,其所持有的锁会自动释放。创建如下代码:

public class Service {    synchronized public void testMethod() {        if (Thread.currentThread().getName().equals("a")) {            System.out.println(                    "ThreadName=" + Thread.currentThread().getName() + " run beginTime=" + 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("a");                }            }        } else {            System.out.println("Thread B run time="+System.currentTimeMillis());        }    }}
public class ThreadA extends Thread{    private Service service;    public ThreadA(Service service){        this.service = service;    }    @Override    public void run() {        service.testMethod();    }}
public class ThreadB extends Thread{    private Service service;    public ThreadB(Service service){         this.service = service;    }    public void run() {        service.testMethod();    }}
public class Run {    public static void main(String[] args) throws InterruptedException {        Service service = new Service();        ThreadA threadA = new ThreadA(service);        threadA.setName("a");        threadA.start();        Thread.sleep(500);        ThreadB threadB = new ThreadB(service);        threadB.setName("b");        threadB.start();    }}

执行结果如下:
这里写图片描述
线程a出现异常并释放锁,线程b进入方法正常打印。

同步不具有继承性:

同步不可以继承。创建如下代码:

public class Main {    synchronized public void serviceMethod() {        System.out.println("int main 下一步 sleep begin threaName=" + Thread.currentThread().getName() + " time="                + System.currentTimeMillis());        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("int main 下一步 sleep end threadName=" + Thread.currentThread().getName() + " time"                + System.currentTimeMillis());    }}
public class Sub extends Main {    @Override    public void serviceMethod() {        System.out.println("int sub 下一步 sleep begin ThreadName=" + Thread.currentThread().getName() + " time="                + System.currentTimeMillis());        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("int sub 下一步 sleep end ThreadName=" + Thread.currentThread().getName() + " time"                + System.currentTimeMillis());        super.serviceMethod();    }}
public class ThreadA extends Thread{    private Sub sub;    public ThreadA(Sub sub){        this.sub = sub;    }    @Override    public void run() {        sub.serviceMethod();    }}
public class ThreadB extends Thread{    private Sub sub;    public ThreadB(Sub sub){        this.sub = sub;    }    @Override    public void run() {        sub.serviceMethod();    }}
public class Run {    public static void main(String[] args) {        Sub sub = new Sub();        ThreadA threadA = new ThreadA(sub);        threadA.setName("a");        threadA.start();        ThreadB threadB  = new ThreadB(sub);        threadB.setName("b");        threadB.start();    }}

执行结果如下:
这里写图片描述
修改子类的代码如下:

public class Sub extends Main {    @Override    synchronized public void serviceMethod() {        System.out.println("int sub 下一步 sleep begin ThreadName=" + Thread.currentThread().getName() + " time="                + System.currentTimeMillis());        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("int sub 下一步 sleep end ThreadName=" + Thread.currentThread().getName() + " time"                + System.currentTimeMillis());        super.serviceMethod();    }}

执行结果如下:
这里写图片描述
从上面可以看出,同步是不能继承的,所有还得在子类的方法中添加synchronized关键字。

1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 手机号销户话费怎么办 微信Q币充错怎么办 淘宝q币充错了怎么办 移动充值错误怎么办 qq忘记密码怎么办登陆 sql数据库满了怎么办 微信充值话费不到账怎么办 填报志愿密码错误怎么办 校园卡被冻结了怎么办 充值卡密码丢了怎么办 联通手机号码不支持开黄钻怎么办 手机qq加载失败怎么办 qq红包额度上限怎么办 qq红包超出限额怎么办 qq红包超过限额怎么办 红包过期没退回怎么办 qb充值错了号码怎么办 天猫q币充值被骗怎么办 微信充错了q币怎么办 qq钱包没银行卡怎么办 qq钱包被限制怎么办 充流量未到账怎么办 云客服被冻结怎么办 联通话费注销了怎么办 我的话费太多怎么办 lol充错账号怎么办 支付宝手机充值没到账怎么办 支付宝限额10000怎么办 米币超过限制怎么办 忘记财付通支付密码怎么办 支付宝忘记登陆密码怎么办 三位数密码忘记了怎么办 登入密码忘记怎么办 电脑登录忘记密码怎么办 支付宝忘记密码怎么办 密码箱忘了密码怎么办 信用卡忘了密码怎么办 我没有财付通钱没有了怎么办 发红包忘记密码怎么办 手机怎么办理财付通 忘记qq红包密码怎么办