第15讲 synchronized和ReentrantLock有什么区别呢

synchronized和ReentrantLock有什么区别?有人说synchronized最慢,这话靠谱吗?

synchronized

是Java内建的同步机制,所以也有人称其为Intrinsic Locking,它提供了互斥的语义和可见性,当一个线程已经获取当前锁时,其他试图获取的线程只能等待或者阻塞在那里。

ReentrantLock

通常翻译为再入锁,是Java 5提供的锁实现,它的语义和synchronized基本相同。再入锁通过代码直接调用lock()方法获取,代码书写也更加灵活。与此同
时, ReentrantLock提供了很多实用的方法,能够实现很多synchronized无法做到的细节控制,比如可以控制fairness,也就是公平性,或者利用定义条件等。但是,编码中也需要注意,必须要明确调用unlock()方法释放,不然就会一直持有该锁。

早期版本synchronized在很多场景下性能相差较大,在后续版本进行了较多改进,在低竞争场景中表现可能优于ReentrantLock。

一个类是线程安全

类在被多个线程访问的时候,类可以持续进行正确的行为。

当多个线程访问一个类的时候,如果不考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步以及在调用方法代码不必作为其他的协调,这个类仍然是正确的,那么称这个类是安全的。

  • 《java并发编程思想》

线程安全需要保证几个基本特性:

  • 原子性,简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。
  • 可见性,是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上, volatile就是负责保证可见性的。
  • 有序性,是保证线程内串行语义,避免指令重排等。

synchronized实现同步的基础

Java中的每一个对象都可以作为锁。具体表现
为以下3种形式:

  • 对于普通同步方法,锁是当前实例对象
  • 对于静态同步方法,锁是当前类的Class对象
  • 对于同步方法块,锁是Synchonized括号里配置的对象

synchronized使用

synchronized同步方法

  • 方法中的变量不存在非线程安全问题。
  • 多个线程访问同一个对象的实例变量,可能出现非线程安全问题。
  • 通过synchronized声明的方法一定是排队运行的。
  • sychronized拥有锁重入的功能
    • 可重入锁概念:自己可以再次获取自己的内部锁。比如有一条线程获得了某个对象的锁,此时这个对象还没有释放,当这次再想获取这个对象的锁的时候还是可以获取的。
    • 父子之间也可以获得“可重入锁”
    • 出现异常的时候锁自动释放
    • 同步不具有继承性

sychronized 同步语句块

  • 方法中使用同步语句块可以解决同步方法耗费时间长的问题。
  • sychronized(任意对象非this) 也可以作为对象监视器
  • sychronized(Class)和 sychronized static静态方法作用一样。
  • sychronized尽量不使用String作为对象。因为String常量池有缓存功能。两个变量为同一个值,就变为了同一个锁。

以上来自《java多线程编程核心技术》

synchronized

我们根本无法进行公平性的选择

ReentrantLock使用

简单使用

public class MyService {
   private Lock lock =  new ReentrantLock();

   public void testMethod(){
       lock.lock();
       for (int i = 0; i < 5; i++) {
           System.out.println("ThreadName = " + Thread.currentThread().getName() +" "+(i+ 1));
       }
       lock.unlock();
   }
}
public class MyThread extends Thread {
    private MyService service;

    public MyThread(MyService service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.testMethod();
    }
}
public class Run {
    public static void main(String[] args) {
        MyService myService = new MyService();
        MyThread a1 = new MyThread(myService);
        MyThread a2 = new MyThread(myService);
        MyThread a3 = new MyThread(myService);
        MyThread a4 = new MyThread(myService);
        MyThread a5 = new MyThread(myService);

        a1.start();
        a2.start();
        a3.start();
        a4.start();
        a5.start();
    }
}

结果

ThreadName = Thread-1 1
ThreadName = Thread-1 2
ThreadName = Thread-1 3
ThreadName = Thread-1 4
ThreadName = Thread-1 5
ThreadName = Thread-2 1
ThreadName = Thread-2 2
ThreadName = Thread-2 3
ThreadName = Thread-2 4
ThreadName = Thread-2 5
ThreadName = Thread-0 1
ThreadName = Thread-0 2
ThreadName = Thread-0 3
ThreadName = Thread-0 4
ThreadName = Thread-0 5
ThreadName = Thread-4 1
ThreadName = Thread-4 2
ThreadName = Thread-4 3
ThreadName = Thread-4 4
ThreadName = Thread-4 5
ThreadName = Thread-3 1
ThreadName = Thread-3 2
ThreadName = Thread-3 3
ThreadName = Thread-3 4
ThreadName = Thread-3 5

