目录

一、引言

二、synchronize同步

1、synchronize代码块

2、synchronize方法

三、lock同步

1、普通lock+condition

2、lock+condition高级应用

3、lock+读写锁

四、总结


一、引言

先介绍两个概念

线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。

同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。

为什么使用同步?

多线程给我们带来了很大的方便,但是同时也给我们带来了一个致命的问题,当我们对线程共享数据进行非原子操作时,会带来知名的错误。当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。这就是多线程同步提上议程的原因,解决多线程安全问题。

举个例子:假设银行里某一用户账户有1000元,线程A读取到1000,并想取出这1000元,并且在栈中修改成了0但还没有刷新到堆中,线程B也读取到1000,此时账户刷新到银行系统中,则账户的钱变成了0,这个时候也想去除1000,再次刷新到行系统中,账号的钱变成0,这个时候A,B都取出1000元,但是账户只有1000,显然出现了问题。针对上述问题,假设我们添加了同步机制,那么就可以很容易的解决。

tips:java1.5新引入了concurrent包,里面都是关于并发的,里面还有locks和atomic两个包,很值得学习,使用方便。

二、synchronize同步

1、synchronize代码块

这里举了个复杂点的例子,模仿的生产者消费者的模式,每次添加一个数,然后读出一个数,添加后不能再次添加,读出后不能再次读出。synchronize(this){}中this为同步锁,可以是任意对象,这里是用的该类本身,只要每次锁一样就能保证两个线程再synchronize代码块中的执行是同步的,所以每次只能读取或者添加。

wait和notify是线程等待和唤醒线程的意思,每当线程wait后被notify会从等待位置继续执行,使用这个是为了在线程中进行通信,使多个线程按照自己的想法有序执行(当添加数据线程添加后在添加该线程就会wait,当读取数据线程读取数据后就会notify添加线程继续执行)。

创建多线程类

package com.lock;public class Testlock {public static void main(String [] args){data();}/**** synchronize*/static void data(){final DataSynchronize data=new DataSynchronize();new Thread(new Runnable() {public void run() {for(int i=0;i<1000000000;i++){data.write();}}}).start();new Thread(new Runnable() {public void run() {for(int i=0;i<1000000000;i++){data.get();}}}).start();}}

封装的共享数据及方法类

