1. 锁的概念

锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁)。 当一个资源被一个线程操作时,会对该资源加上锁,在锁未被释放期间,其他进行操作的线程都会陷入阻塞。

2. 为什么需要锁

当多个线程对同一个资源进行访问时,就会出现线程安全问题,就比如,你坐在桌子边手上拿着筷子正要去夹取最后一块食物时,突然被旁边的人夹走了食物,这时你就已经无法再进行操作,筷子就相当于CPU时间片,食物就相当于共享资源,所以此时程序就会发生一些无法预料的异常。

以下列购票程序为例,若ticket=1时,当线程t0执行到切换点时,失去CPU时间片切换到t1,但t1执行到切换点时也失去了CPU时间片,切换到t2线程顺利运行,ticket=0,t2运行完毕后,又切回t0继续运行,ticket=-1,又切到t1线程,ticket=-2,最后的结果会使得ticket数量出现负数,这显然是错误的。虽然这并不一定会发生,但一定有可能发生,所以线程安全也叫线程隐患。

public class Demo6 {//线程安全例子,以售票为例public static void main(String[] args) {sell s=new sell();Thread t0=new Thread(s, "线程1");Thread t1=new Thread(s, "线程2");Thread t2=new Thread(s, "线程3");t0.start();t1.start();t2.start();}
}
class sell implements Runnable{private int ticket=100;@Overridepublic void run() {// TODO Auto-generated method stubwhile(true){if(ticket>0){//切换点System.out.println("当前"+Thread.currentThread().getName()+"以出售1张票,"+"剩余"+(--ticket));}else{break;}}}}

3.锁的实现

3.1 synchronized

代码形式为:synchronized(obj){    do work    },上述购票代码则可以改成

public class Demo11 {public static void main(String[] args) {TicketSell t=new TicketSell();Thread t1=new Thread(new Run(t),"线程1");Thread t2=new Thread(new Run(t),"线程2");Thread t3=new Thread(new Run(t),"线程3");t1.start();t2.start();t3.start();}
}
class TicketSell{private Integer num=100;public void sell(){num--;}public int getNum(){return num;}
}
class Run implements Runnable{private TicketSell t;//private Object obj;//也可以通过这个对象来获取锁public Run(TicketSell t){this.t=t;}@Overridepublic void run() {while (true) {// synchronized (obj)synchronized (t) {if (t.getNum() > 0) {t.sell();System.out.println(Thread.currentThread().getName() + "出售一张");System.out.println("当前剩余" + t.getNum());try {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}} else {break;}}}}}

synchronized(obj)中的obj可以为任意对象,但必须是一个对象而不是基本类型数据,obj被称为同步锁,用术语说应该是对象监视器。但是我们可以将共享资源封装为一个对象,然后通过该对象来获取锁,在同步代码块中通过调用方法来访问资源对象。

3)同步方法:

只需在方法返回类型前加上synchronized即可,同步方法中的锁时当前实例对象的锁,也就是this。然而对于静态同步方法,锁是当前类的Class对象的锁,若一个线程任务调用此方法则另一个线程不能调用同为该类的其他静态同步方法,静态同步方法的锁只能来自于所在类的Class对象,即只能为静态同步方法或同步代码块synchronized(类名.class){  do work  }。

public class Demo12 {public static void main(String[] args) {TicketSell1 t=new TicketSell1();Thread t1=new Thread(new Run1(t),"线程1");Thread t2=new Thread(new Run1(t),"线程2");Thread t3=new Thread(new Run1(t),"线程3");t1.start();t2.start();t3.start();}
}
class TicketSell1{private Integer num=100;public synchronized void sell(){if(num>0){num--;System.out.println(Thread.currentThread().getName() + "出售一张");System.out.println("当前剩余" + num);try {Thread.sleep(100);// 可以增大线程切换的概率} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public synchronized int getNum(){return num;}
}
class Run1 implements Runnable{private TicketSell1 t;public Run1(TicketSell1 t){this.t=t;}@Overridepublic void run() {while (true) {t.sell();}}}

注意:对于需要加锁的对象,其必须是包装类型不能为基本类型,因为我们需要通过该对象来获取它所持有的锁,且应该设置为private,因为锁无法阻止线程任务直接通过访问域对象来修改值。

3.2 Lock接口显式锁

它提供了与synchronized关键字类似的同步功 能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以 及超时获取锁等多种synchronized关键字所不具备的同步特性。

1)lock锁的使用方式:

public class Demo13 {public static void main(String[] args) {TicketSell2 t=new TicketSell2();Thread t1=new Thread(new Run2(t),"线程1");Thread t2=new Thread(new Run2(t),"线程2");Thread t3=new Thread(new Run2(t),"线程3");t1.start();t2.start();t3.start();}
}
class TicketSell2{private Integer num=100;public void sell(){num--;}public int getNum(){return num;}
}
class Run2 implements Runnable{private TicketSell2 t;private Lock lock=new ReentrantLock();private Condition con1=lock.newCondition();public Run2(TicketSell2 t){this.t=t;}@Overridepublic void run() {while (true) {lock.lock();try {while (t.getNum()<=0) {con1.await();// 调用此方法阻塞当前线程,并且释放锁,便可以让另一个线程来对票数进行补充}t.sell();System.out.println(Thread.currentThread().getName() + "出售一张");System.out.println("当前剩余" + t.getNum());Thread.sleep(100);// 可以增大线程切换的概率con1.signalAll();//若票数充足则唤醒所有的被阻塞线程} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {lock.unlock();}}}}

在finally中释放锁是为了保证在获取锁以后能够释放锁,也不要将获取锁的过程写在try块中,因为如果在获取锁(自定义锁的实现)时发生了异常,异常抛出的同时,也会导致锁无故释放,必须保证return语句发生在try子句中,确保unlock()不会过早发生,将数据暴露给第二个任务。

2)Lock提供了一些synchronized所不具备的特性:

3)相关API:

4)条件对象:

Lock lock = new ReentrantLock();Condition condition = lock.newCondition();public void conditionWait() throws InterruptedException {lock.lock();try {condition.await();} finally {lock.unlock();}}public void conditionSignal() throws InterruptedException {lock.lock();try {condition.signal();} finally {lock.unlock();}}

条件对象通过Lock对象的newCondition()方法获得,当线程A中的条件对象调用await()方法时,他会进入该条件的等待集,即使其他线程已经释放了锁,他仍然会处于阻塞状态,直到某个线程使用同一个条件对象进行了signalAll()操作(signal()方法也可以,但是这个方法是随机解除等待集中的某一个线程的阻塞),使得线程A脱离阻塞状态,但并不一定会立即运行,只有他再次获得锁之后才能继续从上次运行的地点继续运行。但这也可能会带来一个问题,那就是死锁,因为线程A依赖于其他线程来唤醒,如果没有线程来进行唤醒就会造成死锁。Condition的详细API

3.3 线程本地存储

1)概念:线程本地存储是一种自动化机制,它的原理是通过根除对变量的共享,为使用相同变量的每个不同线程都创建不同的存储。例如,有5个线程都要使用变量x所代表的对象,那么本地存储会生成5个用于x的不同存储块。ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。通过这些值我们可以看做线程的一种状态表现,他是每个线程所独享的,不受其他线程影响。

public class ThreadLocalHolder {private static final ThreadLocal<Long> time = new ThreadLocal<Long>() {protected Long initialValue() {return System.currentTimeMillis();}};public static final void begin() {time.set(System.currentTimeMillis());}public static final long end() {return System.currentTimeMillis() - time.get();}public static void main(String[] args) {ThreadRunTime t1=new ThreadRunTime();ThreadRunTime t2=new ThreadRunTime();t1.start();t1.start();}
}
class ThreadRunTime extends Thread{@Overridepublic void run() {try {ThreadLocalHolder.begin();Thread.sleep(1000);Thread.yield();System.out.println(getName()+ThreadLocalHolder.end());} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}

那么如果不使用线程变量会如何?如果不使用线程变量也就是直接将time变量的类型设为Long,如果在线程休眠期间或者线程切使t1线程切换到了t2线程运行,t2运行完毕再返回t1线程时,调用ThreadLocalHolder.end()所获取的就是线程t2的运行时间。因此,从另一种角度来说通过线程变量ThreadLocal也避免了对共享资源的竞争,但是这种方法却无法实现同步,所以我们可以将ThreadLocal中所包含的对象视作线程的状态。

3.4 死锁,活锁,饥饿

理解死锁与活锁只需抓住两点:

1)死锁是两个线程都持有对方锁所需要的锁且永不释放,都等待着对方释放锁,也就是互不让步。比如线程1已经持有A锁,需要B锁才能继续运行,而线程2持有B锁,需要A锁才能继续运行,然而线程1不会释放A锁,线程2不会释放B锁,导致线程陷入无限的等待,就会导致死锁。避免死锁的方法:

