多线程访问临界资源时的数据安全问题
多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问同一块资源时可能出现的问题。
同步机制可以使用synchronized关键字实现。
1 synchronized关键字
synchronized(同步的)。
synchronized关键字的作用域有2种:
1. 作用与方法:synchronized (static) void aMethod(){};
2. 作用于方法中的某个区块中: synchronized(this){/区块/};
被synchronized修饰的代码同一时刻只能被一个线程所访问。
2 同步方法
当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
当synchronized方法执行完或发生异常时,会自动释放锁。
通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:
注意:synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run()声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。
3 同步'成员方法内部'代码块(锁对象)
通过 synchronized关键字来声明 synchronized 块(成员方法内部)。语法如下:
1. 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
2. 当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
3. 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的除synchronized(this)同步代码块以外的部分。
4. 以上规则对其它对象锁同样适用(即,可以把 this 换成其他对象)。
4 同步静态方法
在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。
将synchronized作用于static 函数,示例代码如下:
静态锁不要使用Object.class(会出现错误)
在静态方法中不能使用非静态的锁
多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问同一块资源时可能出现的问题。
同步机制可以使用synchronized关键字实现。
1 synchronized关键字
synchronized(同步的)。
synchronized关键字的作用域有2种:
1. 作用与方法:synchronized (static) void aMethod(){};
2. 作用于方法中的某个区块中: synchronized(this){/区块/};
被synchronized修饰的代码同一时刻只能被一个线程所访问。
2 同步方法
当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
当synchronized方法执行完或发生异常时,会自动释放锁。
通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:
public synchronized void accessVal(int newVal){
//允许访问控制的代码
}
synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例(对象),其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为synchronized)。
注意:synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run()声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。
3 同步'成员方法内部'代码块(锁对象)
通过 synchronized关键字来声明 synchronized 块(成员方法内部)。语法如下:
synchronized(syncObject)//方法内上锁,锁都是对象,这里的锁可以是this,可以是对象
{
//允许访问控制的代码
}
synchronized (this) {// 指定本类对象作为同步锁
}
public class Person{
Object obj1 = new Object();// 指对象,作为锁
public void testSysc() {
synchronized (obj1) {//将obj1指定为该代码块的同步锁
}
}
/**
* 同步方法的锁对象为 本类对象 this
*/
public synchronized void testSysc2() {
}
}
1. 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
2. 当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
3. 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的除synchronized(this)同步代码块以外的部分。
4. 以上规则对其它对象锁同样适用(即,可以把 this 换成其他对象)。
4 同步静态方法
在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。
将synchronized作用于static 函数,示例代码如下:
public class A
{
public synchronized static void methodA(){ // 同步的static 函数 方法上锁,锁都是对象,这里的锁相当于本类所属的Class对象
//….
}
public static void methodB() {
synchronized(A.class){// class (类名称字面常量)方法内上锁,锁都是对象,这里的锁可以是只能是静态对象,或者是本类所属的Class对象,静态方法中同步代码块,锁的对象必须静态的。
}
}
}
public class Person {
static Object obj = new Object();
/**
* 同步静态方法的锁对象为 该类所属Class的对象 Person.class
*/
public static synchronized void testSysc1() {
}
/**
* 同步静态方法的锁对象为 该类所属Class的对象
*/
public static void testSysc2() {
synchronized (Person.class) {// 静态方法中同步代码块,锁的对象必须静态的。
}
}
}
代码中的methodBBB()方法是把class作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
同步静态方法的锁对象为 该类所属Class的对象 Person.class
同步静态方法的锁对象为 该类所属Class的对象
静态锁不要使用Object.class(会出现错误)
在静态方法中不能使用非静态的锁