只为简单易懂,便于理解并在实际中用到:
当多个线程访问某个状态变量并且只要有一个变量执行写入操作时,必须采用同步机制来协同这些线程对变量的访问。Java主要的同步机制是关键字synchronized,他提供了一种独占的加锁方式,在线程执行加synchronized的代码时,其他的线程处于等待状态。
“同步”这个术语还包括volatile类型的变量,显式锁以及原子变量。
什么是线程安全呢?当多个线程访问某个变量时,这个线程始终能表现出正确的行为,那么就称这个类是线程安全的。既然线程是访问变量时才产生线程安全问题,那不访问变量是不是就不存在线程安全问题呢?
比如下面的简单函数:
public int getNum(int a,int b){
return a+b;
}
事实证明这样的函数在线程中一定是线程安全的,因为他不存在域就不会写入写出,所以这样的函数是线程安全的,这样的函数对应的对象叫做无状态对象,无状态的对象一定是线程安全的。
下面再说说线程安全中的原子性,比如下面函数:
public int geNum(){
int num=0;
return num++;
}
当多个线程调用这个函数时,会出现什么情况?首先读取num的值,然后再执行“加”运算,最后执行写入操作,这样线程读取的不一定是最新值,比如A线程读取num是B线程可能此时已经读取了5这个值正进行加运算,最后有可能两个线程的执行这个函数后都得到6这个值,如果在这个函数前加个synchronized会怎样呢?,这时候只有B线程执行完释放锁后A线程才会执行的,这就是加锁,其实加锁的目的就是保持了代码执行的原子性。假设有两个操作A和B,如果从执行A的线程来看,当另一个线程执行B时,要么将B全部执行完,要么完全不执行B,那么A和B对彼此来说是原子的,原子操作时指对于访问同一个状态的所有操作来说,这个操作是一个以原子方式执行的操作。
每个实例只有一个锁,其中,有synchronized修饰的代码获得锁,其他代码将处于等待状态。
有时候代码是这样的:
public calss Widget{
public synchronized void doSomething(){
}
}
public class LoggingWidget extendsWidget{
public synchronized void doSomething(){
System.out.println(toString());
super.doSomething();
}
}
由于两个类的方法都是doSomething(),那么调用子类的doSomething()方法时,会调用父类的doSomething()方法,这时候doSomething()方法已经加锁,在调用父类的doSomething()方法是不是产生死锁,会永远等待下去,其实不然,Java提供了“重入”机制,及同一个线程可以多次访问自己现在持有锁的方法。
如果同步可以避免线程不安全问题,那么为什么不在每个方法声明时都使用关键字sysnchronized?其实,过多的使用sysnchronized,可能导致程序中出现过多的同步,降低效率。
在同步代码是还需要考虑两个问题,简单性和并发性,对于并发性,应该使同步代码块尽可能的小,保证原子性,这样会在一定的实现段执行更多的程序,但这样引来一个问题,在获取和释放锁等操作总需要一定的开销,降低效率;那我们尽量把同步代码快弄大点,这样也会带来一定的问题,一个线程会长时间持有一个锁,比如下载,这样将会是程序处理速度大大降低。