知识点

1、线程与进程

  在开始之前先把进程与线程进行区分一下,一个程序最少需要一个进程,而一个进程最少需要一个线程。关系是线程–>进程–>程序的大致组成结构。所以线程是程序执行流的最小单位,而进程是系统进行资源分配和调度的一个独立单位。以下我们所有讨论的都是建立在线程基础之上。

2、Thread的几个重要方法

  我们先了解一下Thread的几个重要方法。

    a、start()方法,调用该方法开始执行该线程;

    b、stop()方法,调用该方法强制结束该线程执行;

    c、join方法,调用该方法等待该线程结束。

    d、sleep()方法,调用该方法该线程进入等待。

    e、run()方法,调用该方法直接执行线程的run()方法,但是线程调用start()方法时也会运行run()方法,区别就是一个是由线程调度运行run()方法,一个是直接调用了线程中的run()方法!!

  看到这里,可能有些人就会问啦,那wait()和notify()呢?要注意,其实wait()与notify()方法是Object的方法,不是Thread的方法!同时,wait()与notify()会配合使用,分别表示线程挂起和线程恢复。

  这里还有一个很常见的问题,顺带提一下:wait()与sleep()的区别,简单来说wait()会释放对象锁而sleep()不会释放对象锁。这些问题有很多的资料,不再赘述。

3、线程状态

线程总共有5大状态,通过上面第二个知识点的介绍,理解起来就简单了。

  新建状态:新建线程对象,并没有调用start()方法之前
  就绪状态:调用start()方法之后线程就进入就绪状态,但是并不是说只要调用start()方法线程就马上变为当前线程,在变为当前线程之前都是为就绪状态。值得一提的是,线程在睡眠和挂起中恢复的时候也会进入就绪状态哦。
  运行状态:线程被设置为当前线程,开始执行run()方法。就是线程进入运行状态
  阻塞状态:线程被暂停,比如说调用sleep()方法后线程就进入阻塞状态
  死亡状态:线程执行结束

4、锁类型

  可重入锁:在执行对象中所有同步方法不用再次获得锁
  可中断锁:在等待获取锁过程中可中断
  公平锁: 按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利
  读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写

synchronized与Lock的区别

详情对比见下表:

Lock详解

如下为Lock接口的部分源码:

  1. public interface Lock {
  2. /**
  3. * Acquires the lock.
  4. */
  5. void lock();
  6.  
  7. /**
  8. * Acquires the lock unless the current thread is
  9. * {@linkplain Thread#interrupt interrupted}.
  10. */
  11. void lockInterruptibly() throws InterruptedException;
  12.  
  13. /**
  14. * Acquires the lock only if it is free at the time of invocation.
  15. */
  16. boolean tryLock();
  17.  
  18. /**
  19. * Acquires the lock if it is free within the given waiting time and the
  20. * current thread has not been {@linkplain Thread#interrupt interrupted}.
  21. */
  22. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  23.  
  24. /**
  25. * Releases the lock.
  26. */
  27. void unlock();
  28.  
  29. }

  从Lock接口中我们可以看到主要有5个方法,这些方法的功能从注释中可以看出:

    lock():获取锁,如果锁被暂用则一直等待
    unlock():释放锁
    tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true
    tryLock(long time, TimeUnit unit):比起tryLock()就是给了一个时间期限,保证等待参数时间
    lockInterruptibly():用该锁的获得方式,如果线程在获取锁的阶段进入了等待,那么可以中断此线程,先去做别的事
  通过 以上的解释,大致可以解释在上个部分中“锁类型(lockInterruptibly())”,“锁状态(tryLock())”等问题,还有就是前面子所获取的过程我所写的“大致就是可以尝试获得锁,线程可以不会一直等待”用了“可以”的原因。

下面是Lock一般使用的例子,注意ReentrantLock是Lock接口的实现。

lock():

  1. public class LockTest {
  2. private Lock lock = new ReentrantLock();
  3.  
  4. private void method(Thread thread) {
  5. lock.lock();
  6. try {
  7. System.out.println(thread.getName() + " has gotten the lock!");
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. } finally {
  11. System.out.println(thread.getName() + " has unlocked the lock!");
  12. lock.unlock();
  13. }
  14. }
  15.  
  16. public static void main(String[] args) {
  17. final LockTest test = new LockTest();
  18.  
  19. Thread t1 = new Thread(new Runnable() {
  20. @Override
  21. public void run() {
  22. test.method(Thread.currentThread());
  23. }
  24. }, "t1");
  25. Thread t2 = new Thread(new Runnable() {
  26. @Override
  27. public void run() {
  28. test.method(Thread.currentThread());
  29. }
  30. }, "t2");
  31. t1.start();
  32. t2.start();
  33. }
  34.  
  35. }

