示例1、简单来说,此段代码展示的是两个不同的对象访问同一个同步方法的过程,实现依据不同的tag对变量num进行赋值
public class MultiThread {
private int num = 0;
public synchronized void printNum(String tag){
try {
if(tag.equals("a")){
num = 100;
System.out.println("tag a, set num over!");
Thread.sleep(1000);
} else {
num = 200;
System.out.println("tag b, set num over!");
}
System.out.println("tag " + tag + ", num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//这是两个不同的对象
MultiThread m1 = new MultiThread();
MultiThread m2 = new MultiThread();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
m1.printNum("a");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
m2.printNum("b");
}
});
t1.start();
t2.start();
}
}
输出结果如下:
tag a, set num over!
tag b, set num over!
tag b, num = 200
tag a, num = 100
由上述结果可以看出,t1、t2线程对变量num的操作并无互相影响。关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁,所以t1/t2两个对象获取的是两个不同的锁,代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁。
若对代码做如下改变,将synchronize方法同时变为static修饰的静态方法:
public class MultiThread {
private static int num = 0;
public static synchronized void printNum(String tag){
try {
if(tag.equals("a")){
num = 100;
System.out.println("tag a, set num over!");
Thread.sleep(1000);
} else {
num = 200;
System.out.println("tag b, set num over!");
}
System.out.println("tag " + tag + ", num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
输出结果如下:
tag a, set num over!
tag a, num = 100
tag b, set num over!
tag b, num = 200
在静态方法上加synchronized关键字,表示锁为类一级别的锁(独占.class类),在这种情况下,即使是两个不同的对象竞争的也是同一把锁。因此当t1进程获取锁时,t2必须等到t1操作完成释放锁之后才能进行下一步操作。
示例2、对象锁的同步异步问题
当某些资源(对象、变量等)需要与其它进程共享,设计代码时就要实现这些资源的同步性,同时保证程序的线程安全。无共享需求的资源则不必进行同步操作。
异步即每个线程都是独立运行的,彼此之间互不影响。比如web页面发起的Ajax请求一般为异步请求,待请求数据返回之后再进行加载,却丝毫不影响页面的正常操作。
public class MyObject {
public synchronized void method1(){
try {
System.out.println(Thread.currentThread().getName());
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void method2(){
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
final MyObject mo = new MyObject();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
mo.method1();
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
mo.method2();
}
},"t2");
t1.start();
t2.start();
}
}
输出结果:(即时打印完毕)
t1
t2
原因:t1线程先持有object对象的Lock锁,t2线程可以以异步的方式调用对象中的非synchronized修饰的方法
将上述代码中method2方法添加synchronize关键字修饰
public synchronized void method2(){
System.out.println(Thread.currentThread().getName());
}
代码修改后,main方法输出结果:(先打印t1,等待4S之后,再打印t2)
t1
t2
原因: t1线程先持有object对象的Lock锁,t2线程如果在这个时候调用对象中的同步(synchronized)方法则需等待,也就是同步
3、对象锁同步异步问题的应用--解决脏读问题
public class DirtyRead {
private String username = "qwert";
private String password = "123";
public synchronized void setValue(String username, String password){
this.username = username;
//注意此处是为了测试用户名立即更新成功,而密码因某原因未及时更新的情况
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.password = password;
System.out.println("setValue最终结果:username = " + username + " , password = " + password);
}
/** 若用synchronized修饰,可避免脏读 */
public synchronized void getValue(){
System.out.println("getValue方法得到:username = " + this.username + " , password = " + this.password);
}
public static void main(String[] args) throws Exception{
final DirtyRead dr = new DirtyRead();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
dr.setValue("ad", "456");
}
});
t1.start();
Thread.sleep(1000);
dr.getValue();
}
}
a. getValue方法在无synchronize关键字修饰时,程序输出如下:(等待1秒后先输出第一行,再1秒后输出第二行)
getValue方法得到:username = ad , password = 123
setValue最终结果:username = ad , password = 456
b. getValue方法在有synchronize关键字修饰时,程序输出如下:(等待2秒后顺序输出这两行)
setValue最终结果:username = ad , password = 456
getValue方法得到:username = ad , password = 456
小tips:示例1使用两个对象展示访问synchronize锁在有无static关键字修饰时的区别;示例2则是借助同一对象访问有无synchronize关键字修饰方法时的区别,注意不要搞混啦。