一般来说,每个线程只有在确保资源不能被改变或者自身对资源具有独占权时,这个资源才是安全的.Java提供了同步机制来保证线程能安全的访问资源。
1.同步
通过在方法上加入 synchronized 关键字,可以保证方法在一个线程调用时,其他线程不能调用,即线程在这个方法执行期间取得了对这个方法的独占权。
public synchronized String test(){
//do some thing
}
2.同步块
与同步类似,只是可以更加灵活控制需要同步的范围。
public String test(){
synchronized(lock){
//do some thing
}
}
需要注意的是,同步块需要一个"锁",谁先获得了这个锁,谁就先进入这个"门"内,其他线程只能在门外等待,直到先进入者出来,锁自动释放,其他线程再去竞争。
仍然以之前的示例来验证。
public static void main(String[] arg){
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
print("1");
}
});
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
print("2");
}
});
thread1.start();
thread2.start();
}
public static synchronized void print(String msg){
int count=0;
while(count<10){
System.out.print(msg);
count++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行后,我们看到,两个线程有序执行了。
换成同步块,结果一致。
public static void print(String msg){
synchronized(System.out){
int count=0;
while(count<10){
System.out.print(msg);
count++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
然而,对于同步问题,仅仅通过synchronized并不是一劳永逸的解决方案。首先,Java虚拟机为了保证这种机制比然需要进行一系列的如加锁等措施,它必然导致程序性能的下降,尤其是在早期的虚拟机中,这个问题相当严重,最新的Java虚拟机一直在同步处理上改进,性能问题可能不再是问题。其次,同步本身还会大大增加死锁的机会,如果两个线程都在等待一个被对方获取了的资源才能正常工作,那么两个线程将一直等待。
事实上,也还有很多技术或思路可以完全避免同步。使用局部变量代替字段;使用简单类型的参数和字段;String是线程安全的,因为一旦创建它的值就不能被改变;创建不可变对象,它们的值只在构造函数中改变,不提供写的方法,构造函数一般不需要担心线程安全问题;使用线程本地变量ThreadLocal,这是一种将数据在线程内共享的技术,就是每个线程都会得到一份数据的副本。在实际开发中,需要结合具体的业务需求去分析 。