package com.lock;/*** synchronize* 两个线程,一个存数据一个取数据——多个线程全部互斥*/
class DataSynchronize {boolean hasdata;int x;public  void write(){synchronized (this){//有数据if (hasdata==true){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//没数据System.out.println(Thread.currentThread().getName()+"准备添加数据");x=(int)(Math.random()*100000);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"添加数据"+x);//通过上述步骤有了数据,并唤醒取数据hasdata=true;this.notify();}}public  synchronized void get(){synchronized (this){//没有数据if (hasdata==false){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"准备读取数据");//x=(int)Math.random();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"读取数据为"+x);//通过上述步骤没有数据,并唤醒添加数据hasdata=false;this.notify();}}}

2、synchronize方法

这里第一个类没有变化,主要对第二个类进行了改变,把synchronize放在方法上了,说明此方法是线程同步安全的,如在单例模式中getinstance方法中也要使用synchronize关键字。这个比较简单,需要注意的是synchronize加在非静态方法上锁是对象锁,加在静态对象上市类锁。

创建多线程类

package com.lock;public class Testlock {public static void main(String [] args){data();}/**** synchronize*/static void data(){final DataSynchronize data=new DataSynchronize();new Thread(new Runnable() {public void run() {for(int i=0;i<1000000000;i++){data.write();}}}).start();new Thread(new Runnable() {public void run() {for(int i=0;i<1000000000;i++){data.get();}}}).start();}}

封装共享数据及方法类

/*** synchronize* 两个线程,一个存数据一个取数据——多个线程全部互斥*/
class DataSynchronize {boolean hasdata;int x;public synchronized void write(){//有数据if (hasdata==true){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//没数据System.out.println(Thread.currentThread().getName()+"准备添加数据");x=(int)(Math.random()*100000);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"添加数据"+x);//通过上述步骤有了数据,并唤醒取数据hasdata=true;this.notify();}public  synchronized void get(){//没有数据if (hasdata==false){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"准备读取数据");//x=(int)Math.random();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"读取数据为"+x);//通过上述步骤没有数据,并唤醒添加数据hasdata=false;this.notify();}}

三、lock同步

1、普通lock+condition

首先最简单的lock就是把synchronize代码块外皮去掉,在代码块最开始添加lock.lock(),在代码块末尾添加lock.unlock(),所以很简单就不写例子了,lock是在jdk1.5出来的,在lock包里面。

这个方法要达到的效果和上述例子中一样,所以使用了condition,condition主要起条件限制的作用,它的await与signal与上面的wait和notify一个意思,为了使多个线程间通信控制线程执行顺序。

创建多线程类

public class Testlock {public static void main(String [] args){//data();datacondition();//dataconditonOrder();//dataLockReadWrite();}/*** lock+condition*/static void  datacondition(){final DataLockcondition data=new DataLockcondition();new Thread(new Runnable() {public void run() {for(int i=0;i<1000000000;i++){data.write();}}}).start();new Thread(new Runnable() {public void run() {for(int i=0;i<1000000000;i++){data.get();}}}).start();}}

封装共享数据和方法类

/*** lock condition* 两个线程,一个存数据一个取数据——多个线程全部互斥*/
public class DataLockcondition {boolean hasdata;int x;Lock lock=new ReentrantLock();Condition condition=lock.newCondition();public void write(){lock.lock();try{//有数据if (hasdata==true){try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}}//没数据System.out.println(Thread.currentThread().getName()+"准备添加数据");x=(int)(Math.random()*100000);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"添加数据"+x);//通过上述步骤有了数据,并唤醒取数据hasdata=true;condition.signal();}finally {lock.unlock();}}public  void get(){lock.lock();try{//没有数据if (hasdata==false){try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"准备读取数据");//x=(int)Math.random();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"读取数据为"+x);//通过上述步骤没有数据,并唤醒添加数据hasdata=false;condition.signal();}finally {lock.unlock();}}
}

2、lock+condition高级应用

condition既然和synchronize中的wait和notify差不多为什么还使用它呢?是因为condition可以创建多个控制节点,然而synchronize中的代码块只有一个锁可以控制,下面展示下强大的功能。

让主线程、sub1线程、sub2线程中的循环逐条按顺序输出,多么牛逼!!

创建多线程类

public class Testlock {public static void main(String [] args){//data();//datacondition();dataconditonOrder();//dataLockReadWrite();}/*** lock+condition+order*/static void dataconditonOrder(){final DataLockConditonOrder lockConditonOrder=new DataLockConditonOrder();new Thread(new Runnable() {public void run() {for (int i=0;i<10;i++){lockConditonOrder.main(i);}}}).start();new Thread(new Runnable() {public void run() {for (int i=0;i<10;i++){lockConditonOrder.sub1(i);}}}).start();for (int i=0;i<10;i++){lockConditonOrder.sub2(i);}}}

封装共享数据和方法类

/*** 三个线程,按照顺序执行——多个线程互斥,有序*/
public class DataLockConditonOrder {int flag=0;Lock lock=new ReentrantLock();Condition condition1=lock.newCondition();Condition condition2=lock.newCondition();Condition condition3=lock.newCondition();void main(int order){lock.lock();try{//没有轮到1执行if (flag!=0){try {condition1.await();} catch (InterruptedException e) {e.printStackTrace();}}//轮到1执行for(int i=0;i<10;i++){System.out.println("主线程:"+Thread.currentThread().getName()+"第"+order+"次循环输出"+i);}flag=1;condition2.signal();}finally {lock.unlock();}}void  sub1(int order){lock.lock();try{//没有轮到2执行if(flag!=1){try {condition2.await();} catch (InterruptedException e) {e.printStackTrace();}}//轮到2执行for(int i=0;i<20;i++){System.out.println("sub1线程:"+Thread.currentThread().getName()+"第"+order+"次循环输出"+i);}flag=2;condition3.signal();}finally {lock.unlock();}}void  sub2(int order){lock.lock();//没有轮到3执行try{if (flag!=2){try {condition3.await();} catch (InterruptedException e) {e.printStackTrace();}}//轮到3执行for(int i=0;i<30;i++){System.out.println("sub2线程:"+Thread.currentThread().getName()+"第"+order+"次循环输出"+i);}flag=0;condition1.signal();}finally {lock.unlock();}}}

输出结果

主线程:Thread-0第0次循环输出0
主线程:Thread-0第0次循环输出1
主线程:Thread-0第0次循环输出2
主线程:Thread-0第0次循环输出3
主线程:Thread-0第0次循环输出4
主线程:Thread-0第0次循环输出5
主线程:Thread-0第0次循环输出6
主线程:Thread-0第0次循环输出7
主线程:Thread-0第0次循环输出8
主线程:Thread-0第0次循环输出9
sub1线程:Thread-1第0次循环输出0
sub1线程:Thread-1第0次循环输出1
sub1线程:Thread-1第0次循环输出2
sub1线程:Thread-1第0次循环输出3
sub1线程:Thread-1第0次循环输出4
sub1线程:Thread-1第0次循环输出5
sub1线程:Thread-1第0次循环输出6
sub1线程:Thread-1第0次循环输出7
sub1线程:Thread-1第0次循环输出8
sub1线程:Thread-1第0次循环输出9
sub1线程:Thread-1第0次循环输出10
sub1线程:Thread-1第0次循环输出11
sub1线程:Thread-1第0次循环输出12
sub1线程:Thread-1第0次循环输出13
sub1线程:Thread-1第0次循环输出14
sub1线程:Thread-1第0次循环输出15
sub1线程:Thread-1第0次循环输出16
sub1线程:Thread-1第0次循环输出17
sub1线程:Thread-1第0次循环输出18
sub1线程:Thread-1第0次循环输出19
sub2线程:main第0次循环输出0
sub2线程:main第0次循环输出1
sub2线程:main第0次循环输出2
sub2线程:main第0次循环输出3
sub2线程:main第0次循环输出4
sub2线程:main第0次循环输出5
sub2线程:main第0次循环输出6
sub2线程:main第0次循环输出7
sub2线程:main第0次循环输出8
sub2线程:main第0次循环输出9
sub2线程:main第0次循环输出10
sub2线程:main第0次循环输出11
……………………………………………………

3、lock+读写锁

读写锁跟数据库读写锁很像,就是读与读异步,读与写同步,写与写同步。从而达到数据不会出错。

程序目的达到上述目的,为读数据上读锁,写数据上写锁。

创建线程类

public class Testlock {public static void main(String [] args){//data();//datacondition();//dataconditonOrder();dataLockReadWrite();}/*** lockReadwrite*/static void dataLockReadWrite(){final DataLockReadwrite data=new DataLockReadwrite();for (int i=0;i<3;i++) {new Thread(new Runnable() {public void run() {data.write();}}).start();}for (int i=0;i<3;i++){new Thread(new Runnable() {public void run() {data.get();}}).start();}}}

封装共享数据和方法类

/*** 多个线程,读取相同对象——多个线程,分类*/
public class DataLockReadwrite {boolean hasdata;int x;ReentrantReadWriteLock lock=new ReentrantReadWriteLock();//Condition condition=lock.newCondition();public void write(){//有数据
/*            if (hasdata==true){try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}}*///没数据lock.writeLock().lock();System.out.println(Thread.currentThread().getName()+"准备添加++数据");x=(int)(Math.random()*100000);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"添加++数据"+x);//通过上述步骤有了数据,并唤醒取数据lock.writeLock().unlock();}public  void get(){//没有数据
/*            if (hasdata==false){try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}}*/lock.readLock().lock();System.out.println(Thread.currentThread().getName()+"准备读取——数据");//x=(int)Math.random();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"读取数据——为"+x);//通过上述步骤没有数据,并唤醒添加数据lock.readLock().unlock();}
}

输出结果:

添加数据内部都为原子操作,而读取数据可以被打断

Thread-0准备添加++数据
Thread-0添加++数据60461
Thread-2准备添加++数据
Thread-2添加++数据98717
Thread-1准备添加++数据
Thread-1添加++数据18315
Thread-5准备读取——数据
Thread-3准备读取——数据
Thread-4准备读取——数据
Thread-5读取数据——为18315
Thread-3读取数据——为18315
Thread-4读取数据——为18315

四、总结