ReentrantLock选择通知

  • Sychronized wait()和notify()是被jvm随机选择的。
  • 结合Condition可以进行选择性通知。
使用示例
public class MyService {
   private Lock lock =  new ReentrantLock();
   private Condition condition = lock.newCondition();

   public void await(){
       try {
           lock.lock();
           System.out.println("awaits时间为" + System.currentTimeMillis());
           condition.await();
       } catch (InterruptedException e) {
           e.printStackTrace();
       }finally {
           lock.unlock();
           System.out.println("await锁被释放了!");
       }
   }
    public void signal(){
        try {
            lock.lock();
            System.out.println("signal时间为" + System.currentTimeMillis());
            condition.signal();
        }finally {
            lock.unlock();
            System.out.println("signal锁被释放了!");
        }
    }
}

public class MyThread extends Thread {
    private MyService service;

    public MyThread(MyService service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.await();
    }
}

public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyService myService = new MyService();
        MyThread a1 = new MyThread(myService);
        a1.start();
        Thread.sleep(3000);
        myService.signal();
    }
}

结果:

awaits时间为1559575104934
signal时间为1559575107949
signal锁被释放了!
await锁被释放了!

结论:

  • Object类中的wait()相当于 Condition中的await()
  • Object类中的wait(long timeout)相当于Condition中的await(long time,TimeUnit unit)
  • Object中notify()相当于Contion中的signal()
  • Object中的notifyAll()相当于Condition中的signalAll()

可以new多个Condition对象来控制通知部分线程。

  private Lock lock =  new ReentrantLock();
  private Condition condition1 = lock.newCondition();
  private Condition condition2 = lock.newCondition();

对不同方法调用不同对象。

生产者消费者

https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem


再入?

它是表示当一个线程试图获取一个它已经获取的锁时,这个获取动作就自动成功,这是对锁获取粒度的一个概念,也就是锁的持
有是以线程为单位而不是基于调用次数。

Java锁实现强调再入性是为了和pthread的行为进行区分。

再入锁可以设置公平性(fairness)

,我们可在创建再入锁时选择是否是公平的。

ReentrantLock fairLock = new ReentrantLock(true);

这里所谓的公平性是指在竞争场景中,当公平性为真时,会倾向于将锁赋予等待时间最久的线程。公平性是减少线程“饥饿”(个别线程长期等待锁,但始终无法获取)情况发生的一个办法。

如果使用synchronized,我们根本无法进行公平性的选择,其永远是不公平的,这也是主流操作系统线程调度的选择。通用场景中,公平性未必有想象中的那么重要, Java默认的调度策略很少会导致 “饥饿”发生。与此同时,若要保证公平性则会引入额外开销,自然会导致一定的吞吐量下降。所以,我建议只有当你的程序确实有公平性需要的时候,才有必要指定它

带超时的获取锁尝试

tryLock(long timeout, TimeUnit unit)

可以响应中断请求

lockInterruptibly()

写一个死锁的例子

public class DealThread implements Runnable{

    public String username;
    public Object lock1 = new Object();
    public Object lock2 = new Object();

    public void setFlag(String username){
        this.username = username;
    }

    @Override
    public void run() {
        if(username.equals("a")){
            synchronized (lock1){
                try {
                    System.out.println("username =" + username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (lock2){
                    System.out.println("按 lock1 -> lock2代码执行了");
                }
            }
        }
        if(username.equals("b")){
            synchronized (lock2){
                try {
                    System.out.println("username =" + username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (lock1){
                    System.out.println("按 lock2 -> lock1代码执行了");
                }
            }
        }
    }
}

run方法

public class Run {

    public static void main(String[] args) {
        try {
            DealThread t1 = new DealThread();
            t1.setFlag("a");
            Thread thread1 = new Thread(t1);
            thread1.start();
            Thread.sleep(100);
            t1.setFlag("b");
            Thread thread2 = new Thread(t1);
            thread2.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

1.进入jdk安装bin目录
2.运行jps命令 得到运行的Run id值
3.执行jstack命令

jstack -l 9900

第15讲 synchronized和ReentrantLock有什么区别呢_第1张图片第15讲 synchronized和ReentrantLock有什么区别呢_第2张图片

参考:
极客时间:《Java核心技术面试精讲》

本笔记根据专栏主题进行学习笔记,虽然参考了许多做了笔记,但是加上了自己的整理,跟原作者的行文可能有很大偏差。如果想查看原版请自行搜索。谢谢

你可能感兴趣的:(Java核心技术面试精讲整理)