Synchronized和Lock区别+lambda表达式

这篇博客可能会比较长,想看结论可直接调到总结。

1.synchronized

synchronized大家应该都很熟悉,是java中一个常用的关键字。(而lock是JUC中的接口,后面会讲到)
这里用一个死锁的demo熟悉一下synchronized的用法。

public class DeadLockRunable implements Runnable{
    public int num;

    //资源(同时又两只筷子才可以吃饭)
    private static Chopsticks chopsticks1 = new Chopsticks();
    private static Chopsticks chopsticks2 = new Chopsticks();

    /**
     * num = 1 拿到 chopsticks1,等待 chopsticks2
     * num = 2 拿到 chopsticks2,等待 chopsticks1
     */

    @Override
    public void run() {
        if (num == 1){
            System.out.println(Thread.currentThread().getName()+"拿到了筷子1,等待筷子2");
            //锁定资源-chopsticks1
            synchronized (chopsticks1){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (chopsticks2){
                    System.out.println(Thread.currentThread().getName()+"用餐完毕!");
                }
            }
        }

        if (num == 2){
            System.out.println(Thread.currentThread().getName()+"拿到了筷子2,等待筷子1");
            //锁定资源-chopsticks2
            synchronized (chopsticks2){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (chopsticks1){
                    System.out.println(Thread.currentThread().getName()+"用餐完毕!");
                }
            }
        }

    }
}

test类

public class MyTest {
    public static void main(String[] args) {
        DeadLockRunable deadLockRunable1 = new DeadLockRunable();
        deadLockRunable1.num = 1;
        DeadLockRunable deadLockRunable2 = new DeadLockRunable();
        deadLockRunable2.num = 2;
        new Thread(deadLockRunable1,"李雷").start();
        new Thread(deadLockRunable2,"韩梅梅").start();

    }
}

运行会发现程序不会停止,两个线程相互等待对方锁住的资源,故处于死锁的状态。要注意的是:synchronized锁的对象一定要满足一定条件才会生效:synchronized锁住的对象一定是多个线程共享且唯一不变的元素,即内存中独一份。(个人理解)
当然,为了避免以上死锁,也很简单:
test类稍作修改,既保证两个线程拿资源不冲突就可:

public class MyTest {
    public static void main(String[] args) {
        DeadLockRunable deadLockRunable1 = new DeadLockRunable();
        deadLockRunable1.num = 1;
        DeadLockRunable deadLockRunable2 = new DeadLockRunable();
        deadLockRunable2.num = 2;
        new Thread(deadLockRunable1,"李雷").start();
        try {
            TimeUnit.MILLISECONDS.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(deadLockRunable2,"韩梅梅").start();

    }
}

2.Lock之前记一下lambda 表达式

1中的代码通过lambda 表达式可以直接写在一个类中的:

public class MyTest {
    public static void main(String[] args) {
        new Thread(()->{
            for(int i=0;i<100;i++) {
                System.out.println("+++++++++++Runnable");
            }
        }).start();

        new Thread(()->{
            for(int i=0;i<100;i++) {
                System.out.println("===========Runnable");
            }
        }).start();
    }
}

这种方式写出来的代码既优雅又简洁,实现了任务和业务逻辑的解耦合。

3.Lock

Lock是JUC(java.util.concurrent包里的)java并发工具包。
Lock 使⽤频率最⾼的实现类是 ReentrantLock(重⼊锁),可以重复上锁。
做一个小demo熟悉一下ReentrantLock:

public class ReentrantLockTest {
    public static void main(String[] args) {
        Acount acount = new Acount();
        new Thread(()->{
            acount.lock();
        }).start();
        new Thread(()->{
            acount.lock();
        }).start();

    }
}

class Acount{
    private static int num;

    private Lock lock = new ReentrantLock();

    public void lock(){
        lock.lock();
        num++;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"是第"+ num + "位访客");
        lock.unlock();
    }

}

输出结果是正确的,说明lock有效果的。你把lock()那里去掉会发现,结果两个线程都是第一位访客。因为jvm内存模型的关系。也有可能出现并发修改错误(concurrentModifiedException).
Lock的特点:

  1. Lock 上锁和解锁都需要开发者⼿动完成。
  2. 可以重复上锁,上⼏把锁就需要解⼏把锁。
  3. ReentrantLock 除了可以重⼊之外,还有⼀个可以中断的特点:可中断是指某个线程在等待获取锁的过程中可以主动过终⽌线程。
public class ReentrantLockTest {
    public static void main(String[] args) {
        StopLock stopLock = new StopLock();
    Thread t1 = new Thread(()->{
        stopLock.service();
    },"A");
    Thread t2 =new Thread(()->{
        stopLock.service();
    },"B");
t1.start();
t2.start();
try {
        TimeUnit.SECONDS.sleep(1);
        t2.interrupt();
    } catch (InterruptedException e) {
// TODO Auto-generated catch block
        e.printStackTrace();
    }
}
}
class StopLock {
    private ReentrantLock reentrantLock = new ReentrantLock();

    public void service() {
        try {
            reentrantLock.lockInterruptibly();
            System.out.println(Thread.currentThread().getName() + "get lock");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
// TODO Auto-generated catch block
                e.printStackTrace();
            }
        } catch (InterruptedException e1) {
// TODO Auto-generated catch block
            e1.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }
}

运行可发现,第二个线程过了一秒还没有拿到锁,就主动中断了线程,抛出InterruptedException异常。

4.总结

Synchronized和Lock的异同:

  1. ReentrantLock 就是对 synchronized 的升级,⽬的也是为了实现线程同步。
  2. ReentrantLock 是⼀个类,synchronized 是⼀个关键字。
  3. ReentrantLock 是 JDK 实现,synchronized 是 JVM 实现。
  4. synchronized 可以⾃动释放锁,ReentrantLock 需要⼿动释放。
  5. synchronized ⽆法判断是否获取到了锁,Lock 可以判断是否拿到了锁。
  6. synchronized 拿不到锁就会⼀直等待,Lock 不⼀定会⼀直等待
  7. synchronized 是⾮公平锁,Lock 可以设置是否为公平锁。

公平锁:很公平,排队,当锁没有被占⽤时,当前线程需要判断队列中是否有其他等待线程。
⾮公平锁:不公平,插队,当锁没有被占⽤时,当前线程可以直接占⽤,⽽不需要判断当前队列中是否
有等待线程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章