Java 线程安全 与 锁

多线程内存模型

  • 线程私有栈内存

    • 每个线程 私有的内存区域
  • 进程公有堆内存
    • 同一个进程 共有的内存区域

为什么会有线程安全问题?

  • 多个线程同时具有对同一资源的操作权限,又发生了同时对该资源进行读取、写入的情况,那么就会出现重复操作的情况

如何解决线程安全问题呢? 加锁

什么是锁?

锁就是对于操作资源的一种权限

锁可以做什么?

对于一个资源加锁后,每次只能有一个线程对该资源进行操作,当该线程操作结束后,才会解锁。

解锁之后,所有的线程获得竞争此资源的机会。

什么情况下需要加锁?

  • 读读 不需要加锁
  • 写写 需要加锁
  • 读写 需要加锁

加锁的两种方式(synchronized关键字与Lock对象)

第一种:synchronized关键字

  • 方法前加synchronized关键字

    • 功能:线程进入用synchronized声明的方法时就上锁,方法执行完自动解锁,锁的是当前类的对象
    • 调用synchronized声明的方法一定是排队运行的
    • 当A线程 调用object对象的synchronized声明的X方法时
      • B线程可以调用其他非synchronized声明的方法
      • B线程不能调用其他synchronized声明的非X方法
  • synchronized锁重入

    • 锁重入的概念:自己可以重复获得自己的内部锁。即synchronized声明的方法,可以调用本对象的其他synchronized方法。
    • 锁重入支持继承的环境,即子类的synchronized方法也可以调用父类的synchronized方法。
  • synchronized同步代码块

    • synchronized关键字与synchronized代码块的区别

      • synchronized声明的方法是将当前对象作为锁
      • synchronized代码块是将任意对象作为锁
    • 当两个线程访问同一个对象的synchronized代码块时,只有一个线程可以得到执行,另一个线程只能等待当前线程执行完才能执行。

      • 一半同步,一半异步

        • 不在synchronized代码块中就是异步执行,在synchronized代码块中就是同步执行

下面对“一半同步,一半异步”进行代码验证

  • 创建项目ltl0002 ,文件Task的代码如下:
  1. package ltl0002;
  2. public class Task {
  3. public void doTask(){
  4. for (int i = 0; i < 100; i++) {
  5. System.out.println("no synchronized ThreadName = " + Thread.currentThread().getName() + " i = " + (i+1));
  6. }
  7. synchronized (this){
  8. for (int i = 0; i < 100; i++) {
  9. System.out.println("synchronized ThreadName = " + Thread.currentThread().getName() + " i = " + (i+1));
  10. }
  11. }
  12. }
  13. }
  • 两个线程类代码
  1. package ltl0002;
  2. public class MyThread1 implements Runnable{
  3. private Task task = new Task();
  4. public MyThread1(Task task){
  5. this.task = task;
  6. }
  7. @Override
  8. public void run() {
  9. task.doTask();
  10. }
  11. }
  1. package ltl0002;
  2. public class MyThread2 implements Runnable{
  3. private Task task = new Task();
  4. public MyThread2(Task task){
  5. this.task = task;
  6. }
  7. @Override
  8. public void run() {
  9. task.doTask();
  10. }
  11. }

文件Run.java代码如下:

  1. package ltl0002;
  2. public class Run {
  3. public static void main(String[] args) {
  4. Task task = new Task();
  5. MyThread1 myThread1 = new MyThread1(task);
  6. MyThread2 myThread2 = new MyThread2(task);
  7. Thread tr1 = new Thread(myThread1);
  8. Thread tr2 = new Thread(myThread2);
  9. tr1.start();
  10. tr2.start();
  11. }
  12. }

程序运行结果如图所示

进入synchronized代码块之后,排队运行,运行结果如图所示

在第一张图我们可以看到,线程0 和 1交叉输出,说明是异步进行,而在第二张图可以看出线程0运行完之后,线程1才运行,说明它们是同步运行,验证完毕。

  • 现有三个线程,线程一对num进行修改,线程二三对num进行读取,如何可以实现,线程一与线程二三同步执行,而线程二三异步执行呢?

    现在创建项目ltl0003进行测试,Number文件代码如下
  1. package ltl0003;
  2. /**
  3. * @author liTianLu
  4. * @Date 2022/4/23 15:53
  5. * @purpose 成员变量有int num,以及get set方法
  6. */
  7. public class Number {
  8. private int num;
  9. private boolean change = false;
  10. public int getNum() {
  11. return num;
  12. }
  13. public void setNum(int num) {
  14. this.num = num;
  15. }
  16. public boolean isChangeing(){
  17. return change;
  18. }
  19. public void setChange(boolean change) {
  20. this.change = change;
  21. }
  22. }