  • synchronize代码块
  • synchronize方法
  • lock基本操作
  • lock+condition
  • lock+读写锁

Java多线程(三)——多线程实现同步相关推荐

  1. VC++中多线程学习(MFC多线程)三(线程同步包含:原子互锁、关键代码段、互斥器Mutex、Semaphores(信号量)、Event Objects(事件))

    目录 ​​​​​​​​​​​​ 线程同步的必要性: 2.解决同步问题的方法 2.1原子互锁家族函数 2.2Critical Sections(关键代码段.关键区域.临界区域) 2.3 互斥器Mutex ...

  2. Java多线程:多线程同步安全问题的 “三“ 种处理方式 ||多线程 ”死锁“ 的避免 || 单例模式”懒汉式“的线程同步安全问题

    Java多线程:多线程同步安全问题的 "三" 种处理方式 ||多线程 "死锁" 的避免 || 单例模式"懒汉式"的线程同步安全问题 每博一文 ...

  3. java多线程三之线程协作与通信实例

    多线程的难点主要就是多线程通信协作这一块了,前面笔记二中提到了常见的同步方法,这里主要是进行实例学习了,今天总结了一下3个实例: 1.银行存款与提款多线程实现,使用Lock锁和条件Condition. ...

  4. java多线程之线程的同步与锁定(转)

