一、线程的运行模式

1、线程与进程

为了实现在同一个时间运行多个任务,Java引入了多线程的概念。在Java中可以通通过方便、快捷的方式启动多线程模式。多线程常被应用在符合并发机制的程序中,例如网络程序等。

人体可以同时进行呼吸、血液循环、思考问题等活动,可以边听歌边聊天…这种机制在Java中被称为并发机制,通过并发机制可以实现多个线程并发执行,这样多线程就应运而生了。

以多线程在Windows操作系统中的运行模式为例,Windows操作系统事多任务操作系统,它以进程为单位。每个独立执行的程序都被称为进程,比如只在运行的QQ、微信、谷歌浏览器等,每一个都是一个进程,每个进程都包含多个线程。系统可以分配给每个进程一段使用CPU的时间,然后CPU在这段时间执行某个进程,进程中的每个线程也被分配到一小段执行时间,这样一个进程就可以具有多个线程并发执行的线程。接下来,下一个CPU时间段又执行另外另一个进程。由于CPU转换的较快,可以使每个进程好像是被同时执行一样。

多线程在Windows操作系统中的运行模式如下:

  • 进程:单独运行的程序就是一个独立的进程,进程之间是相互独立存在的。比如在Windows系统中运行的QQ、微信、谷歌浏览器程序等,都是一个一个单独的进程。

  • 线程:进程想要执行任务需要依赖线程,进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。

  • 多线程:比如运行谷歌浏览器,浏览器提供很多功能,可以执行下载任务,可以执行播放歌曲任务,也可以执行浏览网页任务等,这些任务就是一个又一个线程。

2、运行模式

(1)串行、并行、并发

一个进程由多个线程构成,一个线程又是由多个任务构成的。线程在执行任务时需要考虑以什么样的模式来执行,是按照顺序逐一运行,还是允许有时间片段交叉或重合的同时运行。这里需要引入两个概念:线程串行、线程并行

线程有两种运行模式:串行和并行。

  • 串行:所谓串行其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子,我们下载多个文件,在串行中它是按照一定的顺序去进行下载的,也就是说必须等下载完A之后,才能开始下载B,它们在时间上是不可能发生重叠的
  • 并行:是说物理上的 “同时” 被执行。下载多个文件,开启多条线程,多个文件同时进行下载,这里是严格意义上的在同一时刻发生的,并行在时间上是重叠的。
  • 并发:是一种程序设计,能够让多个任务在逻辑上交织执行。并发设计的程序,可以启动n个线程,比如2个,然后交给2个核,这时两个线程就是并行执行的(“同时”);这两个线程也可以被1个核 “交替” 执行。

很多时候,会认为并行就是真的同时执行,而并发就是交替执行,这是一般的理解,但是并发真正含义是指设计的程序允许同时 或 交替执行,是一种程序设计方案

(2)单CPU中并行与并发的关系

单CPU不同核数情况下,并行与并发的关系总结如下:

  • 单CPU中进程只能是并发,多CPU计算机中进程可以并行。
  • 单CPU单核中线程只能并发,单CPU多核中线程可以并行
  • 无论是并发还是并行,使用者来看,看到的是多进程,多线程。

二、线程安全

在单线程程序中,每次只做一件事,后面的事情需要等待前面的事情完成后才能执行。如果需要用多线程程序,就会发生两个线程抢占资源的问题,例如两个人以相反的方向同时过一个独木桥,这时候就会涉及到多线程编程中的资源抢占问题。

在操作系统中,线程是不拥有资源的,进程是拥有资源的。而线程是由进程创建的,一个进程可以创建多个线程,这些线程共享着进程中的资源。所以,当线程一起并发运行时,同时对一个数据进行修改,就可能会造成数据的不一致性。

在实际开发中,使用多线程程序的情况很多,如银行排号系统、火车站售票系统等。

