在多线程中,每个线程的执行顺序,是无法预测不可控制的,那么在对数据进行读写的时候便存在由于读写顺序多乱而造成数据混乱错误的可能性。那么如何控制,每个线程对于数据的读写顺序呢?这里就涉及到线程锁。
什么是线程锁?使用锁的目的是什么?先看一个例子。
private voidtestSimple(){
SimpleRunner runner= newSimpleRunner();
pool.execute(runner);
pool.execute(runner);
}int account01 =10;int account02 = 0;class SimpleRunner implementsRunnable{
@Overridepublic voidrun() {while(true){//保证两个账户的总额度不变
account01 --;
sleep(1000);
account02++;
Console.println("account01:"+account01+" account02:"+account02);
}
}
}private void sleep(inttime){try{
Thread.sleep(time);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
调用testSimple()方法开启两个线程执行账户金额转移,运行结果如下:
account01:9 account02:2
account01:9 account02:2
account01:8 account02:4
account01:8 account02:4
account01:6 account02:6
account01:6 account02:6
account01:5 account02:7
account01:4 account02:8
很明显两个账户的金额总和无法保证为10,甚至变多了。之所以发生这种状况一方面是因为++ 和--操作不是原子操作,其次两个变量的修改也没有保证同步进行。由于线程的不确定性则将导致数据严重混乱。下面换一种方式看看如何:
我们修改while循环体,不使用++或者--操作,同时对操作进行加锁:
while(true){//保证两个账户的总额度不变
synchronized ("lock"){//通过synchronized锁把两个变量的修改进行同步
account01 = account01 -1;
account02= account02 +1;
Console.println("account01:"+account01+" account02:"+account02);
sleep(1000);
}
}
执行结果如下:
account01:9 account02:1
account01:8 account02:2
account01:7 account02:3
account01:6 account02:4
account01:5 account02:5
现在数据就能够完全正常了。这里涉及到synchronized锁,其目的就是保证在任意时刻,只允许一个线程进行对临界区资源(被锁着的代码块)的操作。
习惯上喜欢称这种机制为加锁,为了容易理解,可以把这种机制理解为一把钥匙和被锁着的代码块,只有拿到钥匙的线程才能执行被锁住的代码块。而钥匙就是synchronized(“lock”)中的字符串对象"lock",而被锁着的代码块则是{}中的代码。
某个线程如果想要执行代码块中的内容,则必须要拥有钥匙"lock"对象。但“lock”有个特性,同一时刻只允许一个线程拥有(暂时不考虑共享锁)。这样就可以保证所有的线程依次执行被锁着的代码块,避免数据混乱。在这里有一个前提条件,也就是钥匙是对于所有线程可见的,应该设置为全局变量且只有一个实例,否则每一个线程都有一个自己的钥匙,那么就起不到锁的作用了。例如:
while(true){
String lock= new String("lock");//每个线程进入run方法的时候都new一个自己的钥匙
synchronized(lock){
account01= account01 -1;
account02= account02 +1;
Console.println("account01:"+account01+" account02:"+account02);
sleep(1000);
}
}
执行结果如下:
account01:8 account02:2account01:8 account02:2account01:6 account02:3account01:6 account02:3account01:5 account02:5account01:4 account02:5
这样便又发生了混乱,每个线程都有自己的钥匙,他们随时都可以操作临界区资源,和没有加锁无任何区别。所以在多线程操作中,锁的使用至关重要!!!
在java中有哪些锁?该如何进行分类呢?
1、共享锁/排它锁
共享锁和排他锁是从同一时刻是否允许多个线程持有该锁的角度来划分。
共享锁允许同一时刻多个线程进入持有锁,访问临界区资源。而排他锁就是通常意义上的锁,同一时刻只允许一个线程访问临界资源。对于共享锁,主要是指对数据库读操作中的读锁,在读写资源的时候如果没有线程持有写锁和请求写锁,则此时允许多个线程持有读锁。
在这里理解共享锁的时候,不是任意时刻都允许多线程持有共享锁的,而是在某些特殊情况下才允许多线程持有共享锁,在某些情况下不允许多个线程持有共享锁,否则,如果没有前提条件任意时刻都允许线程任意持有共享锁,则共享锁的存在无意义的。例如读写锁中的读锁,只有当没有写锁和写锁请求的时候,就可以允许多个线程同时持有读锁。这里的前提条件就是“没有写锁和写锁请求”,而不是任意时刻都允许多线程持有共享读锁。
2、悲观锁/乐观锁
主要用于数据