java并发编程(synchronized详解)

时间:2023-02-04 20:51:11

        平时做项目的时候,或多说少的涉及到多线程的环境,那么如何在多线程中保证线程的安全,这是我们必须要考虑的,尤其是,银行之间的转账和取钱操作之间,必须要保证,每个时刻,只能有一个线程来操纵方法。而在java中为我们提供了synchronized关键字。

例如我们有一个大房子,里面有很多的房间,这些房间有上锁的(synchronized修饰的方法),

java并发编程(synchronized详解)和普通的房间(普通的方法),然而房子的钥匙就放置在大门口,因此当第一个人来的时候,就要先去拿钥匙,如果有钥匙的话,就可以拿着钥匙进入房间去,用完后需要归还,哪怕还需要进入,也需要把钥匙归还后再去取钥匙。当然如果有很多人都在等钥匙的话,那么钥匙归谁呢,这就看JVM如何来分配资源调配了。不确定的。

 这里需要注意的是,为了保证房子里面上锁的房间的安全,正如上图所示,必须要保证上锁的房间的钥匙是唯一的。

java并发编程(synchronized详解)

        在实际中,锁可以放置在方法上,也可以作为一个单独语句块来上锁,换句话说我们也可以把一个房间里面的一块区域来上锁。(必须要保证锁是唯一的)


 

 


  

 


下面来看几个例子,通过例子来深入的理解一下synchronized关键字。

  

<span style="font-family:Comic Sans MS;font-size:18px;">package com.test;



public class TraditionThread {
public static void main(String[] args) {
final Outputer outputer = new Outputer();
new Thread(new Runnable(){
@Override
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output("哈哈哈");
}

}
}).start();

new Thread(new Runnable(){
@Override
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output2("大大大大阿达");
}

}
}).start();

}

static class Outputer {

public void output(String name) {

//如果有很多需要锁住的方法的话,则也必须保证只有一把钥匙,这个意思就是锁住的方法的钥匙都是一样的
//锁一定要是同一个对象才可以
//这样的话就保证了只有一把钥匙
String xxxString="";
int len = name.length();
//钥匙不一样,所以都可以打开这扇门,如果要保持线程唯一的话,只能有一把钥匙
synchronized (xxxString) {
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}

//而同步方法则是this对象
public synchronized void output2(String name) {

int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}


}
}
</span>

        可以看到在main方法中,同一个对象分别在两个线程中调用两个加锁的方法,这时候为了保证线程中的安全,也就是上面提到的,我在进入一个加锁房间的时候,别人是不能进入的,为了保证能够实现这种效果,因此每个房间的锁都是一样的,可以看上面的程序第一个房间的锁的钥匙是一个xxxString类型的对象,而第二个房间的锁的钥匙是当前的类,也就是this,因此房间的钥匙是不一样的,所以保证不了线程的安全,需要要修改的话,只需要将第一个房间的钥匙修改为this,即synchronized (this) {}。

 变动一

        如果我们这时候把main方法修改一下的话,让两个对象分别在两个线程中调用的话,会是怎样的效果呢?大家可想而知,这样的话,相当于我有两套同样的房子,那么这两个对象都可以拿到各自的钥匙,都可以进入,因此还是保证不了线程的安全

<span style="font-family:Comic Sans MS;font-size:18px;">public class TraditionThread {
public static void main(String[] args) {
final Outputer outputer = new Outputer();
new Thread(new Runnable(){
@Override
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output("哈哈哈");
}

}
}).start();

new Thread(new Runnable(){
@Override
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//两个对象,更加保证不了线程的安全
new Outputer().output2("大大大大阿达");
}

}
}).start();

}</span>


变动二

此时,我们再添加另外一间加锁的房间,所以修改后的代码如下

<span style="font-family:Comic Sans MS;font-size:18px;">//加锁的房子
static class Outputer {

public void output(String name) {

//如果有很多需要锁住的方法的话,则也必须保证只有一把钥匙,这个意思就是锁住的方法的钥匙都是一样的
//锁一定要是同一个对象才可以
//这样的话就保证了只有一把钥匙
String xxxString="";
int len = name.length();
//钥匙不一样,所以都可以打开这扇门,如果要保持线程唯一的话,只能有一把钥匙
synchronized (this) {
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}

//而同步方法则是this对象
//加锁的房间
public synchronized void output2(String name) {

int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}

//加锁的房间
public static synchronized void output3(String name){
int len = name.length();
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
}</span>

 下面我们再来分析一下,新加入的房子的锁的钥匙,因为是静态类,在内存中可以不用实例化对象,就可以调用,因此读取的是字节码,因此这间房子的钥匙是字节码,也就是Outputer.class,所以如果上述还是那么来做的话,还是保证不了线程的唯一。


        小结

        通过上述几个例子的分析,我们可以得到,如果使用synchronized的话,就必须保证钥匙的唯一,正因为钥匙只有一把,所以在执行某个加锁的方法时,才可以不被打扰。