多线程的锁即用于synchronized()语句判断是否可以让当前线程进入同步代码的对象。注意:锁的本体是一个对象。
1 锁有三种作用形式:
1.1 同步代码块
synchronized(Object obj){//需要被同步的代码} 锁是Object obj
如下例子:演示2个线程倒数100到1;
1.2 同步函数
即在函数上直接标注synchronized:public synchronized void show(){//需要被同步的代码}; 锁是this
如下:
class Day13{
public static void main(String[] args){
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
class Demo implements Runnable{
private int x = 100;
public void run(){
while (true){
show();
}
}
synchronized void show(){
if (x>0){
System.out.println(Thread.currentThread().getName()+"..."+x);
x--;
}
}
}
因为非静态的方法必然要被类的对象调用,所以调用该方法的类对象this是必然存在的。非静态方法的synchronized默认锁就是用this
1.3 静态的同步函数
在静态函数上使用synchronized:static synchronized void show(){//需要被同步的代码}; 锁是this.getClass()的返回值(返回一个类),即类文件本身。
只要上述程序稍加更改即可得到静态函数的同步。
class Day13{
public static void main(String[] args){
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
class Demo implements Runnable{
private static int x = 100; //被静态函数调用,
public void run(){
while (true){
show();
}
}
static synchronized void show(){ //静态
if (x>0){
System.out.println(Thread.currentThread().getName()+"..."+x);
x--;
}
}
}
对于静态方法,它不依赖于具体的对象,而是类本身,而一个运行的类必然存在类文件,即Xxx.class文件。它就是静态方法同步中的锁。
2 锁的效率提高。
同步代码被同步之后,凡是试图执行这些代码的线程都要先判断能否获取锁,也就是在synchronized里判断一次。
如果很多线程同时运行的话,频繁判断同步就会降低程序的效率。这时最好有一定的优化。
比如单例设计模式的懒汉式:
class Single{
private static Single s = null;
private Single (){}
public static synchronized Single getInstance(){
if (s == null){
s = new Single();
}return s;
}
}
懒汉式的单例设计模式在多线程运用时可以直接用静态的同步解决,但是这样每次线程到来都会判断一次。
要避免这种情况,需要:
class Single{
private static Single s = null;
private Single (){}
public static Single getInstance(){
if (s == null){
synchronized(Single.class){
if(s == null)
s = new Single();
}
}return s;
}
}
这样,任何一个线程在执行到第一个if判断语句时,如果Single对象已经创建,则直接获取即可,不用判断是否能够获取锁,相对于上面使用同步函数的方法就升了效率。如果当前线程发现Single对象尚未创建,则再来判断是否能够获取锁。
如果判断得到Single对象尚未创建,再进入第二个if判断是否需要创建对象。
为什么明知道第一个if判断出对象尚未创建了,还需要第二个?它的意义是啥?
————这是因为有可能当此线程获取对象未创建后,获取锁之前,线程代码里正有线程正在创建Single对象,此时该线程必须被同步挡在外面,等里面的创建完了,第二个if就能告诉它前面的线程已经创建完成。它可以离开了~~这样该线程只能离开同步代码,直接获取Single对象。
3 死锁
死锁简单的说,就是两个线程各自携带这自己的锁(肯定是不同的锁),但是下一步需要获取对方的锁才能执行。但是因为线程未执行完同各自的步代码之前,是不会释放手头的锁的。这样双方都只能停止下来,称为死锁。
......
public void run(){
while(ture){
synchronized (obj){
demo();
}
}
}
synchronized void demo(){
synchronized (obj){
}
}
}
......
如上面的情形,当run和demo被同时执行的之后,因为run需要获取demo的锁(this),而demo需要获取run的锁(obj),双方未结束本次的语句之前都不会释放锁,很快他们就会产生死锁。