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 类型的方法则需等待,也就是同步。