一、银行取款引出的问题

模拟银行取钱的例子:

public class ThreadDemo06 {public static void main(String[] args) {Bank bank = new Bank();Runnable runnable = new MoneyThread(bank);Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable);thread1.start();thread2.start();}
}class Bank {private int money = 1000;public int get(int number) {if (number < 0) {return -1;} else if (number > money) {return -2;} else if (money < 0) {return -3;} else {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}money -= number;System.err.println("还剩:" + money);return number;}}
}class MoneyThread implements Runnable {private Bank bank;public MoneyThread(Bank bank) {this.bank = bank;}@Overridepublic void run() {bank.get(800);}
}

运行可能的结果:

还剩:200
还剩:-600

造成此类问题的根本原因在于,多个线程在操作共享的数据或者操作共享数据的线程代码有多行,当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程的安全问题的产生

二、问题的解决方案

在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程就不能再使用那个资源,除非被解锁。同一时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。

将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码时其他线程是不可以参与运算的,必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算;

保证取钱和修改余额同时完成:

1)使用同步代码块,synchronized(obj){},还需要一个同步监听对象;

2)使用同步方法,使用synchronized去修饰需要同步的方法;

方法一:同步方法

在Java中通过synchronized关键字来完成对对象的加锁。

上述代码加锁的解决方案如下:

class Bank {private int money = 1000;public synchronized int get(int number) {if (number < 0) {return -1;} else if (number > money) {return -2;} else if (money < 0) {return -3;} else {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}money -= number;System.err.println("还剩:" + money);return number;}}
}

方法二:同步代码块

synchronized块写法:

synchronized(object){

} //表示线程在执行的时候会对object对象上锁

这里的object可以是任意对象,但必须保证多个线程持有的这个object是同一个对象;

synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;synchronized块则是一种细粒度的并发控制,只有将块中的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的。

三、线程同步的关键知识点

1)Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该对象的synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才可能再去访问该对象的其他synchronized方法