两个线程类的代码如下:

  1. package ltl0003;
  2. /**
  3. * @author liTianLu
  4. * @Date 2022/4/23 15:36
  5. * @purpose 更改num的值
  6. */
  7. public class MyThread01 implements Runnable{
  8. static int num = 0;
  9. Number number;
  10. public MyThread01(Number num ){
  11. this.number = num ;
  12. }
  13. @Override
  14. public void run() {
  15. synchronized (this){
  16. number.setChange(true);
  17. for (int i = 0; i < 10000; i++) {
  18. number.setNum(num++);
  19. }
  20. number.setChange(false);
  21. }
  22. }
  23. }
  1. package ltl0003;
  2. import static java.lang.Thread.sleep;
  3. /**
  4. * @author liTianLu
  5. * @Date 2022/4/23 15:35
  6. * @purpose 读取num的值
  7. */
  8. public class MyThread02 implements Runnable{
  9. Number number;
  10. public MyThread02(Number num ){
  11. this.number = num ;
  12. }
  13. @Override
  14. public void run() {
  15. for (int i = 0; i < 1000 ; i++) {
  16. //如果number正在更改,就休眠1ms
  17. while(number.isChangeing()){
  18. try {
  19. sleep(1);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. System.out.println(Thread.currentThread().getName()+"的输出为: num = " + number.getNum());
  25. }
  26. }
  27. }

主函数文件Run代码如下:

  1. package ltl0003;
  2. /**
  3. * @author liTianLu
  4. * @Date 2022/4/23 15:15
  5. * @purpose 解决锁问题 线程一对num进行修改,线程二三对num进行读取,此代码要实现:线程一与线程二三同步执行,而线程二三异步执行。
  6. */
  7. public class Run {
  8. public static void main(String[] args) {
  9. Number number = new Number();
  10. number.setNum(0);
  11. MyThread01 myThread01 = new MyThread01(number);
  12. MyThread02 myThread02 = new MyThread02(number);
  13. Thread tr1 = new Thread(myThread01);
  14. Thread tr2 = new Thread(myThread02);
  15. Thread tr3 = new Thread(myThread02);
  16. tr1.start();
  17. tr2.start();
  18. tr3.start();
  19. }
  20. }

实验结果如图所示

我们发现,线程2/3执行的时候,线程1已经执行完毕,且线程2、3异步进行。

第二种:Lock对象的使用

  • ReentrantLock类可以达到与synchronized同样的效果。
  • 用法:
  1. ReentrantLock lock = new ReentrantLock ();
  2. lock.lock();//加锁
  3. lock.unlock();//解锁
  4. //使用try catch finally 可以确保finally 中的代码执行,在finally中解锁
  5. try{
  6. while(true){
  7. lock.lock ();
  8. //操作代码
  9. }
  10. }catch (Exception e) {
  11. e.printStackTrace();
  12. }finally {
  13. lock.unlock ();
  14. }

Java 线程安全 与 锁的更多相关文章

  1. Java线程安全与锁优化

    线程安全的严谨定义: 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交题执行,也不需要进行额外的同步,或者调用方法进行其他任何操作,调用这个对象的行为都可以或者正确的结果,那么这 ...

  2. 【Java线程安全】锁

    Java都有哪些锁? synchronized 和 reentranlock是最常见的,其中前者又JVM提供实现,后者有专门对应的java.util.concurrent包提供:同时后者功能更加丰富. ...

  3. Java线程同步与锁

    一.synchronized synchronized锁什么?锁对象.可能锁对象包括: this, 临界资源对象,Class类对象. 1,同步方法 synchronized T methodName( ...

  4. 【Thread】java线程之对象锁、类锁、线程安全

    说明: 1.个人技术也不咋滴.也没在项目中写过线程,以下全是根据自己的理解写的.所以,仅供参考及希望指出不同的观点. 2.其实想把代码的github贴出来,但还是推荐在初学的您多亲自写一下,就没贴出来 ...

  5. Java线程安全与锁优化,锁消除,锁粗化,锁升级

    线程安全的定义 来自<Java高并发实战>"当多个线程访问一个对象的时候,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法的时候进行任何 ...

  6. Java线程(二):线程同步synchronized和volatile

    上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程 ...

  7. Java线程专栏文章汇总(转)

    原文:http://blog.csdn.net/ghsau/article/details/17609747 JDK5.0之前传统线程        Java线程(一):线程安全与不安全 Java线程 ...

  8. Java线程专栏文章汇总

        转载自 http://blog.csdn.net/ghsau/article/details/17609747 JDK5.0之前传统线程        Java线程(一):线程安全与不安全 J ...

  9. java线程之二(synchronize和volatile方法)

    要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性.多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现.拿上篇博文中的例子来说明,在多个线程之间共享了Count ...

随机推荐

  1. 题解0004:单词接龙(洛谷P1019)

    题目描述:已知一组单词,给定一个开头的字母,要求出以这个字母开头的最长的"龙"(每个单词都最多在"龙"中出现两次),在两个单词相连时,其重合部分合为一部分. 题 ...

  2. 什么是BASH?

    BASH是Bourne Again SHell的缩写.它由Steve Bourne编写,作为原始Bourne Shell(由/ bin / sh表示)的替代品.它结合了原始版本的Bourne Shel ...

  3. Oracle入门基础(十一)一一PL/SQL基本语法

    1.打印Hello World declare --说明部分 begin --程序 dbms_output.put_line('Hello World'); end; 2.引用型变量 查询并打印783 ...

  4. 并发场景下HashMap死循环导致CPU100%的问题

    参考链接:并发场景下HashMap死循环导致CPU100%的问题

  5. vue使用svg,animate事件绑定无效问题及解决方法

    由于使用svg制作圆形进度条,但是进度展示的太生硬,没有过渡圆滑的效果,所以使用 animate(在svg元素里可以查到) 元素标签,但 这样使用了,还是没有效果,我前端使用的 vue ,所以通过 @ ...

  6. Math.round(11.5) 等于多少?Math.round(-11.5)等于 多少?

    Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11.四舍五 入的原理是在参数上加 0.5 然后进行下取整.

  7. Django的多数据库与读写分离

    1.多个数据库 settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.pa ...

  8. vue循环时设置多选框禁用状态,v-for

    <div v-for="user in users" >            <el-radio v-bind:disabled="user.id== ...

  9. 全网最硬核 Java 新内存模型解析与实验单篇版(不断更新QA中)

    个人创作公约:本人声明创作的所有文章皆为自己原创,如果有参考任何文章的地方,会标注出来,如果有疏漏,欢迎大家批判.如果大家发现网上有抄袭本文章的,欢迎举报,并且积极向这个 github 仓库 提交 i ...

  10. IE中的编码位置

    进入设置 找到Editor 找到File Encodings