运行结果:

  1. t1 has gotten the lock!
  2. t1 has unlocked the lock!
  3. t2 has gotten the lock!
  4. t2 has unlocked the lock!

tryLock():

  1. public class LockTest {
  2. private Lock lock = new ReentrantLock();
  3.  
  4. private void method(Thread thread) {
  5. /*lock.lock();
  6. try {
  7. System.out.println(thread.getName() + " has gotten the lock!");
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. } finally {
  11. System.out.println(thread.getName() + " has unlocked the lock!");
  12. lock.unlock();
  13. }*/
  14. if (lock.tryLock()) {
  15. lock.lock();
  16. try {
  17. System.out.println(thread.getName() + " has gotten the lock!");
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. } finally {
  21. System.out.println(thread.getName() + " has unlocked the lock!");
  22. lock.unlock();
  23. }
  24. } else {
  25. System.out.println("I'm "+thread.getName()+". Someone has gotten the lock!");
  26. }
  27. }
  28.  
  29. public static void main(String[] args) {
  30. LockTest test = new LockTest();
  31.  
  32. Thread t1 = new Thread(() -> test.method(Thread.currentThread()), "t1");
  33. Thread t2 = new Thread(new Runnable() {
  34. @Override
  35. public void run() {
  36. test.method(Thread.currentThread());
  37. }
  38. }, "t2");
  39. t1.start();
  40. t2.start();
  41. }
  42. }

运行结果:

  1. t1 has gotten the lock!
  2. t1 has unlocked the lock!
  3. I'm t2. Someone has gotten the lock!

  看到这里相信大家也都会使用如何使用Lock了吧,关于tryLock(long time, TimeUnit unit)和lockInterruptibly()不再赘述。前者主要存在一个等待时间,在测试代码中写入一个等待时间,后者主要是等待中断,会抛出一个中断异常,常用度不高,喜欢探究可以自己深入研究。

  前面比较重提到“公平锁”,在这里可以提一下ReentrantLock对于平衡锁的定义,在源码中有这么两段:

  1. /**
  2. * Sync object for non-fair locks
  3. */
  4. static final class NonfairSync extends Sync {
  5. private static final long serialVersionUID = 7316153563782823691L;
  6. /**
  7. * Performs lock. Try immediate barge, backing up to normal
  8. * acquire on failure.
  9. */
  10. final void lock() {
  11. if (compareAndSetState(0, 1))
  12. setExclusiveOwnerThread(Thread.currentThread());
  13. else
  14. acquire(1);
  15. }
  16.  
  17. protected final boolean tryAcquire(int acquires) {
  18. return nonfairTryAcquire(acquires);
  19. }
  20. }
  21.  
  22. /**
  23. * Sync object for fair locks
  24. */
  25. static final class FairSync extends Sync {
  26. private static final long serialVersionUID = -3000897897090466540L;
  27.  
  28. final void lock() {
  29. acquire(1);
  30. }
  31.  
  32. /**
  33. * Fair version of tryAcquire. Don't grant access unless
  34. * recursive call or no waiters or is first.
  35. */
  36. protected final boolean tryAcquire(int acquires) {
  37. final Thread current = Thread.currentThread();
  38. int c = getState();
  39. if (c == 0) {
  40. if (!hasQueuedPredecessors() &&
  41. compareAndSetState(0, acquires)) {
  42. setExclusiveOwnerThread(current);
  43. return true;
  44. }
  45. }
  46. else if (current == getExclusiveOwnerThread()) {
  47. int nextc = c + acquires;
  48. if (nextc < 0)
  49. throw new Error("Maximum lock count exceeded");
  50. setState(nextc);
  51. return true;
  52. }
  53. return false;
  54. }
  55. }

  从以上源码可以看出在Lock中可以自己控制锁是否公平,而且,默认的是非公平锁,以下是ReentrantLock的构造函数:

  1. public ReentrantLock() {
  2. sync = new NonfairSync();//默认非公平锁
  3. }

补充

1、两种锁的底层实现方式

  synchronized:我们知道java是用字节码指令来控制程序(这里不包括热点代码编译成机器码)。在字节指令中,存在有synchronized所包含的代码块,那么会形成2段流程的执行。

  1. public class SyncDemo {
  2. public void sync(){
  3. synchronized (SyncDemo.class){
  4. System.out.println(" ");
  5. }
  6. }
  7. }

