Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错。其中同步块 (或方法)可以使用关键字synchronized或使用java.util.concurrent.lock 中的类 ReentrantLock。
这里我们只介绍synchronized,其他的将陆续的介绍。
(1):为什么要采用synchronized等方式来同步。
这主要和java内存模型(简称JMM),它分为主内存区(一般为堆)和工作内存区,java线程之间是不可见的,即工作内存(n个线程存在n个工作内存)是独立,相关的变量变化都是通过主内存(有且只有一个)体现,所有和变量都是存在主内存区,而线程使用变量时进行一下操作:
1.从主内存中复制数据到工作内存
2.执行代码,对数据进行各种操作和计算
3.把操作后的变量值重新写回主内存中
当然这样的运行顺序也是我们所期望的!但是, JVM并不保证第1步和第3步会严格按照上述次序立即执行。因为根据java语言规范的规定,线程的工作内存和主存间的数据交换是松耦合的,什么时候需要刷新工作内存或者什么时候更新主存的内容,可以由具体的虚拟机实现自行决定。由于JVM可以对特征代码进行调优,也就改变了某些运行步骤的次序的颠倒,那么每次线程调用变量时是直接取自己的工作存储器中的值还是先从主存储器copy再取是没有保证的,任何一种情况都可能发生。同样的,线程改变变量的值之后,是否马上写回到主存储器上也是不可保证的,也许马上写,也许过一段时间再写。那么,在多线程的应用场景下就会出现问题了,多个线程同时访问同一个代码块,很有可能某个线程已经改变了某变量的值,当然现在的改变仅仅是局限于工作内存中的改变,此时JVM并不能保证将改变后的值立马写到主内存中去,也就意味着有可能其他线程不能立马得到改变后的值,依然在旧的变量上进行各种操作和运算,最终导致不可预料的结果。
synchronized关键字强制实施一个互斥锁,使得被保护的代码块在同一时间只能有一个线程进入并执行。当然synchronized还有另外一个方面的作用:在线程进入synchronized块之前,会把工作存内存中的所有内容映射到主内存上,然后把工作内存清空再从主存储器上拷贝最新的值。而在线程退出synchronized块时,同样会把工作内存中的值映射到主内存,但此时并不会清空工作内存。这样一来就可以强制其按照上面的顺序运行,以保证线程在执行完代码块后,工作内存中的值和主内存中的值是一致的,保证了数据的一致性!
(2):synchronized使用方式
2.1:synchronized 修饰方法。
示例代码如下:
public synchronized void getVal(int Val);
Java 中,每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能
执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。
注:在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。
class testSynchronized{
private int Val;
public synchronized void increaseVal( ){
Val++;
}
public synchronized void decreaseVal( ){
Val--;
}
}
2.2:通过 synchronized关键字来声明synchronized 块。语法如下:
synchronized(syncObject) {
//允许访问控制的代码
}
synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。
2.3:通过synchronized关键字修饰静态方法:
static synchronized method(){}
2.4:通过synchronized获取类锁:
synchronized(classname.class)
注意:
前面两个使用的锁是对象monitor,后面两者使用的是类monitor,都可以实现互斥访问。
一个对象只有一个对象monitor,一个类也只有一个类monitor。静态方法使用类monitor进行同步,而普通方法则使用对象monitor进行同步。
(3)synchronized 块与方法访问具有以下规则:
一、当两个并发线程访问同一个对象object中的这个synchronized同步代码块或方法时时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、当一个线程访问object的一个synchronized同步代码块或方法时,另一个线程仍然可以访问该object中的非synchronized同步代码块或方法。
三、当一个线程访问object的一个synchronized同步代码块或方法时,它就获取到这个object的对象锁,其他线程对object中所有其它synchronized同步代码块或方法的访问将被阻塞。
四、以上规则对其它对象锁同样适用 。
锁是和对象相关联的,每个对象有一把锁,为了执行synchronized语句,线程必须能够获得synchronized语句中表达式指定的对象的锁,一个对象只有一把锁,被一个线程获得之后它就不再拥有这把锁,线程在执行完synchronized语句后,将获得锁交还给对象。
在方法前面加上synchronized修饰符即可以将一个方法声明为同步化方法。同步化方法在执行之前获得一个锁。如果这是一个类方法,那么获得的锁是和声明方法的类相关的Class类对象的锁。如果这是一个实例方法,那么此锁是this对象的锁。
实例分析
package com.ailk.sms;
public class TestSynchronized {
int k=0;
public static void main(String args[]){
final TestSynchronized ts=new TestSynchronized();
new Thread(){
public void run(){
while( true ){
System.out.println( "线程一");
ts.operate( );
}
//this.currentThread().sel
}
}.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread t2=new Thread(){
public void run(){
while( true ){
System.out.println( "线程二");
ts.printK();
}
}
};
t2.start();
new Thread(){
public void run(){
while( true ){
System.out.println( "线程三");
ts.printK1();
}
}
}.start();
}
synchronized public void operate( ) {
System.out.println( "operate "+k );
while( true ){
// this.k=k++;
k=10000;
}
}
synchronized public void printK(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println( "printK "+k );
}
public void printK1(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println( "printK1 "+k );
}
}
执行结果为
线程一
operate 0
线程二
线程三
printK1 10000
线程三
printK1 10000
线程三
printK1 10000
线程三
printK1 10000
分析:
(1):该类TestSynchronized有三个函数,两个函数分别有synchronized修饰分别为operate,printK不含synchronized的普通函数。
(2):main方法中含有三个线程分别调用TestSynchronized的三个函数。
(3):执行结果为线程先执行线程1,线程1执行operate方法,维持类TestSynchronized对象的锁对象,但因为该方法是个死循环,因此线程1一直维持TestSynchronized对象的锁导致文件,线程二一直获取不到TestSynchronized对象锁一直阻塞【当两个并发线程访问同一个对象object中的这个synchronized同步代码块或方法时时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块】,而printK1普通的方法,不需要TestSynchronized对象的锁,所以正常输出【当一个线程访问object的一个synchronized同步代码块或方法时,另一个线程仍然可以访问该object中的非synchronized同步代码块或方法。】,