    一.同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 例如:两个线程ThreadA.ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据. publicc ...

  5. JAVA笔记14__多线程共享数据(同步)/ 线程死锁 / 生产者与消费者应用案例 / 线程池...

    /*** 多线程共享数据* 线程同步:多个线程在同一个时间段只能有一个线程执行其指定代码,其他线程要等待此线程完成之后才可以继续执行.* 多线程共享数据的安全问题,使用同步解决.* 线程同步两种方法: ...

  6. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

  7. (四)Java中的多线程之间实现同步+多线程并发同步

    一.什么是线程安全问题 为什么有线程安全问题? 当多个线程同时共享同一个全局变量或静态变量,做写的操作(修改变量值)时,可能会发生数据冲突问题,也就是线程安全问题.但是做读操作时不会发生数据冲突问题. ...

  8. JAVA-多线程 三 {多线程状态}JAVA从基础开始 -- 3

    JAVA-多线程 三 {多线程状态}(JAVA从基础开始 -- 3 线程状态 停止方法_stop (舍弃) 休眠状态_sleep 线程礼让_yield 线程强制执行_ join 线程状态观测_Thre ...

  9. Java中多线程、多线程的实现方式、同步代码块的方式

    多线程 进程 线程 概念 目前的程序是单线程 线程的组成部分 代码实现多线程的方式 第一种方式 第二种方式 第三种方式 -- 线程池 第四种方式:Callable 线程状态 线程同步 临界资源 原子操 ...

  10. Java并发编程进阶——多线程的安全与同步

    多线程的安全与同步 多线程的操作原则 多线程 AVO 原则 A:即 Atomic,原子性操作原则.对基本数据类型变量的读和写是保证原子性的,要么都成功,要么都失败,这些操作不可中断. V:即 vola ...

最新文章

  1. java freemarker 模版_Java模板引擎-FreeMarker
  2. Android_注解+反射代替findViewById()
  3. FileChannel与ByteBuffer的使用示例
  4. c语言单链表_C语言笔试题—单链表逆序
  5. 洛谷 P3374 【模板】树状数组 1
  6. python向端口发出数据_Python写的简单的端口监听,显示端口上收到的数据,TCP的...
  7. LAMP LNMP性能测试
  8. 关于TobjectList的一点疑问
  9. Javascript屏蔽鼠标的右键的两种方法。
  10. 实验楼 linux内核原理与分析,《Linux内核原理与分析》第一周作业 20189210
  11. mysql 分表后排序_MySQL优化分库分表,为什么要分表,分表以后如何进行排序查询,业务如何设计?...
  12. 第六篇 JVM核心机制之JVM运行和类加载全过程(一)
  13. 每日算法系列【LeetCode 239】滑动窗口最大值
  14. CCNA学习资料简介
  15. maven安装配置换阿里源
  16. 刚体运动学公式_1.力的观点 ⑴.匀变速直线运动中常见的公式: 牛顿第二定律: 运动学公式:.... ⑵.圆周运动的主要公式:...
  17. Android 测试工具集01
  18. Virtual Hosts
  19. 阿里云DevOps助理工程师认证题库和笔记(ACA)2021最新
  20. 【四二学堂】标准GPS坐标,转换成百度坐标

热门文章

  1. eclipse jstl包_我的Java Web之路41 - JSTL初步使用
  2. java图书馆库存管理系统_书店图书库存管理系统.doc
  3. 全向轮机器人特性分析
  4. *与**在python中的使用
  5. linux服务器_Linux 服务器为什么被黑?
  6. 转 Mac 使用ab性能测试工具
  7. 前端验证码绘制(canvas)
  8. 【汇编语言与计算机系统结构笔记20】补充内容:可定制处理器指令集
  9. 【李宏毅2020 ML/DL】P16 PyTorch Tutorial | 最后提及了 apex.amp
  10. 【操作系统/OS笔记15】死锁的系统模型,死锁的处理办法,银行家算法与死锁检验算法