Java在多线程并发中经常设计数据同步问题,如果数据不同步容易造成数据混乱,我们一般加入synchroinzed关键字实现同步。 synchronized用于多线程设计,有了synchronized关键字,多线程程序的运行结果将变得可以控制。synchronized关键字用于保护共享数据。
synchronized是上锁的关键字,上锁的方式如下:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象,key为()中的的类或者对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象,key为这个类的调用这个方法的对象;
3. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象,key为这个类本身;
一、不同步多线程调用共享数据:
public class NoSynchronized {
private int count = 0;
public void printLn(){
addCount();
Thread t = Thread.currentThread();
System.out.println("线程 "+t.getName()+" 的count值是:"+count);
}
public void addCount(){
count ++;
}
}
public class NoSynchroizedRunnable implements Runnable {
private NoSynchronized mNoSynchronized = new NoSynchronized();
public void run() {
// TODO Auto-generated method stub
for(int i = 0; i< 5; i++){
mNoSynchronized.printLn();
}
}
}
运行:
NoSynchroizedRunnable runnable = new NoSynchroizedRunnable();
Thread t1 = new Thread(runnable,"t1");
Thread t2 = new Thread(runnable,"t2");
t1.start();
t2.start();
结果:
线程 t1 的count值是:2
线程 t2 的count值是:2
线程 t1 的count值是:3
线程 t2 的count值是:4
线程 t1 的count值是:5
线程 t2 的count值是:6
线程 t1 的count值是:7
线程 t2 的count值是:8
线程 t1 的count值是:9
线程 t2 的count值是:10
分析:
从上边可以看到 多线程同时访问同一个值的时候会出现混乱的状态,这是因为线程不同步造成的,比如线程A把count值加1之后还没来得及把count值打印出来,
这时候线程B也把数据去访问count值,把count值加1 ,这是后count值就变成了2,利索当然就出现了上边数数据混乱的局面。
二、线程同步测试
public class SynchronizedMethod {
private int count = 0;
public synchronized void printLn(){
Thread t = Thread.currentThread();
count ++;
System.out.println("线程 "+t.getName()+" 的count值是:"+count);
}
}
public class SynchronizedRunnable implements Runnable{
private SynchronizedMethod mSynchronizedMethod= new SynchronizedMethod();
public void run() {
// TODO Auto-generated method stub
for(int i = 0;i<5;i++){
mSynchronizedMethod.printLn();
try {
Thread t = Thread.currentThread();
t.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
运行:
NoSynchroizedRunnable runnable = new NoSynchroizedRunnable();
Thread t1 = new Thread(runnable,"t1");
Thread t2 = new Thread(runnable,"t2");
t1.start();
t2.start();
结果:
线程 t1 的count值是:1
线程 t2 的count值是:2
线程 t2 的count值是:3
线程 t1 的count值是:4
线程 t1 的count值是:5
线程 t2 的count值是:6
线程 t1 的count值是:7
线程 t2 的count值是:8
线程 t2 的count值是:9
线程 t1 的count值是:10
分析:
从上边的结果可以看出,运行是由顺序的,没有出项前边的数据混乱现象:
这主要是 SynchronizedRunnable 方法中加了 synchronized 锁,可以实现同步。
在线程1 运行 printLn()方法的时候就会直接持有对象锁,那么其他的线程在运行 synchronized方法的时候没有相应的锁不能运行,只可以持有锁的线程放开锁(线程运行加锁的方法或代码块完毕的时候就会放开锁),其他的线程才可以运行加锁的方法或者代码块, 如此一来线程1 持有这个锁(因为printLn()方法加了锁)直到运行方法结束进入睡眠才放开锁让线程2持有锁.
对于被 synchronized 修饰的代码块或者方法都是只有其他线程持有相应的锁的情况下才可以执行,当一个线程正在执行同步的方法或者代码块的时候,那么该线程持有这个锁直到该代码块或者方法执行完毕。
在该线程持有锁的期间其他线程想要执行该代码块或者静态方法会被阻塞,直到锁被放开,锁放开之后阻塞的线程开始争取这把锁,一旦线程抢到锁那么可以执行同步方法或者代码块 ,没有抢到锁的代码块会被继续阻塞。
三、锁的验证:
public class SynchronizedKeyMethod {
private static int count = 0;
private Boolean booleanKey = new Boolean(false);
private static Boolean booleanKeyStatic = new Boolean(false);
private static Boolean booleanKeyStatic2 = new Boolean(false);
/**
* 对象锁是本身的方法
*/
public synchronized void methodKey(){
Thread t = Thread.currentThread() ;
count ++;
System.out.println("methodKey:"+"线程 "+t.getName()+" 的count值是:"+count);
}
public void methodKey1(){
synchronized (this) {
Thread t = Thread.currentThread() ;
count ++;
System.out.println("methodKey:"+"线程 "+t.getName()+" 的count值是:"+count);
}
}
public static synchronized void methodKeyStatic(){
Thread t = Thread.currentThread() ;
count ++;
System.out.println("methodKey:"+"线程 "+t.getName()+" 的count值是:"+count);
}
public void methodKeyStatic1(){
synchronized (this.getClass()) {
Thread t = Thread.currentThread() ;
count ++;
System.out.println("methodKey:"+"线程 "+t.getName()+" 的count值是:"+count);
}
}
public void keyTest(){
synchronized (booleanKey) {
Thread t = Thread.currentThread() ;
count ++;
System.out.println("methodKey:"+"线程 "+t.getName()+" 的count值是:"+count);
}
}
public void keyTest1(){
synchronized (booleanKeyStatic) {
Thread t = Thread.currentThread() ;
count ++;
System.out.println("methodKey:"+"线程 "+t.getName()+" 的count值是:"+count);
}
}
public void keyTest2(){
synchronized (booleanKeyStatic2) {
Thread t = Thread.currentThread() ;
count ++;
System.out.println("methodKey:"+"线程 "+t.getName()+" 的count值是:"+count);
}
}
}
public class SynchonizedKeyRunnable implements Runnable{
private SynchronizedKeyMethod keyMethod = new SynchronizedKeyMethod();
public void run() {
// TODO Auto-generated method stub
for(int i = 0;i <5;i++){
Thread t = Thread.currentThread();
if(t.getName().equals("t1")){
keyMethod.keyTest1();
}else{
keyMethod.keyTest2();
}
try {
t.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
运行:
Thread t1 = new Thread(new SynchonizedKeyRunnable(),"t1");
Thread t2 = new Thread(new SynchonizedKeyRunnable(),"t2");
t1.start();
t2.start();
分析:
(1)
1、keyMethod.methodKey();
2、keyMethod.methodKey1();
3、keyMethod.methodKeyStatic();
4、keyMethod.methodKeyStatic1();
结果:1、2 运行结果是混乱的,3、4运行结果是有序的,这是为什么呢?
Java中的非静态方法是 属于类的实例的,当我们new一个新的对象的到该对象的实例,那么这个实例本身就带有属于自己的方法,
java中的静态方法是 属于类本身的 ,我们可以使用这个类直接调用这个方法体。
synchronized 锁住方法的时候其实就是锁住方法的 从属本身,
比如对于非静态方法锁住的是 实例:
相当于: public synchronized void methodKey(){} = public void methodKey(){ synchronized(this){} }
静态方法锁住的是 类本身:
相当于: public static synchronized void mothodKey(){} = public void methodKey(){ synchronized(this.getClass()){} }
(2)
代码块加锁:
synchronized (key) {}
5、keyMethod.keyTest();
6、keyMethod.keyTest1();
结果:5出现混乱,6有序 为什么呢?
对于每一个加锁的代码块,只有拿到这把锁的key才可以正常执行这段代码块。
keyTest()中的代码块教的锁是非静态,而keyTest1()中加的锁是静态的。
(3)
7、if (t.getName().equals("t1")) {
keyMethod.keyTest1();
} else {
keyMethod.keyTest2();
}
结果:7出现混乱
我们都知道如果只是执行keyTest1()或者keyTest2(),那么不会出现混乱现象,但是两个一起如上边执行那么就有问题了。
对比两个方法我们发现只有一个东西不同,那就是key 不同,这样我们可以分析,在执行一个加锁的代码是时候,加了其他类型的锁的代码不会被阻塞。
总结:
1、当synchronized修饰普通方法的时候,那么锁住的对象是 该方法所处的对象。
2、当synchronized修饰静方法的时候时候,那么锁住的对对象是该法法所处的类的本身
3、当synchronized修饰代码块的时候,那么锁住的是key ( key可以是一个对象或者一个类本身 )
4、普通方法不会因为加锁的方法被持有(阻塞)而被阻塞
5、每一个锁自由一把钥匙(key),只有得到这这把钥匙才可以进入被锁住的代码块,普通方法没有加锁,随便进。