public class ThreadDemo07 {public static void main(String[] args) {Example example = new Example();Runnable runnable = new TheThread(example); //同一对象
Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable);thread1.start();thread2.start();}
}class Example {public synchronized void execute() {for (int i = 0; i < 10; i++) {try {Thread.sleep((long) (Math.random() * 1000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println("execute :" + i);}}
}class TheThread implements Runnable {private Example example;public TheThread(Example example) {this.example = example;}public void run() {example.execute();}
}

上述代码的执行:由于是对同一对象产生的线程,当两个不同线程进行访问的时候,谁先进入synchronized方法就将该example对象上锁了,其他线程就没有办法再进入该对象的任何同步方法了,所以只有当一个线程执行完毕或者抛出异常后第二个线程才能进行访问。

public class ThreadDemo08 {public static void main(String[] args) {Runnable runnable = new TheThread(new Example());Runnable runnable2 = new TheThread(new Example());Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable2);thread1.start();thread2.start();}
}class Example {public synchronized void execute() {for (int i = 0; i < 10; i++) {try {Thread.sleep((long) (Math.random() * 1000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println("execute :" + i);}}
}class TheThread implements Runnable {private Example example;public TheThread(Example example) {this.example = example;}public void run() {example.execute();}
}

上述代码的执行:由于是两个线程对两个不同对象进行访问操作。那么这2个线程就没有任何关联,各自访问各自的对象,互不干扰。

public class ThreadDemo09 {public static void main(String[] args) {Example example = new Example();Runnable runnable = new TheThread(example);//同一对象
Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable);thread1.start();thread2.start();}
}class Example {public synchronized void execute() {for (int i = 0; i < 10; i++) {try {Thread.sleep((long) (Math.random() * 1000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println("execute :" + i);}}public synchronized void execute2() {for (int i = 0; i < 10; i++) {try {Thread.sleep((long) (Math.random() * 1000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println("execute2 :" + i);}}
}class TheThread implements Runnable {private Example example;public TheThread(Example example) {this.example = example;}public void run() {example.execute();}
}class TheThread2 implements Runnable {private Example example;public TheThread2(Example example) {this.example = example;}public void run() {example.execute2();}
}

上述代码的执行结果:是由同一对象生成的两个不同的线程,当两个不同的线程访问同一对象不同的synchronized方法时,谁先进入第一个synchronized方法,那么该线程就将该对象上锁了,其他线程是没有办法再对该对象的任何synchronized方法进行访问。

public class ThreadDemo10 {public static void main(String[] args) {Example example = new Example();Runnable runnable = new TheThread(example);Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable);thread1.start();thread2.start();}
}class Example {public synchronized static void execute() {for (int i = 0; i < 10; i++) {try {Thread.sleep((long) (Math.random() * 1000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println("execute :" + i);}}public synchronized static void execute2() {for (int i = 0; i < 10; i++) {try {Thread.sleep((long) (Math.random() * 1000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println("execute2 :" + i);}}
}

上述代码的执行结果:由于静态方法是属于类级别的,当一个方法锁住后,只有等第一个线程执行完毕以后第二个线程才能进入。

2)synchronized方法使用了static关键字进行修饰,表示将该对象的Class对象加锁

public class ThreadDemo11 {public static void main(String[] args) {Runnable runnable = new TheThread(new Example());Runnable runnable2 = new TheThread(new Example());Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable2);thread1.start();thread2.start();}
}class Example {public synchronized static void execute() {for (int i = 0; i < 10; i++) {try {Thread.sleep((long) (Math.random() * 1000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println("execute :" + i);}}public synchronized static void execute2() {for (int i = 0; i < 10; i++) {try {Thread.sleep((long) (Math.random() * 1000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println("execute2 :" + i);}}
}

上述代码的执行结果:虽然是针对两个不同对象生成的不同的线程,但是由于synchronized方法使用了static关键字进行修饰,表示将该对象的Class对象加锁。所以只有等一个线程执行完毕后,其他线程才能进入访问。

3) 如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的

4)如果某个synchronized方法是static的,那么当线程访问该方法时,它的锁并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为Java中无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static, synchronized方法时,他们的执行顺序也是有顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行

public class ThreadDemo02 {public static void main(String[] args) {C c = new C();Thread t1 = new T1(c);Thread t2 = new T2(c);t1.start();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}t2.start();}
}class C {public synchronized static void hello() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hello");}public synchronized void world() {System.out.println("world");}
}class T1 extends Thread {private C c;public T1(C c) {this.c = c;}@Overridepublic void run() {c.hello();}
}class T2 extends Thread {private C c;public T2(C c) {this.c = c;}@Overridepublic void run() {c.world();}
}

执行结果:先执行world,然后才输出hello,原因是static是给当前对象的Class对象上锁,而没有static的是给当前对象上锁,两把锁锁的对象不同,所以并没有影响。

四、线程同步总结

synchronized修饰方法

1)非静态方法:默认的同步监听器对象是this;

2))静态方法:默认的同步监听器对象是该方法所在类的Class对象。

若线程是实现方式

1)同步代码块:同步监听对象可以选this、这个方法所在类的Class对象、任一不变对象;

2)同步方法:此时可以使用synchronized直接修饰run方法,因为同步监听器是this。

若线程是继承方式

1)同步代码块:同步监听器可以选用该方法所在类的Class对象、任一不变对象;

2)同步方法:此时不能使用synchronized直接修饰run方法;

3)总结:只要是继承方式,不论是同步代码块还是同步方法均不能使用this。

同步的利弊

1)好处:解决了线程的安全问题;

2)弊端:相对降低了效率,因为同步外的线程的都会判断同步锁;

3)前提:同步中必须有多个线程并使用同一个锁。

转载于:https://www.cnblogs.com/luogankun/p/3986705.html

jdk线程的同步问题相关推荐

  1. 线程同步,线程不同步_重新同步多线程集成测试

    线程同步,线程不同步 我最近在Captain Debug的Blog上偶然发现了一篇文章" 同步多线程集成测试 ". 那篇文章强调了设计涉及异步运行业务逻辑的被测类的集成测试的问题. ...

  2. 【JAVA SE】第十六章 进程、线程、同步锁和线程锁的简介

    第十六章 进程.线程.同步锁和线程安全问题 文章目录 第十六章 进程.线程.同步锁和线程安全问题 一.进程 1.基本介绍 2.进程模型 二.线程 1.基本介绍 2.线程的生命周期 3.线程的优先级 4 ...

  3. java伪唤醒,谈谈JDK线程的伪唤醒

    在JDK的官方的wait()方法的注释中明确表示线程可能被"虚假唤醒",JDK也明确推荐使用while来判断状态信息.那么这种情况的发生的可能性有多大呢? 使用生产者消费者模型来说 ...

  4. 02java进阶03-异常、线程、同步、线程池、Lambda表达式、File类、递归

    目录 一.异常 二.异常的处理 三.自定义异常 四.多线程 五.线程.同步 5.1.线程 5.2同步 5.3线程安全 5.4线程状态 六.等待唤醒机制 6.1 线程间通信 6.2 等待唤醒机制 6.3 ...

  5. 深入理解JVM之代码执行机制与线程资源同步及交互机制

    Java规范定义标准结构如图3.1 Java代码的执行机制 Java源码编译机制 javac将Java源码编译为class文件的步骤如图3.2 1.分析和输入到符号表(Parse and Enter) ...

  6. 15分钟读懂进程线程、同步异步、阻塞非阻塞、并发并行,太实用了!

    作者:Martin cnblogs.com/mhq-martin/p/9035640.html 基本概念 1 进程和线程 进程(Process): 是Windows系统中的一个基本概念,它包含着一个运 ...

  7. JDK线程池的ThreadFactory

    JDK线程池:Executors.newFixedThreadPool , Executors.newSingleThreadExecutor,由一个ThreadFactory来创建新的线程,默认情况 ...

  8. 第八章 用户方式中线程的同步(2)

    二.高级线程同步 如果线程访问共享资源或者等待一些特殊事件的发生,如果共享资源可用或特殊事件已发生则函数返回同时该进程保持可调度状态,否则该线程处于等待状态.系统将处于等待状态的线程不占用系统资源(不 ...

  9. C#之任务,线程和同步

    1 概述 对于所有需要等待 的操作,例 如 ,因 为文件 . 数据库或网络访 问都需要一定 的时间,此 时就可以启 动一个新线程,同时完成其他任务,即使是处理密集型的任务,线程也是有帮助的. 2 Pa ...

  10. Windows核心编程 第八章 用户方式中线程的同步(下)

    8.4 关键代码段 关键代码段是指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权.这是让若干行代码能够"以原子操作方式"来使用资源的一种方法.所谓原子操作方式,是 ...

最新文章

  1. Go 分布式学习利器(16) -- go中可复用的package构建
  2. 第七篇:数据预处理(四) - 数据归约(PCA/EFA为例)
  3. python爬虫入门教程--优雅的HTTP库requests(二)
  4. c语言数码管数字时钟程序,数码管显示模拟8位时钟C语言程序设计
  5. 如何复制计算机页面,怎么把电脑命令行窗口里的内容复制到剪贴板
  6. Spring Boot (四)模板引擎Thymeleaf集成
  7. keras保存和载入模型继续训练
  8. 信息系统项目管理师考试公式都在这里了
  9. PHP语法像C,PHP编程语法的三个魅力之处
  10. Google Borg论文
  11. C语言每日一练——第118天:百钱百鸡问题
  12. 2021-08-07:与数组中元素的最大异或值。给你一个由非负整数组成的数组 nums 。另有一个查询数组 queries ,其中 queries[i] = [xi, mi] 。第 i 个查询的答案是
  13. iPhone开发入门(一)
  14. 阿里云Centos6数据盘扩容的问题处理
  15. 红警2 技术篇 地图基础ini 教程
  16. typora定制主题分享--绿豆沙背景主题+新night背景主题
  17. nc文件处理学习资料
  18. 2022数据库系统工程师 下午 真题答案
  19. 一篇文章带你了解和学会VCN安卓快速开发
  20. 基于VMware12虚拟机的Hadoop3.1.2伪分布式安装(含各软件百度云下载地址、安装过程中各类问题的解决方案)

热门文章

  1. 一个专为推荐系统定制的BERT!
  2. 每日算法系列【LeetCode 1039】多边形三角剖分的最低得分
  3. NLP学习—22.Transformer的代码实现
  4. python—如何处理文件中的缺失值
  5. Storm 实战:构建大数据实时计算
  6. 《SaaS架构设计》新书SD会议首发签售
  7. ARM嵌入式系统网络驱动中的重要数据结构
  8. 1.13 Linux创建与删除用户
  9. 编译异常例子java_Java中异常发生时代码执行流程
  10. spring 处理带有特殊字符的请求_Spring爸爸又给Spring MVC生了个弟弟叫Spring WebFlux...