  • 避免一个线程同时获取多个锁。
  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
  • 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
  • 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

2)活锁是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源,也就是互相让步。

3)饥饿是指线程的CPU时间片被其他线程完全抢占了所有CPU时间片而导致的无法运行,原因有

  • 高优先级线程吞噬所有的低优先级线程的CPU时间。
  • 线程被永久堵塞在一个等待进入同步块的状态。
  • 线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait方法)。

3.5 总结

Java中的锁可以避免多线程对同一资源竞争所引起的线程安全问题,并实现同步。必须记住, Java中的锁都是来自于对象,synchronized同步代码块必须给定一个需要进行同步的对象,也就是共享资源对象,而synchronized同步方法实际上相当于synchronized(this)形式的同步代码块,Lock显示锁也类似于synchronized同步代码块,不过Lock显示锁直接通过自身来获取锁,并且比synchronized多了一些特性。

多线程基础篇(3)——初试锁相关推荐

  1. JAVA多线程基础篇-关键字synchronized

    1.概述 syncronized是JAVA多线程开发中一个重要的知识点,涉及到多线程开发,多多少少都使用过.那么syncronized底层是如何实现的?为什么加了它就能实现资源串行访问?本文将基于上述 ...

  2. 基础篇:独占锁、共享锁、公平锁、非公平锁,叫我如何分得清

