点击上方☝SpringForAll社区 轻松关注!及时获取有趣有料的技术文章

本文来源:https://www.cnblogs.com/ITtangtang/p/7602363.html

一、线程安全性

定义:多个线程之间的操作无论采用何种执行时序或交替方式,都要保证不变性条件不被破坏

“共享”:变量可以由多个线程同时访问;

“可变”:变量的值在其生命周期内可以发生改变

如果当多个线程访问同一个可变的状态变量时,没有使用合适的同步,那么程序将会出现错误。有三种方式可以修复该问题:

  • 不在线程之间共享该变量。

  • 将状态变量修改为不可变的变量。

  • 在访问状态变量时使用同步。

竞态条件:

最常见的类型就是“先检查后执行”操作,即通过一个可能失效的观测接口来决定下一步的动作。

二、线程启动

  • 继承Thread类,重写里面的run方法,用start方法启动线程

  • 实现Runnable接口,实现里面的run方法,用new Thread(Runnable target).start()方法来启动

start()和run()

线程的启动并不是简单的调用了run方法,而是由一个线程调度器来分别调用所有线程的run方法,普通的run方法如果没有执行完是不会返回的,也就是会一直执行下去,这样run方法下面的方法就不可能会执行了,可是线程里的run方法却不一样,它只有一定的CPU时间,执行过后就给别的线程了,这样反复的把CPU的时间切来切去,因为切换的速度很快,所以我们就感觉是很多线程在同时运行一样

(01) mythread.run()是在“主线程main”中调用的,该run()方法直接运行在“主线程main”上。

(02) mythread.start()会启动“线程mythread”,“线程mythread”启动之后,会调用run()方法;此时的run()方法是运行在“线程mythread”上。

http://blog.csdn.net/xuxurui007/article/details/7685076

三、线程同步

  • synchronized关键字

内置锁(互斥锁):

在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁。同一时间最多只有一个线程持有该锁,其他线程必须等待或者阻塞,直到该线程释放锁。

如果持有锁的时间过长,将会带来性能问题。

实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。

实例锁对应的就是synchronized关键字。

全局锁 -- 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。

全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。

  • volatile变量

volatile相当于synchronized的弱实现,也就是说volatile实现了类似synchronized的语义,却又没有锁机制。它确保对volatile字段的更新以可预见的方式告知其他的线程。

volatile包含以下语义:

  (1)Java 存储模型不会对valatile指令的操作进行重排序:这个保证对volatile变量的操作时按照指令的出现顺序执行的。

  (2)volatile变量不会被缓存在寄存器中(只有拥有线程可见)或者其他对CPU不可见的地方,每次总是从主存中读取volatile变量的结果。也就是说对于volatile变量的修改,其它线程总是可见的,并且不是使用自己线程栈内部的变量。也就是在happens-before法则中,对一个valatile变量的写操作后,其后的任何读操作理解可见此写操作的结果。

尽管volatile变量的特性不错,但是volatile并不能保证线程安全的,也就是说volatile字段的操作不是原子性的,volatile变量只能保证可见性(一个线程修改后其它线程能够理解看到此变化后的结果),要想保证原子性,目前为止只能加锁!

四、线程协作

线程等待:

等待的原因可能是如下几种情况:

(1)sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。

(2)通过调用join()方法使线程挂起,使自己等待另一个线程的结果,直到另一个线程执行完毕为止。

(3)通过调用wait()方法使线程挂起,直到线程得到了notify()和notifyAll()消息,线程才会进入“可执行”状态。

(4)yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

注意:

  (01) wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,而yield()是让线程由“运行状态”进入到“就绪状态”。

  (02) wait()是会让线程释放它所持有对象的同步锁,而yield()方法不会释放锁。

  (03) sleep()使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁

wait():

  “当前线程”在调用wait()时,必须拥有该对象的同步锁。该线程调用wait()之后,会释放该锁;然后一直等待直到“其它线程”调用对象的同步锁的notify()或notifyAll()方法。然后,该线程继续等待直到它重新获取“该对象的同步锁”,然后就可以接着运行wait()后面的代码。wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上运行的线程!因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)
  wait的东西一定要notify吗?不一定,

notify():

  notify() 执行该方法的线程唤醒在对象的等待池中等待的一个线程,JVM从对象的等待池中随机选择一个线程,把它转到对象的锁池中。使线程由阻塞队列进入就绪状态。

notifyAll():

  当前的线程已经放弃对资源的占有,通知所有的等待线程从wait()方法后的语句开始运行。

这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的锁的线程,并不决定哪个线程能够获取到锁.

  举个简单的例子:

  假如有三个线程Thread1、Thread2和Thread3都在等待对象objectA的锁,此时Thread4拥有对象objectA的锁,当在Thread4中调用objectA.notify()方法之后,Thread1、Thread2和Thread3只有一个能被唤醒。注意,被唤醒不等于立刻就获取了objectA的锁,假若在Thread4中调用objectA.notifyAll()方法,则Thread1、Thread2和Thread3三个线程都会被唤醒,至于哪个线程接下来能够获取到objectA的锁就具体依赖于操作系统的调度了。

  上面尤其要注意一点,一个线程被唤醒不代表立即获取了对象的锁,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。

