Java多线程(1):synchronized 方法与锁对象

概述

我们经常在使用synchronized关键字来修饰方法以保证实例变量的线程安全,今天来讨论一下synchronized关键字的相关特性

实现同步

synchronized使用起来很简单,比如在方法上使用,在多个线程访问同一个同步方法的时候,由于synchronized获得是对象锁,并不是把每一个方法当作锁,所以哪个线程先执行这个同步方法,哪个线程就持有该方法所属对象的锁,其他线程就需要等待当前持有对象锁的线程释放对象锁。

package com.leolee.multithreadProgramming.test.objectVariate;

/**
 * @ClassName MyObject
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/8/24
 * @Version V1.0
 **/
public class MyObject {

    public synchronized void methodA() {

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

}
package com.leolee.multithreadProgramming.test.objectVariate;

/**
 * @ClassName ThreadA
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/8/24
 * @Version V1.0
 **/
public class ThreadA extends Thread {

    private MyObject object;

    public ThreadA(MyObject object) {
        super();
        this.object = object;
    }

    @Override
    public void run() {
        object.methodA();
    }
}
package com.leolee.multithreadProgramming.test.objectVariate;

/**
 * @ClassName ThreadB
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/8/24
 * @Version V1.0
 **/
public class ThreadB extends Thread {

    private MyObject object;

    public ThreadB(MyObject object) {
        super();
        this.object = object;
    }

    @Override
    public void run() {
        super.run();
        object.methodA();
    }
}
package com.leolee.multithreadProgramming.test.objectVariate;

/**
 * @ClassName Run
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/8/24
 * @Version V1.0
 **/
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();
    }
}

运行结果表明两个线程按照先后调用同步方法的顺序执行

begin methodA threadName = A
end
begin methodA threadName = B
end

验证“锁”的是对象

在MyObject类中添加b方法

package com.leolee.multithreadProgramming.test.objectVariate;

/**
 * @ClassName MyObject
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/8/24
 * @Version V1.0
 **/
public class MyObject {

    public synchronized void methodA() {

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


    public void methodB() {

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

}

修改ThreadB调用methodB方法

package com.leolee.multithreadProgramming.test.objectVariate;

/**
 * @ClassName ThreadB
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/8/24
 * @Version V1.0
 **/
public class ThreadB extends Thread {

    private MyObject object;

    public ThreadB(MyObject object) {
        super();
        this.object = object;
    }

    @Override
    public void run() {
        super.run();
        object.methodB();
    }
}

运行结果:不被synchronized修饰的方法可以异步的被线程B调用

begin methodB threadName = B
begin methodA threadName = A
end
end

当给methodB也加上synchronized修饰的时候运行结果如下

begin methodA threadName = A
end
begin methodB threadName = B
end

虽然两个线程调用了MyObject类的不同方法,但是还是同步执行的。

得出以下结论:

  • A线程首先持有某一个类的锁对象的时候,B线程可以异步调用该类的非同步方法
  • A线程首先持有某一个类的锁对象的时候,B线程调用该类的另一个同步方法需要等待A线程释放

脏读

上面的例子通过synchronized对象锁实现了线程的同步,但是这时候并不意味着真正的实现了线程安全。因为在多线程实行过程中“写操作”会有执行时间的原因,会导致写与读之间的时间差,进而造成脏读现象的出现。脏读一定会出现操作实例变量的情况下,是多线程争夺实例变量的结果。

示例:

package com.leolee.multithreadProgramming.test.dirtyRead;

/**
 * @ClassName PublicVar
 * @Description: 脏读
 * @Author LeoLee
 * @Date 2020/8/27
 * @Version V1.0
 **/
public class PublicVar {

    public String userName;

    public String password;

    public PublicVar(String userName, String password) {
        this.userName = userName;
        this.password = password;
    }

    public void getValue() {
        System.out.println("getValue method thread Name:" + Thread.currentThread().getName());
        System.out.println("userName:" + this.userName + ",password:" + this.password);
    }

    public synchronized void setValue(String userName, String password) {
        try {
            this.userName = userName;
            Thread.sleep(5000);
            this.password = password;
            System.out.println("getValue method thread Name:" + Thread.currentThread().getName());
            System.out.println("userName:" + this.userName + ",password:" + this.password);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package com.leolee.multithreadProgramming.test.dirtyRead;

/**
 * @ClassName ThreadA
 * @Description:
 * @Author LeoLee
 * @Date 2020/8/27
 * @Version V1.0
 **/
public class ThreadA extends Thread {

    private PublicVar publicVar;

    public ThreadA(PublicVar publicVar) {
        super();
        this.publicVar = publicVar;
    }

    @Override
    public void run() {
        super.run();
        publicVar.setValue("B", "BB");
    }
}
package com.leolee.multithreadProgramming.test.dirtyRead;

/**
 * @ClassName Run
 * @Description:
 * @Author LeoLee
 * @Date 2020/8/27
 * @Version V1.0
 **/
public class Run {

    public static void main(String[] args) {

        try {
            PublicVar publicVarRef = new PublicVar("A", "AA");
            ThreadA threadA = new ThreadA(publicVarRef);
            threadA.start();
            Thread.sleep(2000);
            publicVarRef.getValue();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果如下:

getValue method thread Name:main
userName:B,password:AA
getValue method thread Name:Thread-0
userName:B,password:BB

很明显的在userName赋值之后,由于sleep了5秒模拟了业务操作,导致了只有userName赋值成功,主线程调用getValue方法读取了不在预期的脏数据。

解决办法显而易见,getValue方法也加上synchronized。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值