我们点击查看SyncDemo.java的源码SyncDemo.class,可以看到如下:

  如上就是这段代码段字节码指令,没你想的那么难吧。言归正传,我们可以清晰段看到,其实synchronized映射成字节码指令就是增加来两个指令:monitorentermonitorexit。当一条线程进行执行的遇到monitorenter指令的时候,它会去尝试获得锁,如果获得锁那么锁计数+1(为什么会加一呢,因为它是一个可重入锁,所以需要用这个锁计数判断锁的情况),如果没有获得锁,那么阻塞。当它遇到monitorexit的时候,锁计数器-1,当计数器为0,那么就释放锁。

  那么有的朋友看到这里就疑惑了,那图上有2个monitorexit呀?因为synchronized锁释放有两种机制,一种就是执行完释放另外一种就是发送异常,虚拟机释放。图中第二个monitorexit就是发生异常时执行的流程,这就是我开头说的“会有2个流程存在“。而且,从图中我们也可以看到在第13行,有一个goto指令,也就是说如果正常运行结束会跳转到19行执行。

  这下,你对synchronized是不是了解的很清晰了呢。接下来我们再聊一聊Lock。

Lock

  Lock实现和synchronized不一样,后者是一种悲观锁,它胆子很小,它很怕有人和它抢吃的,所以它每次吃东西前都把自己关起来。而Lock呢底层其实是CAS乐观锁的体现,它无所谓,别人抢了它吃的,它重新去拿吃的就好啦,所以它很乐观。具体底层怎么实现,这里不在细述。如果面试问起,就说底层主要靠volatile和CAS操作实现的。

  现在,我要说的是:尽可能去使用synchronized而不要去使用LOCK

  什么概念呢?我和大家打个比方:你叫jdk,你生了一个孩子叫synchronized,后来呢,你领养了一个孩子叫LOCK。起初,LOCK刚来到新家的时候,它很乖,很懂事,各个方面都表现的比synchronized好。你很开心,但是你内心深处又有一点淡淡的忧伤,你不希望你自己亲生的孩子竟然还不如一个领养的孩子乖巧。这个时候,你对亲生的孩子教育更加深刻了,你想证明,你的亲生孩子synchronized并不会比领养的孩子LOCK差。(只是打个比方)那如何教育呢?在jdk1.6~jdk1.7的时候,也就是synchronized16、7岁的时候,你作为爸爸,你给他优化了,具体优化在哪里呢:

1、线程自旋和适应性自旋

  我们知道,java线程其实是映射在内核之上的,线程的挂起和恢复会极大的影响开销。并且jdk官方人员发现,很多线程在等待锁的时候,在很短的一段时间就获得了锁,所以它们在线程等待的时候,并不需要把线程挂起,而是让他无目的的循环,一般设置10次。这样就避免了线程切换的开销,极大的提升了性能。

  而适应性自旋,是赋予了自旋一种学习能力,它并不固定自旋10次一下。他可以根据它前面线程的自旋情况,从而调整它的自旋,甚至是不经过自旋而直接挂起。

2、锁消除

  什么叫锁消除呢?就是把不必要的同步在编译阶段进行移除。那么有的小伙伴又迷糊了,我自己写的代码我会不知道这里要不要加锁?我加了锁就是表示这边会有同步呀?并不是这样,这里所说的锁消除并不一定指代是你写的代码的锁消除,我打一个比方:

  在jdk1.5以前,我们的String字符串拼接操作其实底层是StringBuffer来实现的(这个大家可以用我前面介绍的方法,写一个简单的demo,然后查看class文件中的字节码指令就清楚了),而在jdk1.5之后,那么是用StringBuilder来拼接的。我们考虑前面的情况,比如如下代码:

  1. String str1="qwe";
  2. String str2="asd";
  3. String str3=str1+str2;

  底层实现会变成这样:

  1. StringBuffer sb = new StringBuffer();
  2. sb.append("qwe");
  3. sb.append("asd"); 

  我们知道,StringBuffer是一个线程安全的类,也就是说两个append方法都会同步,通过指针逃逸分析(就是变量不会外泄),我们发现在这段代码并不存在线程安全问题,这个时候就会把这个同步锁消除。

3、锁粗化

  在用synchronized的时候,我们都讲究为了避免大开销,尽量同步代码块要小。那么为什么还要加粗呢?
  我们继续以上面的字符串拼接为例,我们知道在这一段代码中,每一个append都需要同步一次,那么我可以把锁粗化到第一个append和最后一个append(这里不要去纠结前面的锁消除,我只是打个比方)