首先,wait()和notify(),notifyAll()是Object类的方法,sleep()和yield()是Thread类的方法。

  当然由于Thread类继承了Object类,所以Thread也可以调用前面三个方法

  由于每个对象都拥有锁,所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个对象的锁,如果通过线程来操作,就非常复杂了。

(1).常用的wait方法有wait()和wait(long timeout):

void wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。

void wait(long timeout) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。

wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其它synchronized数据可被别的线程使用。

wait()和notify()因为会对对象的“锁标志”进行操作,所以它们必须在synchronized函数或synchronized  block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运 行时会发生IllegalMonitorStateException的异常。

(2).Thread.sleep(long millis),必须带有一个时间参数。

sleep(long)使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;

sleep(long)可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会;

sleep(long)是不会释放锁标志的。

(3).yield()没有参数。

sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出CPU占有权,但让出的时间是不可设定的。

yield()也不会释放锁标志。

实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程。

sleep方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,所以不可能让出较低优先级的线程些时获得CPU占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。所以yield()只能使同优先级的线程有执行的机会。

sleep和yield区别:

  1、sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低线程一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。

  2、当线程执行了sleep(long millis)方法后,将转到阻塞状态,参数millis指定睡眠时间;当线程执行了yield()方法后,将转到就绪状态。

  3、sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常

为什么notify(), wait()等函数定义在Object中,而不是Thread中?

  Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。

wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!那么:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。

  负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。

  总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。

Condition

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,Java阻塞队列实际上是使用了Condition来模拟线程间协作。

  • Condition是个接口,基本的方法就是await()和signal()方法;

  • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()

  • 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

      Conditon中的await()对应Object的wait();

      Condition中的signal()对应Object的notify();

      Condition中的signalAll()对应Object的notifyAll()。

五、线程中断

http://blog.csdn.net/dlite/article/details/4218105

Java的线程调度不提供抢占式中断,而采用协作式的中断。其实,协作式的中断,原理很简单,就是轮询某个表示中断的标记。

通常,我们通过“标记”方式终止处于“运行状态”的线程。其中,包括“中断标记”和“额外添加标记”。

(01) 通过“中断标记”终止线程。

  形式如下:

