在分布式开发中,锁是线程控制的重要途径。Java为此也提供了2种锁机制,synchronized和lock。
1、lock是一个接口,而synchronized是java的一个关键字。
2、synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁;而lock发生异常时,不会主动释放占有的锁,必须手动来释放锁,可能引起死锁的发生,Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
3、Lock是显示锁(需要手动开关),synchronized始于隐式锁,出了作用域自动释放。
4、Lock锁只能锁代码块,不能锁方法。
5、Lock的性能更好,优先考虑Lock,其次同步代码块,再次同步方法
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
1、普通同步方法,锁是当前实例对象(this)
2、静态同步方法,锁是当前类的class对象
3、同步代码块,锁是括号里面的对象 (自己在代码块中会使用到的对象)
synchronized详解案例
对于上面的题目,我最初很笃定选B,但在实际运行代码过后,我发现C才是对的
对于A: 把run()方法改写成同步方法,那么上锁的时当前类,也就时当输出“aaa”时上锁的是t1,输出“bbb”时,上锁的时t2,那么在输出“aaa”的时候上锁t1最远,“bbb”的输出只需要t2这个资源,不需要等”aaa"输出后数释放t1,所以无法阻止交互输出。
对于B:对data上锁,那么就是在输出“aaa”的时候上锁的是“aaa“这个字符串,同理输出”bbb“的时候也一样,那么“aaa”输出的时候,“bbb”的输出也不需要“aaa“这个字符串资源,所以还是会交替输出。
对于C:当”aaa"输出的时候,上锁的是System.out,out是System的静态属性,只有一份,那么再输出“aaa”的时候上锁的是System.out,输出bbb的时候也是System.out,那么他们需要的是同一个资源,所以只有等待一方使用完毕才能交给另一方使用。
Lock是一个接口,位于java.util.concurrent.locks.Lock;它的实现类ReentrantLock位于java.util.concurrent.locks.ReentrantLock;
示例(取票问题)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class test implements Runnable {
Lock lock=new ReentrantLock();
int ticket=10;
@Override
public void run() {
while (true){
try {
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "第" + (ticket--) + "张票");
Thread.sleep(200);
} else {
break;
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
test t=new test();
new Thread(t,"黄牛").start();
new Thread(t,"粉丝").start();
new Thread(t,"票贩").start();
}
}
定义:多个线程各自占用资源,并且互相等待其他线程释放占用的资源,只有获得等其他线程释放资源后,才能继续运行,所以会出现多个线程都在等待的其他线程释放资源,就陷入了死锁。
死锁形成的必要条件:
互斥条件:一个资源只能被一个线程所使用 *互斥条件:一个资源只能被一个线程所使用
1*请求与保持条件:一个线程因请求资漓而阻塞时,己获取的资源保持不放
2*不剥夺条件:线程已获取资源,在未使用完成时,不能强行剥夺
3*请求与保持条件:一个线程因请求资漓而阻塞时,己获取的资源保持不放
4*不剥夺条件:线程已获取资源,在未使用完成时,不能强行剥夺
5*循环等待:若于线程之间形成一种头尾衔接的循环等待资源的关系 *循环等待:若于线程之间形成一种头尾衔接的循环等待资源的关系
只要破坏掉其中一种就可以避免死锁出现。
示例(只有两只筷子,当同时拿到两只筷子的时候才能吃饭)
public class person implements Runnable{
String wichkuazi;
public person(String wichkuazi) {
this.wichkuazi = wichkuazi;
}
static kuaizi a=new kuaizi("A");
static kuaizi b=new kuaizi("B");
@Override
public void run() {
eat();
}
public void eat(){
if (wichkuazi.equals("A")){
synchronized (a){
System.out.println(Thread.currentThread().getName()+"获得了筷子"+a.name);
synchronized (b){
System.out.println(Thread.currentThread().getName()+"获得了筷子"+b.name);
System.out.println(Thread.currentThread().getName()+"开始吃饭咯");
}
}
}else if (wichkuazi.equals("B")){
synchronized (b){
System.out.println(Thread.currentThread().getName()+"获得了筷子"+b.name);
synchronized (a){
System.out.println(Thread.currentThread().getName()+"获得了筷子"+a.name);
System.out.println(Thread.currentThread().getName()+"开始吃饭咯");
}
}
}
}
}
public class test {
public static void main(String[] args) {
new Thread(new person("A"),"哥哥").start();
new Thread(new person("B"),"妹妹").start();
}
}
运行:
会发现,哥哥和妹妹一人有一只筷子,都在等待对方礼让,就陷入了死锁,如果不处理,她们可能会“饿死”。
解决办法1:我们规定只能时先得到了A筷子的人才能先吃饭,修改如下
public void eat(){
if (wichkuazi.equals("A")){
synchronized (a){
System.out.println(Thread.currentThread().getName()+"获得了筷子"+a.name);
synchronized (b){
System.out.println(Thread.currentThread().getName()+"获得了筷子"+b.name);
System.out.println(Thread.currentThread().getName()+"开始吃饭咯");
}
}
}else {
System.out.println(Thread.currentThread().getName()+"请先拿A筷子");
}
}
结果
解决办法2,使用Lock锁
来源:
lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;
异常是否释放锁:
synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)
是否响应中断
lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;
是否知道获取锁
Lock可以通过trylock来知道有没有获取锁,而synchronized不能;
Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度,
1、synchronized和lock的用法区别
synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
lock:一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
2、synchronized和lock性能区别
synchronized是托管给JVM执行的,
而lock是java写的控制锁的代码。
在Java1.5中,synchronize是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。
但是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地。
2种机制的具体区别:
synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。
而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。
现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。
3、synchronized和lock用途区别
synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候。
1.某个线程在等待一个锁的控制权的这段时间需要中断
2.需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程
3.具有公平锁功能,每个到来的线程都将排队等候
下面细细道来……
先说第一种情况,ReentrantLock的lock机制有2种,忽略中断锁和响应中断锁,这给我们带来了很大的灵活性。比如:如果A、B 2个线程去竞争锁,A线程得到了锁,B线程等待,但是A线程这个时候实在有太多事情要处理,就是一直不返回,B线程可能就会等不及了,想中断自己,不再等待这个锁了,转而处理其他事情。这个时候ReentrantLock就提供了2种机制:可中断/可不中断
第一,B线程中断自己(或者别的线程中断它),但是ReentrantLock不去响应,继续让B线程等待,你再怎么中断,我全当耳边风(synchronized原语就是如此);
第二,B线程中断自己(或者别的线程中断它),ReentrantLock处理了这个中断,并且不再等待这个锁的到来,完全放弃。