前言
在上一篇博客里我提到了线程安全性的问题,谈到线程的安全性问题,不得不提的就是锁了,下面对锁进行介绍。
锁
什么是线程锁呢,上一讲我提到了锁这个概念,通俗来讲就是保证多个线程对同一共享资源的操作是串行执行的,即同一时刻只有一个线程操作共享资源,提到锁,我们在java中最常用的锁就是Synchronized同步锁了,我们先看一个不加锁的例子。
public class Demo {
public void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("a....");
}
public void b() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("b....");
}
public static void main(String[] args) {
Demo demo=new Demo();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
demo.a();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
demo.b();
}
}).start();
}
}
运行之后,我们会看到两个线程基本同时执行结束,即两个线程是并行执行的
a….
b….
但是当我们加了synchronized同步锁后会发现明显的等待,即一个线程等待另一个线程执行完后再继续执行,即两个线程是串行执行的
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("a....");
}
public synchronized void b() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("b....");
}
这就是锁的作用,线程执行时首先要获取锁,然后执行,另一个线程执行任务必须等待前面的线程释放锁,才能获取锁继续执行,从而保证线程的安全性
这里的synchornized 既可以用于修饰方法,又可以用作synchronized代码块当synchronized修饰静态方法时,此时的锁为当前类的Class对象,又叫类锁,当修饰成员方法时,默认的锁为this对象,又叫对象锁,相信大家看到这里会有疑问,这有区别吗,不都是锁吗,这里可以很认真的告诉大家有区别,不妨像一下两个线程在执行任务时,如果拿的是不同的锁,那肯定是锁不住的,所以我们要记住,如果我们希望两个线程是串行执行的,必须使用相同的锁。
大家可以思考下,下面的代码是怎么执行的,是两个线程同时执行结束,还是一个线程等待另一个线程执行完后继续执行
实例一
public class Demo {
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("a....");
}
public synchronized void b() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("b....");
}
public static void main(String[] args) {
Demo demo1=new Demo();
Demo demo2=new Demo();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
demo1.a();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
demo2.b();
}
}).start();
}
}
实例二
public class Demo {
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("a....");
}
public static synchronized void b() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("b....");
}
public static void main(String[] args) {
final Demo demo1=new Demo();
final Demo demo2=new Demo();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
demo1.a();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
demo1.b();
}
}).start();
}
}
实例三
public class Demo {
public static synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("a....");
}
public static synchronized void b() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("b....");
}
public static void main(String[] args) {
final Demo demo1=new Demo();
final Demo demo2=new Demo();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
demo1.a();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
demo2.b();
}
}).start();
}
}
答案
实例一 同时执行结束
实例二 一个线程执行结束后另一个线程才执行结束
实例三 一个线程执行结束后另一个线程才执行结束
简单分析下
实例一中 synchornized修饰的都是成员方法,所以锁是this对象(即当前执行方法的对象),demo1和demo2显然不是同一个对象,因此同时执行结束
实例二中 由于采用的同一个demo对象,因此锁是同一个对象,因此一个线程执行结束后另一个线程才执行结束
实例三中 有于synchronized修饰的是静态方法,所以锁定的是Class对象,即使行方法的demo1和demo2不是同一个对象,但是还是拿到的是同一把锁,所以一个线程执行结束后另一个线程才执行结束。
上面说完了synchronized修饰方法的情况,下面说下synchronized代码块的情况,这种就更加灵活的指定锁的对象了,而且相比前面的,代码块这种形式只是锁定方法里面的一部分代码,而不是锁定整个方法,这样就意味着代码块外的代码不用等待线程锁的释放(特别在那种一个方法里面很多代码的那种更为适用),可以提高程序的运行效率,因此在实际中代码块用的更多。
实例
public class T {
int count = 0;
synchronized void m1() {
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
count ++;
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void m2() {
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
//采用细粒度的锁,可以使线程争用时间变短,从而提高效率
synchronized(this) {
count ++;
}
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
很显然方法m2执行效率比m1高,因此同步代码块更加常用。
实例
public class Demo {
private Object obj1 = new Object();
private Object obj2 = new Object();
public void a() {
synchronized (obj1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("a....");
}
}
public void b() {
synchronized (obj2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("b....");
}
}
public static void main(String[] args) {
final Demo demo1=new Demo();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
demo1.a();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
demo1.b();
}
}).start();
}
}
这里很显然可以看到 obj1与obj2不是同一个对象,因此是同时执行结束
同步和非同步方法是否可以同时调用?
public class T {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + " m1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m1 end");
}
public void m2() {
System.out.println(Thread.currentThread().getName() + " m2 ");
}
public static void main(String[] args) {
T t = new T();
new Thread(new Runnable() {
@Override
public void run() {
t.m1();
}
}).start();
//为了使前面的线程先执行,这里睡几秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
t.m2();
}
}).start();
}
}
打印结果
Thread-0 m1 start…
Thread-1 m2
Thread-0 m1 end
可以看出后面启动的线程反而先执行结束,不需要等待前面的线程释放锁,所以同步和非同步方法可以同时调用。
总结
这篇博客主要讲了类锁和对象锁的概念以及synchorized修饰方法或者synchorized代码块的区别,这是面试中经常被问到的,希望引起大家重视!!!