第二章、(2)方法内变量线程安全、synchronized 对象锁、synchronized方法和非synchronized方法可异步调用

synchronized 同步方法:(摘要)

一、方法内的私有变量不存在"非线程安全"问题,实例变量如果在被多个线程访问时,可能出现"线程安全"问题。

二、使用 synchronized 关键字申明方法,访问该方法的线程获得的是对象锁(该方法所属对象的锁)。哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。如果多个线程访问多个对象,则JVM会创建多个锁。

三、A、B两个线程访问同一个Object对象(对象中两个方法,方法a 有synchronized申明,方法b无),A线程先持有Object的锁(正在执行 a方法)。B线程可以异步调用b方法,但是如果要调用 a 方法则需要等待 A线程执行完a 方法。

具体示例如下:

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

“非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题

 

public class HasSelfPrivateNum {
    public void addI(String username) {
        try {
            int num = 0;
            if (username.equals("a")) {
                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 (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread {
    private HasSelfPrivateNum numRef;
    public ThreadA(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }
    @Override
    public void run() {
        super.run();
        numRef.addI("a");
    }
}
public class ThreadB extends Thread {
    private HasSelfPrivateNum numRef;
    public ThreadB(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }
    @Override
    public void run() {
        super.run();
        numRef.addI("b");
    }
}
public class Test {
    public static void main(String[] args) {
        HasSelfPrivateNum numRef = new HasSelfPrivateNum();
        ThreadA threadA = new ThreadA(numRef);
        threadA.start();
        ThreadB threadB = new ThreadB(numRef);
        threadB.start();
    }
    /*
    运行结果:
    a set over
    b set over
    b num = 200
    a num = 100
     */
}

 

2. 实例变量非线程安全

如果多个线程共同访问1个对象中的实例变量,则有可能出现“非线程安全”问题。用线程访问的对象中如果有多个实例变量,则运行结果有可能出现交叉的情况;若对象仅有一个实例变量,则有可能出现覆盖的情况。
修改 HasSelfPrivateNum.java 类,将 num 变为成员变量,其他类不变

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");
            }
            System.out.println(username + " num = " + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
/*
    运行结果:
    a set over
    b set over
    b num = 200
    a num = 200
     */

 

如果两个线程同时操作业务对象中的实例变量,则有可能会出现“非线程安全”的问题。解决的方法:在 addI() 方法前加关键字 synchronized 即可

public class HasSelfPrivateNum {
    private int num = 0;
    public synchronized 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");
            }
            System.out.println(username + " num = " + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
/*
    运行结果:
    ----synchronized---
    a set over
    a num = 100
    b set over
    b num = 200
     */

在两个线程访问同一个对象中的同步方法时一定是线程安全的。上面例子由于是同步访问,所以先打印出a,在打印出b。

 

3. 多个对象多个锁

在2的基础上,修改Test 代码

public class Test {
    public static void main(String[] args) {
        HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
        HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
        ThreadA threadA = new ThreadA(numRef1);
        threadA.start();
        ThreadB threadB = new ThreadB(numRef2);
        threadB.start();
    }
    /*
    运行结果:
    a set over
    b set over
    b num = 200
    a num = 100
     */
}

可以看见,跟2例子中的结果不同了,打印的顺序不同步,而是交叉的。关键字synchronized取得的锁都是对象锁,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。如果多个线程访问多个对象,则JVM会创建多个锁。

 

4. synchronized 方法与锁对象

为了证明上面讲述的关键字synchronized取得的锁都是对象锁,我们来看个例子:

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 class ThreadA extends Thread {
    private MyObject object;
    public ThreadA(MyObject object) {
        super();
        this.object = object;
    }
    @Override
    public void run() {
        super.run();
        object.methodA();
    }
}
public class ThreadB extends Thread {
    private MyObject object;
    public ThreadB(MyObject object) {
        super();
        this.object = object;
    }
    @Override
    public void run() {
        super.run();
        object.methodA();
    }
}
public class Test {
    public static void main(String[] args) {
        MyObject object = new MyObject();
        ThreadA threadA = new ThreadA(object);
        threadA.setName("A");
        ThreadB threadB = new ThreadB(object);
        threadB.setName("B");
        threadA.start();
        threadB.start();
    }
    /*
    运行结果:
    begin methodA threadName = A
    begin methodA threadName = B
    end
    end
    -----methodA 添加 synchronized-------
    begin methodA threadName = A
    end
    begin methodA threadName = B
    end
     */
}

从上面两个结果可以得到结论:调用关键字synchronized申明的方法一定是排队运行的。另外,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本就没有同步的必要。那其他方法在被调用时会是什么效果?看下面的例子

 

修改 MyObject.java 类,添加 methodB() 方法:

public class MyObject {
    public synchronized void methodA() {
        try {
            System.out.println("begin methodA threadName = " + Thread.currentThread().getName() + " begin time = " + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("end methodA threadName = " + Thread.currentThread().getName() +" endTime = " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            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 threadName = " + Thread.currentThread().getName() +" endTime = " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

 

ThreadA.java 不变,ThreadB.java 中调用 methodB():

 

public class ThreadB extends Thread {
    private MyObject object;
    public ThreadB(MyObject object) {
        super();
        this.object = object;
    }
    @Override
    public void run() {
        super.run();
        object.methodB();
    }
}

 

/*
    运行结果:
    begin methodA threadName = A begin time = 1505098824261
    begin methodB threadName = B begin time = 1505098824261
    end methodB threadName = B endTime = 1505098829262
    end methodA threadName = A endTime = 1505098829262
    -----methodB 也添加 synchronized-------
    begin methodA threadName = A begin time = 1505098911474
    end methodA threadName = A endTime = 1505098916475
    begin methodB threadName = B begin time = 1505098916475
    end methodB threadName = B endTime = 1505098921475
     */

 

此实验结论:

1)A线程先持有 object 对象的 Lock 锁,B线程可以以异步的方式调用object 对象中的非 synchronized 类型的方法。

2)A线程先持有 object 对象的 Lock 锁,B线程如果在这时调用 object 对象中的 synchronized 类型的方法则需等待,也就是同步。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值