1@Override2publicvoid run() {3    while (!isInterrupted()) {4        // 执行任务...    }5}

说明:isInterrupted()是判断线程的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时;可以调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()会返回true。此时,就会退出while循环。

注意:interrupt()并不会终止处于“运行状态”的线程!它会将线程的中断标记设为true。

(02) 通过“额外添加标记”。

  形式如下:

 1private volatile boolean flag = true; 2protected void stopTask() { 3    flag = false; 4} 5 6@Override 7public void run() { 8    while (flag) { 9        // 执行任务...    }10}

说明:线程中有一个flag标记,它的默认值是true;并且我们提供stopTask()来设置flag标记。当我们需要终止该线程时,调用该线程的stopTask()方法就可以让线程退出while循环。

注意:将flag定义为volatile类型,是为了保证flag的可见性。即其它线程通过stopTask()修改了flag之后,本线程能看到修改后的flag的值。

通常,我们通过“中断”方式终止处于“阻塞状态”的线程。

当线程由于被调用了sleep(), wait(), join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。将InterruptedException放在适当的为止就能终止线程

1@Override2public void run() {3    try {4        while (true) {5            // 执行任务...        }6    } catch (InterruptedException ie) {  7        // 由于产生InterruptedException异常,退出while(true)循环,线程终止!    }8}

注意:对InterruptedException的捕获务一般放在while(true)循环体的外面,这样,在产生异常时就退出了while(true)循环。

参考资料:

http://www.cnblogs.com/jobs/archive/2010/07/29/1788156.html

梁飞:Java并发编程常识.pptx

● Kafka基本架构及原理

● JVM:基础

● 数据仓库:Mysql大量数据快速导出

● 深入理解Java:注解

● 深入理解Java:类加载机制及反射

● Java 如何设计 API 接口,实现统一格式返回?

● 图文:你女朋友也能看懂的 Kubernetes !

● 打开黑盒:从 MySQL 架构设计出发,看它是如何执行一条 SQL 语句的?

● 记一次神奇的 sql 查询经历,group by 慢查询优化!

● 花了一个月时间梳理了一下公司的微服务核心架构,原来也不是太难.

● 一次900万+数据量的 SQL 查询优化分析【上百倍性能优化】

● 缓存穿透、缓存并发、缓存失效之思路变迁

● 花了一个月时间梳理了一下公司的微服务核心架构,原来也不是太难.

● 一文详解微服务架构

● API规范约定

thread.sleep是让哪个线程休眠_Java多线程:多线程基础知识相关推荐

  1. thread.sleep是让哪个线程休眠_java开发两年,这些线程知识你都不知道,你怎么涨薪?...

    前言 什么是线程:程序中负责执行的哪个东东就叫做线程(执行路线,进程内部的执行序列),或者说是进程的子任务. Java中实现多线程有几种方法 继承Thread类: 实现Runnable接口: 实现Ca ...

  2. thread.sleep是让哪个线程休眠_java多线程必看:java线程的生命周期

    点击蓝字 关注我们 线程是一个动态执行的过程,它也有从创建到死亡的过程.线程的几种状态 在 Thread 类中,有一个枚举内部类: 上面的信息以图片表示如下: 第一张图: 第二张图:把等待.计时等待. ...

  3. java 一个线程运行_Java并发(基础知识)—— 创建、运行以及停止一个线程

    在计算机世界,当人们谈到并发时,它的意思是一系列的任务在计算机中同时执行.如果计算机有多个处理器或者多核处理器,那么这个同时性是真实发生的:如果计算机只有一个核心处理器那么就只是表面现象. 现代所有的 ...

  4. java中线程总结_java中多线程学习笔记总结

    线程的简单学习笔记: 1.进程与线程的概念 进程:从用户角度看进程是应用程序的一个执行过程. 从操作系统核心角度看进程代表的是操作系统分配的内存和CPU时间片等资源的基本单位,是为正在运行的程序提供的 ...

  5. java线程概念_《Java基础知识》Java线程的概念

    按照规划,从本篇开始我们开启『并发』系列内容的总结,从本篇的线程开始,到线程池,到几种并发集合源码的分析,我们一点点来,希望你也有耐心,因为并发这块知识是你职业生涯始终绕不过的坎,任何一个项目都或多或 ...

  6. java编程语言大全_JAVA编程语言的基础知识(一)

    下面介绍的是java编程相关的基础知识: 1.面向对象的特征有哪些方面 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并不打算了解全部问题,而只 ...

  7. java爬虫面试题_Java 网络爬虫基础知识入门解析

    前言 说起网络爬虫,大家想起的估计都是 Python ,诚然爬虫已经是 Python 的代名词之一,相比 Java 来说就要逊色不少.有不少人都不知道 Java 可以做网络爬虫,其实 Java 也能做 ...

  8. auto.js停止所有线程_Java多线程编程基础知识 概念介绍,以及线程状态

    一.进程 进程是操作系统结构的基础:是一次程序的执行:是一个程序及其数据在处理机上顺序执行时所发生的活动.操作系统中,几乎所有运行中的任务对应一条进程(Process).一个程序进入内存运行,即变成一 ...

  9. java1.7线程暂停_Java实现多线程断点下载实例代码(下载过程中可以暂停)

    线程可以理解为下载的通道,一个线程就是一个文件的下载通道,多线程也就是同时开启好几个下载通道.当服务器提供下载服务时,使用下载者是共享带宽的,在优先级相同的情况下,总服务器会对总下载线程进行平均分配. ...

最新文章

  1. DeepCut及DeeperCut:基于Tensorflow的人体姿态估计
  2. using 的三种用法
  3. 一款开源免注册的加密工具,防火防盗防泄密~
  4. jira webhook 事件触发并程序代码调用jenkins接口触发构建操作
  5. Android 曲线图绘制
  6. command not found Operation not permitted
  7. [php入门] 4、HTML基础入门一篇概览
  8. C# 视频监控系列(11):H264播放器——封装API[HikPlayM4.dll]
  9. 【Python】retrying模块使用场景
  10. redhat常用基础命令代码
  11. 服务器网口显示图像,简单的python3web服务器,可以在浏览器中显示图像
  12. redis源码编译和调试
  13. html5游戏燕郊,December | 2019 | 微信html5游戏源码发布站
  14. guava 的重试机制 guava-retrying 使用
  15. font-family常见中文字体对应的英文名称
  16. BS7799, ISO/IEC 17799, ISO/IEC 27001容易混淆
  17. 计算机组成:真正理解单周期处理器的“数据通路”
  18. 如何判断文件的编码方式
  19. web 系统发展历程
  20. win10资源管理器——删除左侧图标(自用)

热门文章

  1. android monkey压力测试,Android-Monkey 压力测试
  2. php.ini开启命名空间,Zend Framework教程之模型Model基本规则和使用方法
  3. collections求和方法_java集合求和最大值最小值示例分享
  4. oem718d 基准站设置_RTK电台、网络模式作业设置流程
  5. Python数据结构常见的八大排序算法(详细整理)
  6. Flink中的容错机制
  7. python修改mac地址_python利用_winreg模块制作MAC地址修改工具
  8. 深入浅出不可思议的中文分词技术
  9. 论文浅尝 - ISWC2020 | KnowlyBERT: 知识图谱结合语言模型补全图谱查询
  10. 科普 | “开放知识”的定义