4、轻量级锁

5、偏向锁

原文参考【Java知音网站

详解synchronized与Lock的区别与使用的更多相关文章

  1. (转)synchronized和lock的区别

    背景:最近在准备java基础知识,对于可重入锁一直没有个清晰的认识,有必要对这块知识进行总结. 1 . 什么是可重入锁 锁的概念就不用多解释了,当某个线程A已经持有了一个锁,当线程B尝试进入被这个锁保 ...

  2. 详解CALayer 和 UIView的区别和联系

    详解CALayer 和 UIView的区别和联系   前言 前面发了一篇iOS 面试的文章,在说到 UIView 和 CALayer 的区别和联系的时候,被喵神指出没有切中要点,所以这里就 CALay ...

  3. Java中synchronized和Lock的区别

    synchronized和Lock的区别synchronize锁对象可以是任意对象,由于监视器方法必须要拥有锁对象那么任意对象都可以调用的方法所以将其抽取到Object类中去定义监视器方法这样锁对象和 ...

  4. Java synchronized和 Lock 的区别与用法

    在分布式开发中,锁是线程控制的重要途径.Java为此也提供了2种锁机制,synchronized和lock.做为Java爱好者,自然少不了对比一下这2种机制,也能从中学到些分布式开发需要注意的地方.  ...

  5. 同步锁Synchronized与Lock的区别?

    synchronized与Lock两者区别: 1:Lock是一个接口,而Synchronized是关键字. 2:Synchronized会自动释放锁,而Lock必须手动释放锁. 3:Lock可以让等待 ...

  6. Synchronized和lock的区别和用法

    一.synchronized和lock的用法区别 (1)synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要 ...

  7. 详解path和classpath的区别

    详解path和classpath的区别 path的作用 path是系统用来指定可执行文件的完整路径,即使不在path中设置JDK的路径也可执行JAVA文件,但必须把完整的路径写出来,如C:\Progr ...

  8. Synchronized与Lock的区别与应用场景

    转载. https://blog.csdn.net/fly910905/article/details/79765381 同步代码块,同步方法,或者是用java提供的锁机制,我们可以实现对共享资源变量 ...

  9. synchronized 与 lock 的区别

    synchronized 和 lock 的用法区别 synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized 可以加在方法上,也可以加在特定代码块中,括号中表示需要锁 ...

随机推荐

  1. Linux系统中常见文件系统格式

    Windows常用的分区格式有三种,分别是FAT16.FAT32.NTFS格式. 在Linux操作系统里有Ext2.Ext3.Linux swap和VFAT四种格式. FAT16: 作为一种文件名称, ...

  2. Python_csv电子表格

    import csv with open('test.csv','w',newline='')as fp: test_writer=csv.writer(fp,delimiter=' ',quotec ...

  3. Lucene入门简介

    一  Lucene产生的背景 数据库中的搜索很容易实现,通常都是使用sql语句进行查询,而且能很快的得到查询结果. 为什么数据库搜索很容易? 因为数据库中的数据存储是有规律的,有行有列而且数据格式.数 ...

  4. PAT1100:Mars Numbers

    1100. Mars Numbers (20) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue People o ...

  5. Java公开课-04.异常

    一,异常的概念 程序在运行时,发生了我们没有预测的结果,它阻止了程序按照我们预期效果执行 二,怎么保证我们的程序在发生异常以后 ,代码继续执行? 异常处理机制 在程序发生异常以后,还能按照我们事先设定 ...

  6. redis与python交互

    import redis #连接 r=redis.StrictRedis(host="localhost",port=6379,password="sunck" ...

  7. Vector简单介绍

    /*枚举就是Vector特有的取出方式发现枚举和迭代器很像其实枚举和迭代是一样的因为枚举的名称以及方法的名称都过长了.所以被迭代器取代了枚举郁郁而终了. */ import java.util.*;c ...

  8. Android 实现个性的ViewPager切换动画 实战PageTransformer(兼容Android3.0以下)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40411921,本文出自:[张鸿洋的博客] 1.概述 之前写过一篇博文:Andro ...

  9. Java 学习笔记 (二) Selenium WebDriver Java 弹出框

    下面这段实例实现了以下功能: 1. profile使用用户本地电脑上的 (selenium 3有问题.因为selenium 3把profile复制到一个temp文件夹里,但并不复制回去.所以每次打开仍 ...

  10. freemarker导出word文档

    使用freemarker导出word文档的过程 **************************************************************************** ...