synchronized同步方法
多线程中经常需要和“线程安全”与“非线程安全”打交道,“非线程安全”会在多个线程对同一个对象中的实例变量进行并发的读写,产生的后果就是可能会出现“脏读”,也就是当一个线程对一个实例变量经过一系列计算后得出一个结果,正要使用时线程暂停,另一个线程同样对该变量进行一系列计算后得出另一个不同的结果,最后,前一个线程只能取到错误的数据。“线程安全”就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。
方法内的变量为线程安全和实例变量的非线程安全
代码2-1
class PrintAB {
private String str = "a";
private String checkNum = "偶数";
public void methodSecurity(Integer num) {
try {
String str = null;
String checkNum = null;
if (num % 2 == 0) {
str = "a";
checkNum = "偶数";
} else {
str = "b";
checkNum = "奇数";
}
Thread.sleep(50);
System.out.println(num + "为" + checkNum + ",打印" + str);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void methonUnSecurity(Integer num) {
try {
if (num % 2 == 0) {
str = "a";
checkNum = "偶数";
} else {
str = "b";
checkNum = "奇数";
}
Thread.sleep(50);
System.out.println(num + "为" + checkNum + ",打印" + str);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class SecurityThread extends Thread {
private int num;
private PrintAB printAB;
public SecurityThread(int num, PrintAB printAB) {
super();
this.num = num;
this.printAB = printAB;
}
@Override
public void run() {
printAB.methodSecurity(num);
}
}
class UnSecutiryThread extends Thread {
private int num;
private PrintAB printAB;
public UnSecutiryThread(int num, PrintAB printAB) {
super();
this.num = num;
this.printAB = printAB;
}
@Override
public void run() {
printAB.methonUnSecurity(num);
}
}
public class SecurityTest {
public static void main(String[] args) throws InterruptedException {
PrintAB printAB = new PrintAB();
System.out.println("—————————方法内变量为线程安全—————————");
for (int i = 0; i < 6; i++) {
new SecurityThread(i, printAB).start();
}
Thread.sleep(500);
System.out.println("—————————实例变量为非线程安全—————————");
for (int i = 0; i < 6; i++) {
new UnSecutiryThread(i, printAB).start();
}
}
}
代码2-1运行结果:
—————————方法内变量为线程安全—————————
2为偶数,打印a
3为奇数,打印b
5为奇数,打印b
4为偶数,打印a
1为奇数,打印b
0为偶数,打印a
—————————实例变量为非线程安全—————————
1为奇数,打印b
2为奇数,打印b
4为奇数,打印b
5为奇数,打印b
0为奇数,打印b
3为奇数,打印b
由代码代码2-1运行结果可知,实例变量因为多个线程同时读写,最后导致取得的数据时错误的。修改methonUnSecurity方法为其添加synchronized关键字,如代码2-2,即可实现线程安全的效果
代码2-2
public synchronized void methonUnSecurity(Integer num) {
try {
if (num % 2 == 0) {
str = "a";
checkNum = "偶数";
} else {
str = "b";
checkNum = "奇数";
}
Thread.sleep(50);
System.out.println(num + "为" + checkNum + ",打印" + str);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
代码2-2运行结果:
—————————方法内变量为线程安全—————————
0为偶数,打印a
5为奇数,打印b
1为奇数,打印b
2为偶数,打印a
4为偶数,打印a
3为奇数,打印b
—————————实例变量为非线程安全—————————
0为偶数,打印a
5为奇数,打印b
4为偶数,打印a
3为奇数,打印b
2为偶数,打印a
1为奇数,打印b
当A线程调用B对象的某个synchronized方法时,A线程就获得了B对象的对象锁,必须等A线程执行完在B对象中调用的synchronized方法,其他线程才可以调用B对象的synchronized方法
代码2-3
import java.util.Date;
class SyncClass {
public synchronized void methodA() {
System.out.println(Thread.currentThread().getName() + "进入methodA时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
System.out.println(Thread.currentThread().getName() + "调用methodB");
methodB();
System.out.println(Thread.currentThread().getName() + "离开methodA时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
}
public synchronized void methodB() {
try {
System.out.println(Thread.currentThread().getName() + "进入methodB时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "离开methodB时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class SynchronizeTest {
public static void main(String[] args) throws InterruptedException {
final SyncClass sync = new SyncClass();
Runnable runnableA = new Runnable() {
@Override
public void run() {
sync.methodA();
}
};
new Thread(runnableA, "线程A").start();
Runnable runnableB = new Runnable() {
@Override
public void run() {
sync.methodB();
}
};
new Thread(runnableB, "线程B").start();
}
}
代码2-3运行结果:
线程A进入methodA时间:2016-11-28 09:52:19
线程A调用methodB
线程A进入methodB时间:2016-11-28 09:52:19
线程A离开methodB时间:2016-11-28 09:52:20
线程A离开methodA时间:2016-11-28 09:52:20
线程B进入methodB时间:2016-11-28 09:52:20
线程B离开methodB时间:2016-11-28 09:52:21
如代码2-3运行结果所示,虽然线程B与线程A一起启动,但线程A优先获得sync对象的锁,而线程B必须等到线程A执行完才可以执行,线程A调用synchronized void methodA()方法,而methodA()又调用synchronized void methodB()方法,说明同步方法之间是可以互相调用的,如果一个线程已经获得一个对象的锁,该线程进入该对象多个synchronized方法/块
代码2-4
import java.util.Date;
class Father {
public synchronized void methodA() {
try {
System.out.println(Thread.currentThread().getName() + "进入methodA时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "离开methodA时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public synchronized void methodC() {
try {
System.out.println(Thread.currentThread().getName() + "进入 Father methodC时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "离开 Father methodC时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Son extends Father {
public synchronized void methodB() {
try {
System.out.println(Thread.currentThread().getName() + "进入methodB时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "离开methodB时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void methodC() {
try {
System.out.println(Thread.currentThread().getName() + "进入 Son methodC时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "离开 Son methodC时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class SyncExtends {
public static void main(String[] args) throws InterruptedException {
final Son son = new Son();
Runnable runnableA = new Runnable() {
@Override
public void run() {
son.methodA();
}
};
new Thread(runnableA, "A线程").start();
Runnable runnableB = new Runnable() {
@Override
public void run() {
son.methodB();
}
};
new Thread(runnableB, "B线程").start();
Thread.sleep(3000);
Runnable runnableC = new Runnable() {
@Override
public void run() {
son.methodC();
}
};
for (int i = 0; i < 3; i++) {
new Thread(runnableC, "C" + i + "线程").start();
}
}
}
代码2-4运行结果:
A线程进入methodA时间:2016-11-28 13:58:56
A线程离开methodA时间:2016-11-28 13:58:57
B线程进入methodB时间:2016-11-28 13:58:57
B线程离开methodB时间:2016-11-28 13:58:58
C2线程进入 Son methodC时间:2016-11-28 13:58:59
C0线程进入 Son methodC时间:2016-11-28 13:58:59
C1线程进入 Son methodC时间:2016-11-28 13:58:59
C1线程离开 Son methodC时间:2016-11-28 13:59:00
C2线程离开 Son methodC时间:2016-11-28 13:59:00
C0线程离开 Son methodC时间:2016-11-28 13:59:00
由代码2-4运行结果可以得出,synchronized方法继承时也可实现同步,但是如果重写某些父类的方法,原先方法有synchronized关键字而子类没有,那么调用子类重写的方法时将不能实现同步,同时,如果声明两个不同的son对象,分别由线程A和线程B调用,也不能实现同步
出现异常时,如无捕捉则会自动释放锁
代码2-5
import java.util.Date;
class ParseNum {
public synchronized void parseInt(String num) {
try {
System.out.println(Thread.currentThread().getName() + "进入parseInt方法时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
System.out.println(Thread.currentThread().getName() + "输入的数字为" + Integer.parseInt(num));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "离开parseInt方法时间" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class GiveupLock {
public static void main(String[] args) {
final ParseNum num = new ParseNum();
Runnable runnableA = new Runnable() {
@Override
public void run() {
num.parseInt("a");
}
};
Runnable runnableB = new Runnable() {
@Override
public void run() {
num.parseInt("1");
}
};
new Thread(runnableA, "线程A").start();
new Thread(runnableB, "线程B").start();
}
}
代码2-5运行结果:
线程A进入parseInt方法时间:2016-11-28 20:39:25
Exception in thread "线程A" java.lang.NumberFormatException: For input string: "a"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:492)
at java.lang.Integer.parseInt(Integer.java:527)
at com.jerry.ch2.ParseNum.parseInt(GiveupLock.java:12)
线程B进入parseInt方法时间:2016-11-28 20:39:25
线程B输入的数字为1
at com.jerry.ch2.GiveupLock$1.run(GiveupLock.java:30)
at java.lang.Thread.run(Thread.java:745)
线程B离开parseInt方法时间2016-11-28 20:39:26
代码2-5运行结果中,线程A最先开始执行,进入parseInt(String num)方法,如无异常,应该将字符串num转换为数字,然后暂停一秒,再离开parseInt(String num)方法,然而当试图将a转化为数字,异常出现了,线程A立刻释放锁,线程B进入parseInt(String num)方法,所以线程A和线程B最开始进入parseInt(String num)方法的时间是一样的
synchronized同步语句块
前面介绍过,如果调用某个对象用synchronized标记过的方法,必须等一个线程执行完此方法,其他线程才可以再次执行该对象的synchronized方法,在某些情况下这是短板,例如,对于某些方法,有一部分代码可以异步执行,而不会影响其结果,而有一部分代码则需要一个线程处理完,其他线程才可以处理,需要同步执行,这时候,就可以使用synchronized块
代码2-6
import java.util.Date;
class SyncObject {
public void methodA() {
try {
System.out.println(Thread.currentThread().getName() + "进入methodA时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
synchronized (this) {
System.out.println(Thread.currentThread().getName() + "进入methodA同步块时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "离开methodA同步块时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
}
System.out.println(Thread.currentThread().getName() + "离开methodA时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void methodB() {
try {
System.out.println(Thread.currentThread().getName() + "进入methodB时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
synchronized (this) {
System.out.println(Thread.currentThread().getName() + "进入methodB同步块时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "离开methodB同步块时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
}
System.out.println(Thread.currentThread().getName() + "离开methodB时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void methodC(Object object) {
try {
System.out.println(Thread.currentThread().getName() + "进入methodC时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "进入methodC同步块时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "离开methodC同步块时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
}
System.out.println(Thread.currentThread().getName() + "离开methodC时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void methodD(Object object) {
try {
System.out.println(Thread.currentThread().getName() + "进入methodD时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "进入methodD同步块时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "离开methodD同步块时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
}
System.out.println(Thread.currentThread().getName() + "离开methodD时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class SyncLock {
public static void main(String[] args) throws InterruptedException {
final SyncObject sync = new SyncObject();
Runnable runnableA = new Runnable() {
@Override
public void run() {
sync.methodA();
}
};
Runnable runnableB = new Runnable() {
@Override
public void run() {
sync.methodB();
}
};
new Thread(runnableA, "线程A").start();
new Thread(runnableB, "线程B").start();
Thread.sleep(5000);
System.out.println();
final Object lock = new Object();
Runnable runnableC = new Runnable() {
@Override
public void run() {
sync.methodC(lock);
}
};
Runnable runnableD = new Runnable() {
@Override
public void run() {
sync.methodD(lock);
}
};
new Thread(runnableC, "C线程").start();
new Thread(runnableD, "D线程").start();
}
}
代码2-6运行结果:
线程B进入methodB时间:2016-11-28 20:58:00
线程A进入methodA时间:2016-11-28 20:58:00
线程B进入methodB同步块时间:2016-11-28 20:58:00
线程B离开methodB同步块时间:2016-11-28 20:58:02
线程B离开methodB时间:2016-11-28 20:58:02
线程A进入methodA同步块时间:2016-11-28 20:58:02
线程A离开methodA同步块时间:2016-11-28 20:58:03
线程A离开methodA时间:2016-11-28 20:58:03
C线程进入methodC时间:2016-11-28 20:58:05
D线程进入methodD时间:2016-11-28 20:58:05
C线程进入methodC同步块时间:2016-11-28 20:58:05
C线程离开methodC同步块时间:2016-11-28 20:58:07
C线程离开methodC时间:2016-11-28 20:58:07
D线程进入methodD同步块时间:2016-11-28 20:58:07
D线程离开methodD同步块时间:2016-11-28 20:58:09
D线程离开methodD时间:2016-11-28 20:58:09
如代码2-6运行结果所示,线程A个线程B同时分别进入了methodA和methodB的代码,但是在线程B获取到this对象的锁后,线程B执行methodB中的同步代码块后,线程A才得以执行,同步代码块必须传入一个对象,对象可以是当前对象本身,也可以是其他对象
死锁检测
JDK自带工具来检测是否有死锁的现象,代码2-7为执行两个线程,两个线程都需要两个锁,但两个线程又不同抢占了两个锁,最终导致死锁,因为都无法获得对方的对象锁
代码2-7
class DeadLockThread extends Thread {
private String sign;
private static final Object LOCK1 = new Object();
private static final Object LOCK2 = new Object();
public DeadLockThread(String sign) {
this.sign = sign;
}
@Override
public void run() {
try {
if ("a".equals(sign)) {
synchronized (LOCK1) {
System.out.println(Thread.currentThread().getName() + "获取LOCK1对象锁");
Thread.sleep(1000);
synchronized (LOCK2) {
System.out.println(Thread.currentThread().getName() + "获取LOCK2对象锁");
}
}
}
if ("b".equals(sign)) {
synchronized (LOCK2) {
System.out.println(Thread.currentThread().getName() + "获取LOCK2对象锁");
Thread.sleep(1000);
synchronized (LOCK1) {
System.out.println(Thread.currentThread().getName() + "获取LOCK1对象锁");
}
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class DeadLock {
public static void main(String[] args) {
new DeadLockThread("a").start();
new DeadLockThread("b").start();
}
}
代码2-7运行结果:
Thread-0获取LOCK1对象锁
Thread-1获取LOCK2对象锁
执行jps命令查看正在运行的java进程
可以看到DeadLock的线程Run的id为5040,在执行jstack命令,可以检测出死锁的现象
锁对象
Java多线程中,synchronized方法也需要对象锁,但synchronized方法是以当前对象为对象锁,即为this,且静态方法也可以标记synchronized,当静态方法标记为synchronized时,将以静态方法所在的类的class对象,作为对象锁
代码2-8
import java.util.Date;
public class SyncLockObject {
public synchronized void methodA() {
try {
System.out.println(Thread.currentThread().getName() + "进入methodA时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "离开methodA时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void methodB() {
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName() + "进入methodB同步块时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "离开methodB同步块时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static synchronized void methodC() {
try {
System.out.println(Thread.currentThread().getName() + "进入methodC时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "离开methodC时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void methodD() {
synchronized (SyncLockObject.class) {
try {
System.out.println(Thread.currentThread().getName() + "进入methodD同步块时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "离开methodD同步块时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
final SyncLockObject object = new SyncLockObject();
Runnable runnableA = new Runnable() {
@Override
public void run() {
object.methodA();
}
};
Runnable runnableB = new Runnable() {
@Override
public void run() {
object.methodB();
}
};
new Thread(runnableA, "线程A").start();
new Thread(runnableB, "线程B").start();
Thread.sleep(3000);
System.out.println();
Runnable runnableC = new Runnable() {
@Override
public void run() {
SyncLockObject.methodC();
}
};
Runnable runnableD = new Runnable() {
@Override
public void run() {
object.methodD();
}
};
new Thread(runnableD, "线程D").start();
new Thread(runnableC, "线程C").start();
}
}
代码2-8运行结果:
线程A进入methodA时间:2016-11-29 05:30:01
线程A离开methodA时间:2016-11-29 05:30:02
线程B进入methodB同步块时间:2016-11-29 05:30:02
线程B离开methodB同步块时间:2016-11-29 05:30:03
线程D进入methodD同步块时间:2016-11-29 05:30:04
线程D离开methodD同步块时间:2016-11-29 05:30:05
线程C进入methodC时间:2016-11-29 05:30:05
线程C离开methodC时间:2016-11-29 05:30:06
由代码2-8运行结果可知,当线程A获取本身对象的对象锁执行完methodA时,线程B才得以接着获取本身对象的对象锁执行methoB,线程D执行的methodD不是静态方法,但它的对象锁是SyncLockObject.class,线程C执行的methodC是一个被标记synchronized的静态对象,但线程C必须等到线程D执行完毕,才得以执行
慎用String对象作为对象锁
代码2-9
import java.util.Date;
class StringLock extends Thread {
private String lock;
public StringLock(String lock) {
super();
this.lock = lock;
}
public void run() {
try {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "获取lock时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "释放lock时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
}
public class StringConstantsLock {
public static void main(String[] args) throws InterruptedException {
String lock1 = "lock";
String lock2 = "lock";
new StringLock(lock1).start();
new StringLock(lock2).start();
Thread.sleep(3000);
System.out.println(lock1 == lock2);
}
}
代码2-9运行结果:
Thread-0获取lock时间:2016-11-29 05:59:18
Thread-0释放lock时间:2016-11-29 05:59:19
Thread-1获取lock时间:2016-11-29 05:59:19
Thread-1释放lock时间:2016-11-29 05:59:20
true
JVM中具有String常量池缓存的功能,虽然声明两个引用,但两个引用都指向同一个lock字符串,所以两个线程分别使用两个不同的引用所指向的对象作为锁,但实际上还是使用同一个对象作为锁
代码2-10
import java.util.Date;
class StringReplace {
private String lock = "123";
public void replaceLock() {
try {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "获取lock时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
lock = "456";
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "释放lock时间:" + DateUtils.DateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class StringReplaceLock {
public static void main(String[] args) throws InterruptedException {
final StringReplace replace = new StringReplace();
Runnable runnable = new Runnable() {
@Override
public void run() {
replace.replaceLock();
}
};
new Thread(runnable, "线程A").start();
Thread.sleep(100);
new Thread(runnable, "线程B").start();
}
}
代码2-10运行结果:
线程A获取lock时间:2016-11-29 06:38:55
线程B获取lock时间:2016-11-29 06:38:55
线程A释放lock时间:2016-11-29 06:38:56
线程B释放lock时间:2016-11-29 06:38:56
由代码2-10运行结果可观察到,线程A先获取lock的对象锁,后又将lock的值修改为456,此时线程A虽然还是获得最初lock为123的对象锁,但是当线程B开始运行时,lock已经替换成另一个对象,且该对象的对象锁并未被任何线程锁获得,所以线程A和线程B在同一时间执行
由代码2-9和代码2-10可知,由于JVM对String类的特殊优化,有时反而会称为在多线程中的短板,因此在使用对象锁时,应慎重使用String对象
volatile关键字
关键字volatile的主要作用是使变量在多个线程间可见
代码2-11
class CycleThread extends Thread {
private boolean cycle = true;
public boolean isCycle() {
return cycle;
}
public void setCycle(boolean cycle) {
this.cycle = cycle;
}
@Override
public void run() {
System.out.println("线程启动");
while (cycle) {
}
System.out.println("线程结束");
}
}
public class Cycle {
public static void main(String[] args) throws InterruptedException {
CycleThread thread = new CycleThread();
thread.start();
Thread.sleep(1000);
thread.setCycle(false);
System.out.println("thread的cycle被赋值为false");
}
}
代码2-11运行结果:
线程启动
线程结束
thread的cycle被赋值为false
代码2-11的运行结果为正常Windows下JVM不配置任何参数的运行结果,当在Windows下添加JVM的配置参数-server,则会出现线程间值不同步的结果
点击Run运行将会出现另外一种结果,且线程不会终止
代码2-11将JVM设置为-server运行结果:
线程启动
thread的cycle被赋值为false
在Java虚拟机中,分client启动和server启动,client启动较快,server启动较慢,但以server启动的话,将比client拥有更高的运行性能以及更好的内存管理效率,当以server启动时,thread中的cycle变量将存在于公共堆栈及线程的私有堆栈中,线程的执行一直取得是私有堆栈的值,公有堆栈虽然将cycle设置为false,但于线程的私有堆栈却没有影响,因此线程一直循环下去
将cycle变量标记为volatile,形如:private volatile boolean cycle = true;后再次以-server运行Cycle类
代码2-11将cycle 标记为volatile变量后运行结果:
线程启动
thread的cycle被赋值为false
线程结束
将cycle 标记为volatile变量后,线程可以结束运行,volatile的作用是强制线程到公有堆栈中对cycle进行取值
synchronized和volatile比较:
- volatile只能修饰变量,synchronized可以修饰方法和块,volatile比synchronized执行效率要高
- volatile不会发生阻塞而synchronized会出现阻塞
- volatile只能保证数据可见性而不能保证原子性,而synchronized既可以保证原子性也可间接保证可见性,它会将私有内存和公共内存的数据做同步
import java.util.concurrent.atomic.AtomicInteger;
class AddCountThread extends Thread {
private AtomicInteger count = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " count:" + count.incrementAndGet());
}
}
public int getCount() {
return count.get();
}
}
public class AddCount {
public static void main(String[] args) throws InterruptedException {
AddCountThread thread = new AddCountThread();
for (int i = 0; i < 6; i++) {
new Thread(thread).start();
}
Thread.sleep(1000);
System.out.println("count:" + thread.getCount());
}
}
代码2-12运行结果:
Thread-5 count:5
Thread-6 count:7
Thread-6 count:9
Thread-6 count:10
Thread-2 count:51
Thread-1 count:54
Thread-1 count:57
Thread-1 count:58
Thread-2 count:56
Thread-2 count:59
Thread-2 count:60
count:60
synchronized代码块有volatile同步的功能
关键字synchronized可以使多个线程访问同一个资源具有同步性,而且它还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能
代码2-13
public class SyncVisible {
public static boolean STOP = true;
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
int i = 0;
while (STOP) {
i++;
}
}
};
new Thread(runnable).start();
Thread.sleep(100);
STOP = false;
}
}
如果以JVM以-server来运行的话,代码2-13将一直执行,但是如果修改runnable对象中的run()方法如代码2-14或代码2-15,程序将会结束运行,因为synchronized同步代码块会将线程的私有工作内存中的变量与公共内存中的变量进行同步,代码2-14的System.out.println()的方法里,有同步代码块
代码2-14
Runnable runnable = new Runnable() {
@Override
public void run() {
int i = 0;
while (STOP) {
i++;
System.out.println(i);
}
}
};
代码2-15
Runnable runnable = new Runnable() {
@Override
public void run() {
int i = 0;
while (STOP) {
i++;
synchronized ("") {
}
}
}
};