多线程的程序通常会存在一些安全问题,以火车站售票系统为例:有一段判断当前的票数是否大于0的程序,如果程序判断大于0就执行把火车票售给乘客操作。

现在发生了一种常见的情况:如果当前仅剩下少量的票,多个线程同时访问判断程序后得到当前票数大于0的结果,如果此时访问的线程数大于剩余票数,多个线程又根据判断结果(大于0)都执行了售票操作,那么系统票数就会为负数,明显这种情况是不能被允许的。因此,我们在编写多线程程序时,应该考虑到线程安全问题。

而实际上,线程安全问题大多来源于多个线程同时操作单一对象数据的情况。下面我们看个例子:

实例1:多线程并发操作同一数据造成的不一致问题

在一个周末的晚上,哆啦A梦邀请了胖虎,小静,大雄去帮他卖铜锣烧,他们三位分别摆起了地摊,而这时候哆啦A梦拿出了30个铜锣烧希望他们一起卖完这30个!

分析:为了提高卖出铜锣烧的效率,那么就为他们三个人每人开启一个线程,这样就能很快的卖完!

public class SellThread implements Runnable {private int num = 20;@Overridepublic void run() {// TODO Auto-generated method stubwhile(true) {if(num>0){try {Thread.sleep(300);//卖一会儿休息一会儿System.out.println(Thread.currentThread().getName()+"帮多啦A梦卖第"+num--+"个铜锣烧");}catch(InterruptedException e) {e.printStackTrace();}}  }   }
}public class SellTest {public static void main(String[] args) {SellThread t = new SellThread();Thread daxiong = new Thread(t,"大雄");Thread xiaojing = new Thread(t,"小静");Thread panghu = new Thread(t,"胖虎");daxiong.start();xiaojing.start();panghu.start();}
}
public class SellTest {public static void main(String[] args) {SellThread t = new SellThread();Thread daxiong = new Thread(t,"大雄");Thread xiaojing = new Thread(t,"小静");Thread panghu = new Thread(t,"胖虎");daxiong.start();xiaojing.start();panghu.start();}
}
Console:大雄帮多啦A梦卖第20个铜锣烧
胖虎帮多啦A梦卖第18个铜锣烧
小静帮多啦A梦卖第19个铜锣烧
大雄帮多啦A梦卖第17个铜锣烧
小静帮多啦A梦卖第15个铜锣烧
胖虎帮多啦A梦卖第16个铜锣烧
大雄帮多啦A梦卖第14个铜锣烧
小静帮多啦A梦卖第13个铜锣烧
胖虎帮多啦A梦卖第12个铜锣烧
大雄帮多啦A梦卖第11个铜锣烧
小静帮多啦A梦卖第10个铜锣烧
胖虎帮多啦A梦卖第9个铜锣烧
胖虎帮多啦A梦卖第7个铜锣烧
大雄帮多啦A梦卖第8个铜锣烧
小静帮多啦A梦卖第8个铜锣烧
胖虎帮多啦A梦卖第6个铜锣烧
大雄帮多啦A梦卖第5个铜锣烧
小静帮多啦A梦卖第4个铜锣烧
胖虎帮多啦A梦卖第3个铜锣烧
小静帮多啦A梦卖第2个铜锣烧
大雄帮多啦A梦卖第1个铜锣烧
小静帮多啦A梦卖第0个铜锣烧
胖虎帮多啦A梦卖第-1个铜锣烧

从输出结果可以看出,不仅卖出了第0个,还有第负个,还有两个人同时卖出同一个的,这明显是不合逻辑的。

这样就是一个并发操作同一个数据造成的线程不安全问题。问题出在这里:

     while(true) {if(num>0){try {Thread.sleep(300);//卖一会儿休息一会儿System.out.println(Thread.currentThread().getName()+"帮多啦A梦卖第"+num--+"个铜锣烧");}catch(InterruptedException e) {e.printStackTrace();}}   }

当大雄start了线程,开卖的时候,遇到了sleep方法,就在那里睡了300ms,如果这个时候,胖虎把最后一个铜锣烧给卖掉了,然后大雄醒来后,还继续执行接下来的代码! 这就导致了卖出第0个铜锣烧了!

那么我们要如何解决这个问题呢?

三、线程同步机制(锁机制)

当出现线程安全问题时,我们应该如何解决资源抢占问题呢?这时候我们就需要借助线程同步机制(也称为锁机制)来防治资源冲突。

所有解决线程资源冲突问题的方法都是在指定时间只允许一个线程访问共享资源,这时候就需要给共享资源上一道锁,这就好比一个人上洗手间时,进入洗手间就上锁,防止其他人进入;出来时再将锁打来,然后其他人就可以进去了。

Java的线程同步机制,主要有两方面的内容:

1、同步块

同步机制使用synchronized关键字,使用该关键字的代码块称为同步块,也成临界区。

synchronized(Object){//被锁住的代码
}

通常将共享资源的操作放置在synchronized定义的区域内,这样当其他线程获取到这个锁时,就必须等待锁被释放后才能进入该区域。Object为任意一个对象,每个对象都存在一个标识位置,并具有两个值,分别为0和1。

代码块中程序执行逻辑如下:

  • 一个线程运行到同步代码块时首先检查该对象的标识位,如果为0状态,表明此同步块内存在其他线程,这时当前线程处于就绪状态。
  • 直到处于同步代码块中的线程执行玩同步代码块中的代码后,这时该对象的标识位设置为1,当前线程才能开始执行同步代码块中的代码。
  • 执行同步块中的代码的同时,将Object对象的标识位设为0,防止其他线程执行同步代码块中的代码。

实例1中,我们可以使用Java同步代码块来解决线程安全问题:

实例2:使用同步代码块解决线程安全问题

java针对这种情况给我们配了一把锁(同步代码块)!这把锁是来锁一个被共同操作的代码块:

synchronized(new Object){private int num = 20;//被锁住的代码while(true) {if(num>0){try {Thread.sleep(300);//卖一会儿休息一会儿System.out.println(Thread.currentThread().getName()+"帮多啦A梦卖第"+num--+"个铜锣烧");}catch(InterruptedException e) {e.printStackTrace();}}}
}

使用同步代码块后,我们的实例1就优化成了这样:

public class SellThread implements Runnable {private int num = 25;@Overridepublic void run() {// TODO Auto-generated method stubwhile(true) {synchronized (this) { //加锁if(num>0) {try {Thread.sleep(300);//卖一会儿休息一会儿}catch(InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"帮多啦A梦卖第"+num--+"个铜锣烧");}}  }   }
}
Console:大雄帮多啦A梦卖第25个铜锣烧
大雄帮多啦A梦卖第24个铜锣烧
大雄帮多啦A梦卖第23个铜锣烧
大雄帮多啦A梦卖第22个铜锣烧
大雄帮多啦A梦卖第21个铜锣烧
大雄帮多啦A梦卖第20个铜锣烧
大雄帮多啦A梦卖第19个铜锣烧
大雄帮多啦A梦卖第18个铜锣烧
大雄帮多啦A梦卖第17个铜锣烧
大雄帮多啦A梦卖第16个铜锣烧
大雄帮多啦A梦卖第15个铜锣烧
大雄帮多啦A梦卖第14个铜锣烧
大雄帮多啦A梦卖第13个铜锣烧
大雄帮多啦A梦卖第12个铜锣烧
大雄帮多啦A梦卖第11个铜锣烧
大雄帮多啦A梦卖第10个铜锣烧
大雄帮多啦A梦卖第9个铜锣烧
大雄帮多啦A梦卖第8个铜锣烧
大雄帮多啦A梦卖第7个铜锣烧
大雄帮多啦A梦卖第6个铜锣烧
大雄帮多啦A梦卖第5个铜锣烧
大雄帮多啦A梦卖第4个铜锣烧
大雄帮多啦A梦卖第3个铜锣烧
大雄帮多啦A梦卖第2个铜锣烧
大雄帮多啦A梦卖第1个铜锣烧

这样就解决了线程并发的安全问题。不会出现0,-1,重复的情况了。这是因为将共享资源放置在了同步代码块中。

2、同步方法

虽然同步代码块的使用提高了线程的安全性,但是也降低了线程的运行效率!如果出现同步嵌套就容易会出现死锁问题!

除了这样的锁对象之外呢!还有一种是方法锁(同步方法),这种锁是在方法的修饰符后添加synchronized关键字!它的锁对象是this(当前对象!)

同步方法就是被synchronized关键字修饰的方法。

synchronized void func(){}

当某个对象调用了同步方法时,该对象的其他同步方法就必须等待该同步方法执行完毕后才能被执行。必须将每个能访问共享资源的方法都修饰为synchronized,否在会出现错误。

实例3:使用同步方法解决线程安全问题

public class SellThread implements Runnable {private int num = 25;@Overridepublic synchronized void run() {// TODO Auto-generated method stubwhile(true) {if(num>0) {try {Thread.sleep(300);//卖一会儿休息一会儿System.out.println(Thread.currentThread().getName()+"帮多啦A梦卖第"+num--+"个铜锣烧");}catch(InterruptedException e) {e.printStackTrace();}}    }   }
}
public class SellTest {public static void main(String[] args) {SellThread t = new SellThread();Thread daxiong = new Thread(t,"大雄");Thread xiaojing = new Thread(t,"小静");Thread panghu = new Thread(t,"胖虎");daxiong.start();xiaojing.start();panghu.start();}
}
Console:大雄帮多啦A梦卖第25个铜锣烧
大雄帮多啦A梦卖第24个铜锣烧
大雄帮多啦A梦卖第23个铜锣烧
大雄帮多啦A梦卖第22个铜锣烧
大雄帮多啦A梦卖第21个铜锣烧
大雄帮多啦A梦卖第20个铜锣烧
大雄帮多啦A梦卖第19个铜锣烧
大雄帮多啦A梦卖第18个铜锣烧
大雄帮多啦A梦卖第17个铜锣烧
大雄帮多啦A梦卖第16个铜锣烧
大雄帮多啦A梦卖第15个铜锣烧
大雄帮多啦A梦卖第14个铜锣烧
大雄帮多啦A梦卖第13个铜锣烧
大雄帮多啦A梦卖第12个铜锣烧
大雄帮多啦A梦卖第11个铜锣烧
大雄帮多啦A梦卖第10个铜锣烧
大雄帮多啦A梦卖第9个铜锣烧
大雄帮多啦A梦卖第8个铜锣烧
大雄帮多啦A梦卖第7个铜锣烧
大雄帮多啦A梦卖第6个铜锣烧
大雄帮多啦A梦卖第5个铜锣烧
大雄帮多啦A梦卖第4个铜锣烧
大雄帮多啦A梦卖第3个铜锣烧
大雄帮多啦A梦卖第2个铜锣烧
大雄帮多啦A梦卖第1个铜锣烧

将共享资源放置在同步方法总中,运行结果与使用代码块的结果是一致的。

11_05.【Java】线程安全与线程同步相关推荐

  1. Java并发——线程间通信与同步技术

    传统的线程间通信与同步技术为Object上的wait().notify().notifyAll()等方法,Java在显示锁上增加了Condition对象,该对象也可以实现线程间通信与同步.本文会介绍有 ...

  2. Java并发编程之线程同步

    线程安全就是防止某个对象或者值在多个线程中被修改而导致的数据不一致问题,因此我们就需要通过同步机制保证在同一时刻只有一个线程能够访问到该对象或数据,修改数据完毕之后,再将最新数据同步到主存中,使得其他 ...

  3. Java的知识点31——线程同步

    线程同步--并发控制 并发:同一个对象多个线程同时操作 线程不安全: 数据有负数.相同 开辟多线程,每个线程都有自己的工作空间  与 主存 进行交互 /*** 线程不安全: 数据有负数.相同* @au ...

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

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

  5. Java多线程02(线程安全、线程同步、等待唤醒机制)

    Java多线程2(线程安全.线程同步.等待唤醒机制.单例设计模式) 1.线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量 ...

  6. JAVA并发编程3_线程同步之synchronized关键字

    在上一篇博客里讲解了JAVA的线程的内存模型,见:JAVA并发编程2_线程安全&内存模型,接着上一篇提到的问题解决多线程共享资源的情况下的线程安全问题. 不安全线程分析 public clas ...

  7. 【java】Java 中的 Exchanger 线程同步使用方法 线程之间交换数据

    1.概述 转载:https://www.pdai.tech/md/java/thread/java-thread-x-juc-tool-exchanger.html 视频参考:https://www. ...

  8. Java并发编程:线程的同步

    <?xml version="1.0" encoding="utf-8"?> Java并发编程:线程的同步 Java并发编程:线程的同步 Table ...

  9. 【Java 并发编程】【05】线程安全问题与线程同步

    5. 线程安全问题与线程同步 多线程编程是有趣且复杂的事情,它常常容易突然出现"错误情况",这是由于系统的线程调度具有一定的随机性.即使程序在运行过程中偶尔会出现问题,那也是由于我 ...

  10. Java性能-线程和同步-JVM线程优化和线程优先级

    线程和同步-JVM线程优化 优化线程栈大小 每个线程都有一个原生栈,操作系统会在这里存储线程的调用栈信息,如果空间不足可以通过调整线程使用的内存. 64位机器默认原生栈大小1MB 如果将线程栈设置的非 ...

最新文章

  1. python条件语句-python条件、循环语句
  2. CSS3景深-perspective
  3. C# 使用微软的Visual Studio International Pack 类库提取汉字拼音首字母
  4. 修改JAVA代码,需要重启Tomcat的原因
  5. 第七季1:MP4文件格式解析
  6. 计算机基础知识整理大全_知识大全 | 物理选修35quot;波粒二象性quot;
  7. I/O的一些简单操作
  8. C语言和数据结构_1
  9. 使用FFTW3做二维DFT的示例代码
  10. Redis实现MongoDB的getlasterror功能
  11. linux 安装Elasticsearch
  12. C语言strtok()函数详解
  13. HTB打靶(Active Directory 101 Forest)
  14. 【渝粤教育】国家开放大学2018年秋季 2045T金融企业会计 参考试题
  15. 分词工具与方法:jieba、spaCy等
  16. Python解析SWAN气象雷达数据--(解析、生成ASCII、Image、netCDF)
  17. 《约会专家》片尾【约会宝典】总结
  18. cocos2d-x 横板游戏触屏人物和背景移动 方法2
  19. StarRocks Contributor 人数破百,极速统一,你我协力
  20. 【Java学习】从Java历史背景到创建第一个工程——超详细Java入门(多图预警

热门文章

  1. 淘宝、天猫API调用如何按关键词上搜索,item_search_tmall - 按关键字搜索天猫商品
  2. 35岁 计算机 学 什么好,35岁一事无成, 想重新学习, 应该学习哪方面的技能?
  3. 奶奶说标题不能起的太长要不然会有憨憨跟着读之Linux简述及常用命令
  4. 从达特茅斯会议到图灵奖---人工智能学习分享
  5. 彩云之南,难忘的地方
  6. 私有化部署的知识文档系统,不再担心文档数据泄漏
  7. 三相全桥整流电路_什么是三相全波整流电路,三相全波整流电路的工作原理是什么,三相全波整流电路电路图...
  8. 程序员必备的远程控制软件,ToDesk为什么最合适?
  9. 解二元一次方程————拓展欧几里得算法
  10. 修改CodeRunner快捷键