12、synchronized同步方法

版权声明:版权归 爱装逼的文艺小青年所有 https://blog.csdn.net/toyota_rav4/article/details/84960812

ps:最近几天带媳妇回了趟家,博文没有更新。

目录

方法内部的私有变量为线程安全

实例变量非线程安全

给方法加锁

synchronized取得的锁是对象锁

A线程持有同步方法的锁,B线程可以异步的调用该对象中的非同步方法

脏读


方法内部的私有变量为线程安全

在使用synchronized同步方法之前,我们先来做多个线程访问方法内部的私有变量,是否存在线程安全的问题。

package com.demo12;

public class MyObject {
    public void addNum(String name){
        try {
            int num = 0;
            if("a".equals(name)){
                num = 100 ;
                System.out.println("a 赋值完毕");
                Thread.sleep(2000);
            }else{
                num = 200;
                System.out.println("b 赋值完毕");
            }
            System.out.println("线程name=" + name + ";num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package com.demo12;

public class ThreadA extends Thread {
    private MyObject myObject;

    public ThreadA(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.addNum("a");
    }
}
package com.demo12;

public class ThreadB extends Thread{
    private MyObject myObject;

    public ThreadB(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.addNum("b");
    }
}
package com.demo12;

public class Run {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        ThreadA threadA = new ThreadA(myObject);
        ThreadB threadB = new ThreadB(myObject);
        threadA.start();
        threadB.start();
    }
}

运行结果:

a 赋值完毕
线程name=a;num=100
b 赋值完毕
线程name=b;num=200

由输出结果可以看出:a线程赋值完毕之后,b线程才对num进行赋值。哪怕a sleep()了,b线程也没抢占cpu资源去执行赋值。方法内部的私有变量,不存在线程安全的问题。

实例变量非线程安全

package com.demo12;

public class MyObject {
    private int num = 0;
    public void addNum(String name){
        try {
            //int num = 0;
            if("a".equals(name)){
                num = 100 ;
                System.out.println("a 赋值完毕");
                Thread.sleep(2000);
            }else{
                num = 200;
                System.out.println("b 赋值完毕");
            }
            System.out.println("线程name=" + name + ";num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

将上例中的MyObject中的方法内部的变量改为实例变量,运行结果为:

a 赋值完毕
b 赋值完毕
线程name=b;num=200
(此处等待两秒)
线程name=a;num=200

线程a赋值完毕之后被sleep(),这时候b拿到了Cpu资源,对num进行了赋值,最后a才拿到cpu资源,继续走打印语句。但是num已经是被b给修改过了的。这就是非线程安全的场景。

给方法加锁

这个时候,我们可以使用synchronized对方法进行加锁,使上面的场景变成线程安全的!

package com.demo12;

public class MyObject {
    private int num = 0;
    synchronized public void addNum(String name){
        try {
            //int num = 0;
            if("a".equals(name)){
                num = 100 ;
                System.out.println("a 赋值完毕");
                Thread.sleep(2000);
            }else{
                num = 200;
                System.out.println("b 赋值完毕");
            }
            System.out.println("线程name=" + name + ";num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

a 赋值完毕
(此处等待了2秒)
线程name=a;num=100
b 赋值完毕
线程name=b;num=200

很明显为线程安全的了。

synchronized取得的锁是对象锁

修改上述代码如下:

package com.demo12;

public class MyObject {
    private int num = 0;
    synchronized public void addNum(String name){
        try {
            //int num = 0;
            if("a".equals(name)){
                num = 100 ;
                System.out.println("a 赋值完毕");
                Thread.sleep(2000);
            }else{
                num = 200;
                System.out.println("b 赋值完毕");
            }
            System.out.println("线程name=" + name + ";num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package com.demo12;

public class ThreadA extends Thread {
    private MyObject myObject;

    public ThreadA(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.addNum("a");
    }
}
package com.demo12;

public class ThreadB extends Thread{
    private MyObject myObject;

    public ThreadB(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.addNum("b");
    }
}
package com.demo12;

public class Run {
    public static void main(String[] args) {
        MyObject myObject1= new MyObject();
        MyObject myObject2= new MyObject();
        ThreadA threadA = new ThreadA(myObject1);
        threadA.start();
        ThreadB threadB = new ThreadB(myObject2);
        threadB.start();
    }
}

运行结果为:

a 赋值完毕
b 赋值完毕
线程name=b;num=200
(此处等待了2秒)
线程name=a;num=100

分析:虽然结果打印出来的形如异步,但是结果却没有乱。a , b 线程在获取CPU资源之后,分别持有synchronied方法所属的对象锁。如果不是分别拿到的两个对象的锁,最后的打印结果num=200。出现非线程安全的问题。

A线程持有同步方法的锁,B线程可以异步的调用该对象中的非同步方法

package com.demo13;

public class MyObject {
    synchronized public void methodA(){
        try {
            System.out.println("同步methodA beigin , 线程名称=" + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("同步methodA end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void methodB(){
        System.out.println("非同步methodB begin , 线程名称=" + Thread.currentThread().getName());
        System.out.println("非同步methodB end");
    }
}
package com.demo13;

public class ThreadA extends Thread {
    private MyObject myObject;

    public ThreadA(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.methodA();
    }
}
package com.demo13;

public class ThreadB extends Thread {
    private MyObject myObject;

    public ThreadB(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run(){
        myObject.methodB();
    }
}
package com.demo13;

public class Run {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        ThreadA threadA = new ThreadA(myObject);
        ThreadB threadB = new ThreadB(myObject);
        threadA.setName("A");
        threadB.setName("B");
        threadA.start();
        threadB.start();
    }
}

运行结果:

同步methodA beigin , 线程名称=A
非同步methodB begin , 线程名称=B
非同步methodB end
(此处等待5秒)
同步methodA end

脏读

上面所述的情况可能导致脏读

package com.demo14;

public class MyObject  {
    private String username = "1";
    private String password = "11";
    synchronized public void setValue(String username , String password){
        try {
            System.out.println("赋值线程名=" + Thread.currentThread().getName());
            this.username = username;
            Thread.sleep(2000);
            this.password = password;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void getValue(){
        System.out.println("取值线程名=" + Thread.currentThread().getName());
        System.out.println("username=" + username );
        System.out.println("password=" + password);
    }
}
package com.demo14;

public class ThreadA extends Thread {
    MyObject myObject = new MyObject();

    public ThreadA(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.setValue("2","22");
    }
}
package com.demo14;

public class ThreadB extends Thread {
    private MyObject myObject;

    public ThreadB(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        myObject.getValue();
    }
}
package com.demo14;

public class Run {
    public static void main(String[] args) {
        try {
            MyObject myObject = new MyObject();
            ThreadA threadA = new ThreadA(myObject);
            ThreadB threadB = new ThreadB(myObject);
            threadA.setName("A");
            threadB.setName("B");
            threadA.start();
            Thread.sleep(500); //给时间 保证让A将username赋值完毕
            threadB.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

赋值线程名=A
取值线程名=B
username=2
password=11

很明显出现了脏读。修改MyObject.java文件

package com.demo14;

public class MyObject  {
    private String username = "1";
    private String password = "11";
    synchronized public void setValue(String username , String password){
        try {
            System.out.println("赋值线程名=" + Thread.currentThread().getName());
            this.username = username;
            Thread.sleep(2000);
            this.password = password;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public void getValue(){
        System.out.println("取值线程名=" + Thread.currentThread().getName());
        System.out.println("username=" + username );
        System.out.println("password=" + password);
    }
}

运行结果为:

赋值线程名=A
取值线程名=B
username=2
password=22

上述说明:B线程想要调用对象中没有被A线程锁住的同步方法B,必须等A线程释放A方法的同步锁。

猜你喜欢

转载自blog.csdn.net/toyota_rav4/article/details/84960812