    文章目录 引言 锁的独占与共享 内置锁和显式锁的排他性 AQS 的模板方法 共享锁应用案例 锁的公平与非公平 插队的诱惑 内置锁和显式锁的公平性 启示录 引言 本文继续讲解 Java 并发编程实践的基 ...

  3. JAVA多线程基础篇 4、可见性、有序性与Volatile

    文章目录 1. 可见性问题和有序性问题 # 2. 可见性问题的实验 2.1 volatile确保了可见性 3. 一个指令乱序的实验 总结 1. 可见性问题和有序性问题 在多线程开发中,可见性问题和有序 ...

  4. 4.0 多线程基础篇

    本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末 如果觉得本文内容过长,请前往本人 "简书" 4.0-1.1 进程 概念 : 进程是指在系统中正在运行的一个应用程序 ...

  5. 多线程“基础篇”11之 生产消费者问题

    本章,会对"生产/消费者问题"进行讨论.涉及到的内容包括: 1. 生产/消费者模型 2. 生产/消费者实现 1. 生产/消费者模型 生产/消费者问题是个非常典型的多线程问题,涉及到 ...

  6. JAVA多线程基础篇-join方法的使用

    1.概述 join()是Thread类中的一个方法,它的作用是将当前线程挂起,等待其他线程结束后再执行当前线程,即当前线程等待另一个调用join()方法的线程执行结束后再往下执行.通常用于在main主 ...

  7. Java多线程基础篇(02)-多线程的实现

    为什么80%的码农都做不了架构师?>>>    1.概要 JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Call ...

  8. java多线程基础篇第一篇-JMM

    1.在开始多线程之前,我们先来聊聊计算机的缓存 计算机处理一个程序需要cpu处理器与存储设备的交互.但是在计算机发展的过程中,cpu处理器的处理速度不断提高,而存储设备的读写速度却没有得到与cpu同样 ...

  9. java多线程基础篇第二篇-volidate关键字

    volidate关键字 转载于:https://www.cnblogs.com/code-star/p/11197708.html

最新文章

  1. Java项目:角色权限后台脚手架系统(java+Springboot+Maven+myBaits-Plus+Vue+Element-UI+Mysql)
  2. matlab v7.0,matlab下载-matlab免费版v7.0 官方版下载-6188手游网
  3. python官方推荐的三本书-如果只能推荐3本关于python的书,你会推荐哪3本?
  4. 从零开始学python电子书-从零开始学Python程序设计 PDF 完整影印版
  5. 光纤通信是如何接入网络的?
  6. [MySQL优化案例]系列 -- OPTIMIZE的威力
  7. OpenCV极线epipolar lines的实例(附完整代码)
  8. 菜鸟学Java(六)——简单验证码生成(Java版)
  9. kvm虚拟化学习笔记(十)之kvm虚拟机快照备份
  10. JAVA08 多态
  11. 多线程相关知识点详解
  12. 经典的经典:《自然哲学的数学原理》
  13. SpringBoot 开发案例之各种参数传递,以及前端代码和postman测试(完整版)
  14. 有一种爱,我们不能称之为爱情
  15. docker-compose部署lepus 5.0(含lepus-console)
  16. 斗鱼直播Android开发二面被刷,真香!
  17. 含文档+PPT+源码等]精品基于ssm的足球联赛管理系统的设计与实现vue[包运行成功]计算机Java毕业设计SSM项目源码
  18. Faiss 相似度搜索使用余弦相似性
  19. IE主页注册表项修改
  20. 『R语言Python』 Excel文件的读取以及DataFrame的相关操作 (1)

热门文章

  1. 使用MASM04 - Win32汇编语言012
  2. linux vsftp的配置
  3. RedisTemplate常用方法总结
  4. 零基础Java学习之类和对象
  5. 在spark上构造随机森林模型过程的一点理解
  6. vue虚拟don diff原理
  7. findHomography(src_points, dst_points, CV_RANSAC)
  8. 数据结构与算法(十二):八大经典排序算法再回顾
  9. POJ 3259 Wormholes(负权环路)
  10. create-react-native-app