-
关线程安全以及如何实现线程安全
2019-03-18 20:44:16当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程...有时候越是基础的概念,反而越是模糊。有时候先要对概念理解清楚。
学习Java多线程,经常跟线程安全打交道,那么什么是线程安全呢?
一、什么是线程安全
《Java并发编程实践》中对线程安全有这样的定义:
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
《深入理解Java虚拟机》的作者也认可这个观点。本人也认为这是一个恰当的定义,因为线程安全的主体是什么?是方法还是代码块?这里给出的主体是对象,这是非常恰当的,因为Java是纯面向对象的,Java中一切为对象。因此通过对象定义线程安全是恰当的。
但是,这里并不是说其他的方式定义不对(这里绝没有这个意思)。
我们可以看一下其他的定义方式,进行一下对比:
当多个线程访问某个方法时,不管你通过怎样的调用方式或者说这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类时线程安全的。
如果一段代码可以保证多个线程访问的时候正确操作共享数据,那么它是线程安全的
其实这些定义都表达了线程安全是什么。不管如何表述,重要的是自己真正理解了就好。
二、线程安全的实现方法
1、互斥同步(阻塞同步)
同步是指多个线程在并发访问共享数据时,保证共享数据在同一时刻只能被一个线程使用。而互斥是实现同步的手段。
临界区、互斥量、信号量都是主要的互斥实现的方式。
这里可以通过加锁的方式,实现,比如使用synchronized关键字,ReentrantLock等。
2、非阻塞同步
使用互斥同步的最主要问题是进行线程阻塞和唤醒所带来的性能问题,这种同步也成为阻塞同步。
随着硬件指令集的发展,我们有了另一种选择:基于冲突检测的的乐观并发策略。
就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再采取其他的补救措施(最常见的补偿措施就是重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步被称为非阻塞同步。
为了使得操作和冲突检测都具备原子性,就需要硬件的支持。硬件保证一个从语义上看起来需要多次操作的行为只通过一条处理器指令就能完成;常用指令有:
- 测试并设置(Test-ans-Set)
- 获取并增加(Fetch-and-Increment)
- 交换(Swap)
- 比较并交换(Comapre-and-Swap)
- 加载连接/条件存储(Load-Linked/Store-Condition)
3、无同步方案
如果一些代码本身并不涉及共享数据,那么就无需使用任何的同步方式去保证它的正确性,因此有些代码天生就是线程安全的。
可重入代码(Reentrant Code): 这种代码也叫纯代码,可以在代码执行的任何时刻中断它,在控制权返回后,不会出现任何错误。因此所有可重入的代码都是线程安全的。
那么什么样的代码是可重入的呢?
可重入代码有一些共同特征:如不依赖堆上的数据和 公用的系统资源、用到的状态量由参数传入、不可调用非重入的代码等。
可以这样判断代码是否具有可重入性:如果一个方法,它的返回结果是可预测的,只要输入了相同的参数,就都能返回相同的结果,那它就满足可重入性的要求。
例如:Math.max(x,y);获取两个值中的最大者。
线程本地存储(Thread Local Storage): 如果一段代码中所需的数据必须与其他代码共享,那就看看这些共享数据的的代码是否能保证在同一个线程中执行?如果能保证,我们就可以把共享数据的可见范围控制在同一个线程中,这样,无需同步也不会出现数据争用的问题。
例如:生产者和消费者模式,可以将消费者的消费过程尽量控制在同一个线程中执行。
-
我只好到这里问问题了,关于java多线程,已经怎样关线程
2013-12-09 17:04:19我有个类继承了Jpanle,这个类里有自动创建线程的方法, 而我新new这个类的时候原来的线程还存在。 我不断new结果是线程只增不减 问题就出来了,原来线程还在运行,导致显示出的效果非常差劲 原来是多线程并发... -
【第九关】线程基础
2020-05-15 15:18:26第九关:线程基础线程 - 介绍定义:作用:状态说明:线程分类守护线程非守护线程a.主线程(UI线程(MainThread))b.子线程(工作线程)守护线程与非守护线程的区别线程优先级表示设置多线程 - 介绍定义:作用应用...个人学习笔记
参考文章:
https://www.jianshu.com/nb/6977014
https://juejin.im/entry/593109e72f301e005830cd76
https://blog.csdn.net/xiao__gui/article/details/8188833?utm_source=blogxgwz7
https://www.jianshu.com/p/f7d4819b7b24
https://my.oschina.net/nenusoul/blog/794634线程 - 介绍
定义:
一个基本的CPU执行单元&程序执行流的最小单元
> 比进程更小的可独立运行的基本单位,可理解为:轻量级进程
> 组成:线程ID+程序计数器+寄存器集合+堆栈
> 注:线程自己不拥有系统资源,与其他线程共享进程所拥有的全部资源。作用:
减少程序在并发执行时所付出的时空开销,提高操作系统的并发性能
状态说明:
拥有类似于进程的就绪,阻塞,运行 3种基本状态,具体如下图:
线程分类
线程主要分为:守护线程,非守护线程(用户线程)
守护线程
- 定义:守护用户线程的线程,即在程序运行时为其他线程提供一种通用服务
- 常见:如垃圾回收线程
- 设置方式:
//设置该线程为守护线程 thread.setDaemon(true);
非守护线程
主要包括:主线程&子线程
a.主线程(UI线程(MainThread))
- 定义:Android系统在程序启动时会自动启动一条主线程
- 作用:处理四大组件与用户进行交互的事情(如UI,界面交互相关)
- 注:因为用户随时会与界面发生交互,因此主线程任何时候都必须保持很高的相应速度,所以主线程不允许进行耗时操作,否则会出现ANR(应用程序无响应)
b.子线程(工作线程)
- 定义:手动创建的线程
- 作用:耗时的操作(网络请求,I/O操作等)
守护线程与非守护线程的区别
区别:虚拟机是否退出?
- 当所有用户线程结束时,因为没有守护的必要,所以守护线程也会终止,虚拟机也同样退出;反过来,只要任何用户线程还在运行,守护线程就不会终止,虚拟机就不会退出
线程优先级
表示
线程优先级分为10个级别,分别用Thread类常量表示
// 譬如: Thread.MIN_PRIORITY // 优先级1 Thread.MAX_PRIORITY // 优先级10
设置
- 通过方法
setPriority(int grade)
进行优先级设置 - 默认线程优先级是5,即
Thread.NORM_PRIORITY
多线程 - 介绍
定义:
多个线程同时进行,即多个任务同时进行
1.其实,计算机任何特定时候只能执行一个任务;
2.多线程只是一种错觉:只是因为JVM快速调度资源来轮换线程,使得线程不断轮流执行,所以看起来好像在同时执行多个任务而已;作用
Android官方声明:在多线程编程时有两大原则:
- 不要阻塞UI线程(即主线程) :单线程会导致主线程阻塞,然后出现ANR错误:主线程被阻塞超过5s则会出现错误。
- 不要在UI线程之外更新UI组件
所以,我们需要多线程(1个主线程+x个工作线程)来解决上述两个问题:
- 将耗时任务放在工作线程中进行
对应原则:不要阻塞UI线程(即主线程),即当我们有耗时任务,如果在UI线程中执行,那就会阻塞UI主线程,必须要抛到工作(子 )线程中去执行
- 将更新UI组件放在主线程中进行
对应原则:不要在UI线程之外访问UI组件,即更新UI组件时,一定要在UI线程里执行,故需要在工作线程中执行的任务结果返回到UI线程中去更新组件
应用场景
- 将耗时任务从主线程抛到工作线程中进行
- 将更新UI组件任务从工作线程抛到主线程中进行
实现方式
Android多线程实现方式包括:
使用方式
基础使用
- 继承Thread类
- 实现Runnable接口
- Handler
继承Thread类
1. 简介
2. 使用讲解
2.1 使用步骤
2.2 具体使用
// 步骤1:创建线程类 (继承自Thread类) class MyThread extends Thread{ // 步骤2:复写run(),内容 = 定义线程行为 @Override public void run(){ ... // 定义的线程行为 } } // 步骤3:创建线程对象,即 实例化线程类 MyThread mt=new MyThread(“线程名称”); // 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起 / 停止 // 此处采用 start()开启线程 mt.start();
2.3 简便使用:匿名类
很多情况下,开发者会选择一种更加方便的方法去创建线程:匿名类
// 步骤1:采用匿名类,直接 创建 线程类的实例 new Thread("线程名称") { // 步骤2:复写run(),内容 = 定义线程行为 @Override public void run() { // 步骤3:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起 / 停止 }.start();
2.4.两者区别
- 注:调用Thread时,有两种方式:run(),start();run方法只是调用了Thread实例的run()方法而已,它仍然运行在主线程上,而start()方法会开辟一个新的线程,在新的线程上调用run()方法,此时它运行在新的线程上。
3. 与实现“Runnable”接口对比
- 在Java中,继承Thread类和实现Runnable接口是实现多线程最常用的两种方法
实现Runnable接口
1. 简介
2. 使用讲解
2.1 使用步骤
特别注意 :- Java中真正能创建新线程的只有Thread类对象
- 通过实现Runnable的方式,最终还是通过Thread类对象来创建线程
所以对于实现了Runnable接口的类,称为线程辅助类;Thread类才是真正的线程类
2.2 具体使用
// 步骤1:创建线程辅助类,实现Runnable接口 class MyThread implements Runnable{ .... @Override // 步骤2:复写run(),定义线程行为 public void run(){ } } // 步骤3:创建线程辅助对象,即 实例化 线程辅助类 MyThread mt=new MyThread(); // 步骤4:创建线程对象,即 实例化线程类;线程类 = Thread类; // 创建时通过Thread类的构造函数传入线程辅助类对象 // 原因:Runnable接口并没有任何对线程的支持,我们必须创建线程类(Thread类)的实例,从Thread类的一个实例内部运行 Thread td=new Thread(mt); // 步骤5:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起 / 停止 // 当调用start()方法时,线程对象会自动回调线程辅助类对象的run(),从而实现线程操作 td.start(); //MyThread myThread = new MyThread(); //new Thread(myThread).start();
2.3 简便使用:匿名类
很多情况下,开发者会选择一种更加方便的方法去创建线程:匿名类
// 步骤1:通过匿名类 直接 创建线程辅助对象,即 实例化 线程辅助类 Runnable mt = new Runnable() { // 步骤2:复写run(),定义线程行为 @Override public void run() { } }; // 步骤3:创建线程对象,即 实例化线程类;线程类 = Thread类; Thread mt1 = new Thread(mt, "窗口1"); // 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起 / 停止 mt1.start();
2.4 两种区别
Handler
1. 简介
2. 使用讲解
请查看文章:Android Handler详解
复合使用
Android多线程实现的复合使用包括:
- AsyncTask
- HandlerThread
- IntentService
称为“复用”的主要原因是:这3中方式的本质原理都是Android多线程基础实现的组合实现,下面,我将详细讲解。
AsyncTask
-
简介
-
具体使用 & 实例讲解:Android 多线程:手把手教你使用AsyncTask
-
工作原理 & 源码分析:Android 多线程:AsyncTask的原理 及其源码分析
HandlerThread
-
简介
-
具体使用 & 实例讲解:Android多线程:手把手教你使用HandlerThread
-
工作原理 & 源码分析:Android多线程:这是一份详细的HandlerThread源码分析攻略
IntentService
-
简介
-
具体使用 & 实例讲解:Android多线程:IntentService使用教程(含实例讲解)
-
工作原理 & 源码分析:Android多线程:这是一份全面 & 详细的IntentService源码分析指南
高级使用
Android 多线程的高级使用主要是线程池(ThreadPool)
ThreadPool
- 简介
优点:
-
重用线程池中的线程,避免频繁地创建和销毁线程带来地性能消耗
-
有效控制线程的最大并发数量,防止线程过大导致抢占资源造成系统阻塞
-
可以对线程进行一定的管理
-
具体使用&工作原理:Android多线程:线程池ThreadPool 全面解析
补充:
- corePoolSize:线程池中核心线程的数量,默认情况下,即使核心线程没有任务在执行它也存在的,我们固定一定数量的核心线程且它一直存活这样就避免了一般情况下CPU创建和销毁线程带来的开销。我们如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程就会有超时策略,这个时间由keepAliveTime来设定,即keepAliveTime时间内如果核心线程没有回应则该线程就会被终止。allowCoreThreadTimeOut默认为false,核心线程没有超时时间。
- maximumPoolSize:线程池中的最大线程数,当任务数量超过最大线程数时其他任务可能就会被阻塞。最大线程数 = 核心线程+非核心线程。非核心线程只有当核心线程不够用且线程池有空余时才会被创建,执行完任务后非核心线程会被销毁。
- keepAliveTime:在终止之前等待新任务的最长时间,当执行时间超过这个时间时,非核心线程就会被回收。当allowCoreThreadTimeOut设置为true时,此属性也作用在核心线程上。
- unit:枚举时间单位,TimeUnit。
- workQueue:线程池中的任务队列,我们提交给线程池的runnable会被存储在这个对象上。
线程池分配规则:
- 当线程池中的核心线程数量未达到最大线程数时,启动一个核心线程去执行任务;
- 如果线程池中的核心线程数量达到最大线程数时,那么任务会被插入到任务队列中排队等待执行
- 如果在上一个步骤中任务队列已满但是线程池中线程数量未达到限定线程总数,那么启动一个非核心线程来处理任务
- 如果上一步骤中线程数量达到了限定线程总量,那么线程池则拒绝执行该任务,且ThreadPoolExecutor会调用RejectedtionHandler的rejectedExecution方法来通知调用者。
一般用法
- shutDown():关闭线程池,需要执行完已提交的任务
- shutDownNow():关闭线程池,并尝试结束已提交的任务
- allowCoreThreadTimeOut(boolen):允许核心线程闲置超时回收
- execute():提交任务无返回值
- submit():提交任务有返回值
对比
线程调度
调度方式
- 当系统存在大量线程时,系统会通过时间片轮转 的方式调度线程,因此线程不可能做到绝对的并发
- 处于就绪状态(Runnable)的线程都会进入到线程队列(MessageQueue)中等待CPU资源
同一时刻在线程队列中可能有很多个
- 在采用时间片的系统中,每个线程都有机会获得CPU的资源以便进行自身的线程操作;当线程使用CPU资源的时间到后,即使当前线程没有完成自己的全部操作,JVM也会中断当前线程的执行,把CPU资源的使用权切换给下一个队列中等待的线程。
被中断的线程将等待CPU资源的下一次轮回,然后从中断处继续执行
调度优先级
Java虚拟机(JVM)中的线程调度器负责管理线程,并根据以下规则进行调度:
- 根据线程优先级(高 - 低),将CPU资源分配给各线程
- 具备相同优先级的线程以轮流的方式获取CPU资源
示例:存在A,B,C,D四个线程,其中:A和B的优先级高于C和D(A,B同级,C,D同级),那么JVM将先以轮流的方式调度A,B,直到A,B线程死亡,再以轮流的方式调度C,D
线程同步
- 定义:当线程A使用同步方法A时,其他线程必须等到线程A使用完同步方法A后才能使用。为什么呢,因为该关键字的作用是保证同一时刻最多只有1个线程执行被Synchronized修饰的方法/代码。
- 同步方法用关键字Synchronized进行修饰
public synchronized void Sb_Android(){ }
- 关于关键字Synchronized的介绍请看文章:Java:手把手教你全面学习神秘的Synchronized关键字
线程联合
- 定义:线程A在占有CPU资源期间,通过调用join()方法中断自身线程执行,然后运行联合它的线程B,直到线程B执行完毕后线程A再重新排队等待CPU资源,这个过程称为线程A联合线程B
- 线程A联合线程B,即在线程A的执行操作里定义:
B.join();
进程 - 介绍
定义
是进程实体的运行过程&系统进行资源分配和调度的一个独立单位
作用
使多个
程序
可并发执行,以提高系统的资源利用率和吞吐量进程状态说明
注意 :就绪,阻塞状态的区别
就绪状态:进程缺少的资源 = 处理机,只要获得处理机资源立即执行
等待状态:指进程缺少其他资源(除了处理机)/等待某一事件状态转换
进程与线程的区别
单/多进程,单/多线程的区别
假设:进程 = 桌子,单线程 = 1个人吃饭
- 单进程、单线程:一个人在一个桌子上吃饭
- 单进程、多线程:多个人在同一个桌子上一起吃饭
- 多进程、单线程:多个人每个人在自己的桌子上吃饭
其他
线程同步:Synchronized关键字
具体请看文章:Java:手把手教你全面学习神秘的Synchronized关键字
Java线程同步:synchronized锁住的是代码还是对象线程变量:ThreadLocal
-
关与线程
2005-04-22 09:30:00线程:(好东西)为什么要使用线程:1、 提高系统的吞吐率或使用效率。(I/0等待)2、 提供灵敏的用户操作。举例: 第一部分 原理定义:程序内部顺序执行的指令序列。线程本身不拥有资源(没有自己的资源),如内存空间...线程:(好东西)
为什么要使用线程:
1、 提高系统的吞吐率或使用效率。(I/0等待)
2、 提供灵敏的用户操作。
举例:
第一部分 原理
定义:
程序内部顺序执行的指令序列。线程本身不拥有资源(没有自己的资源),如内存空间。但是它可以使用进程的资源。
线程与进程的区别:
1、 从资源分配的角度
进程是资源分配的基本单位,而线程本身没有资源。
2、 从程序调度的角度
进程可以包含多个线程,是程序调度的基本单位。
3、 从系统的开销角度
进程的创建过程的系统开销大与线程创建过程的时间开销,因为进程创建需要为他分配资源,而此过程的系统消开销是很大的,而线程共享进程的资源,无需要进行分配。
4、 从并发性的角度
进程与线程在执行过程中都可以并发的进行,但并发的粒度不同。进程的并发表现为应用级的并发,如:如放歌与编写程序。而线程的并发表现在应用内部的功能性并发,如:电影中的图像与声音。
举例说明:
1、 进程与线程
2、 很好的例用线程,必须明确原理
进程的状态:
进程运行的必要条件:
1、 拥有资源
2、 拥有CPU时间
三态:
运行状态:当线程在处理机上运行的状态程为运行状态(拥有资源和CPU)
就绪状态:当线程拥有资源但没有CPU时间时,等CPU时间的状态。
阻塞状态:当线程等待资源的时的状态。
五态:
新建:刚刚创建还没有提交的状态。
终止:结束运行。
第二部分 Java中的线程。
线程的创建:
两种创建方式:
1、 继承Thread类。
声明一个 Thread 类的子类,并覆盖 run() 方法。
class mythread extends Thread {
public void run( ) {/* 覆盖该方法*/ }
}
2、 实现Runnable接口。
声明一个实现 Runnable 接口的类,并实现 run() 方法。
class mythread implements Runnable{
public void run( ) {/* 实现该方法*/ }
}
Ex1:
public class CreateWinthThread extends Thread {
private int countDown = 5;
private static int threadCount = 0;
public SimpleThread() {
super("" + ++threadCount); // Store the thread name
start();
}
public String toString() {
return "#" + getName() + ": " + countDown;
}
public void run() {
while(true) {
System.out.println(this);
if(--countDown == 0) return;
}
}
public static void main(String[] args) {
for(int i = 0; i < 5; i++)
new CreateWinthThread ();
}
}
线程的主要方法:
Run():在此方法中编写线程所要完成的功能。
Start():将线程从新建装态改为就绪状态。
线程就绪以后实际的执行是由JVM和OS负责。
l 执行的时间是无法确定的(多线程中)
l 执行的顺序是无法确定的(多线程中)
l 执行的结果是无法确定的(多线程中)
继承Thread常用的几种构造函数:
l Thread(),用缺省名称创建一个Thread对象
l Thread(String name),用指定的name参数的名称创建一个Thread对象
l Thread(Runnable target) 用实现了Runnable接口的类创建一个Thread对像
l Thread(Runnable target, String name) 用实现了Runnable接口的类创建一个Thread对像,线程为name参数指定的名称。
Ex2:
public class CreateWithRunnable implements Runnable {
private int countDown = 100;
public String toString() {
return "#" + Thread.currentThread().getName() +
": " + countDown;
}
public void run() {
while(true) {
System.out.println(this);
if(--countDown == 0) return;
}
}
public static void main(String[] args) {
for(int i = 1; i <= 5; i++)
new Thread(new CreateWithRunnable(), "" + i).start();
}
}
currentThread():得到当前线程
getName():得到线程的名称
Runnable接口中只有一个Run方法,没有start()无法将线程从新建态转为就绪态,所以无法运行线程。必须把Runnable转为Thread类。才可以运行。
创建反应敏捷的用户界面
public class FriendlyUI {
private static int i = 0;
public static void main(String[] args) {
new StopRun().start();
Compute compute = new Compute();
compute.setI(i);
compute.start();
}
}
class StopRun extends Thread {
private String flag = "";
public void setFlag() {
try {
InputStreamReader reader = new InputStreamReader(System.in);
BufferedReader input = new BufferedReader(reader);
System.out.println("如果要终止程序请输入 exit");
this.flag = input.readLine();
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
while (!flag.equals("exit")) {
this.setFlag();
}
System.exit(0);
}
}
class Compute extends Thread {
private int i = 0;
public void setI(int i) {
this.i = i;
}
public int getI() {
return i;
}
public void run() {
while (true) {
i++;
}
}
}
sleep ():让它停一段以毫秒计的时间
Yielding():把线程的控制权转交其它线程
Join():待线程处理完之后才转交控制权
守护线程(精灵线程、监控线程、服务线程)
所谓"守护线程(daemon thread)"是指,只要程序还在运行,它就应该在后台提供某种公共服务的线程,但是守护线程不属于程序的核心部分。因此,当所有非守护线程都运行结束的时候,程序也结束了。相反,只要还有非守护线程在运行,程序就不能结束。比如,运行main( )的线程就属于非守护线程
Ex3(sleep,yield,join)
public class YieldingThread extends Thread {
private static Test monitor = new Test();
private int countDown = 100;
private static int threadCount = 0;
public YieldingThread() {
super("" + ++threadCount);
start();
}
public String toString() {
return "#" + getName() + ": " + countDown;
}
public void run() {
while(true) {
System.out.println(this);
if(--countDown == 0) return;
//yield();
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
for(int i = 0; i < 5; i++)
try {
new YieldingThread().join();
} catch (InterruptedException e) {
// TODO 自动生成 catch 块
e.printStackTrace();
}
//new YieldingThread();
}
}
友好用户界面的例字
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class FriendlyUI {
private static int i = 0;
public static void main(String[] args) {
new StopRun().start();
Compute compute = new Compute();
compute.setI(i);
compute.start();
}
}
class StopRun extends Thread {
private String flag = "";
public void setFlag() {
try {
InputStreamReader reader = new InputStreamReader(System.in);
BufferedReader input = new BufferedReader(reader);
System.out.println("如果要终止程序请输入 exit");
this.flag = input.readLine();
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
while (!flag.equals("exit")) {
this.setFlag();
}
System.exit(0);
}
}
class Compute extends Thread {
private int i = 0;
public void setI(int i) {
this.i = i;
}
public int getI() {
return i;
}
public void run() {
while (true) {
i++;
}
}
}
线程之间的优先级:
1-10
MAX_PRIORITY=10
通过 setPriority()进行设置
虽然JDK提供了10级优先级,但是却不能很好地映射到很多操作系统上。比方说,Windows 2000平台上有7个等级还没固定下来,因此映射是不确定的(虽然Sun的Solaris有231个等级)。要想保持可移植性,唯一的办法就是,在调整优先级的时候,盯住MIN_PRIORITY, NORM_PRIORITY, 和MIN_PRORITY
线程之间的协作
l 线程互斥使用资源
l 线程同步
线程互斥使用资源:
临界区:一段共公的内存空间或设备,为两个或两个以上的线程所使用。
各线程共同使用临界区的时候可能会产生冲突。
public class CreateMutexThread extends Thread {
static VarClass varClass = new VarClass();
/*
run方法中的内容为线程的中要执行的内容
*/
public void run(){
while(true){
varClass.setI();
int number=0;
number=varClass.getI();
if((number%2)!=0){
System.out.println(getName()+" "+ varClass.getI());
System.exit(0);
}
}
}
public static void main(String[] args) {
for(int i=0;i<10;i++){
new CreateMutexThread().start();
}
}
}
class VarClass{
int i=0;
public void setI(){
i++;
i++;
}
public int getI(){
return i;
}
}
解决办法:
代码的前后设一条加锁和解锁的语句,这样同一时刻只有一个线程能够执行这段代码。
每个对象都有一个锁(也称监控器monitor),它是对象生来就有的东西(因此你不必为此写任何代码)。当你调用synchronized方法时,这个对象就被锁住了。在方法返回并且解锁之前,谁也不能调用同一个对象的其它synchronized方法。就说上面那两个方法,如果你调用了f( ),那么在f( )返回并且解锁之前,你是不能调用同一个对象的g( )的。因此对任何一个特定的对象,所有的synchronized方法都会共享一个锁,而这个锁能防止两个或两个以上线程同时读写一块共用内存
public class CreateMutexThread extends Thread {
static VarClass varClass = new VarClass();
/*
run方法中的内容为线程的中要执行的内容
*/
public void run(){
while(true){
varClass.setI();
int number=0;
number=varClass.getI();
if((number%2)!=0){
System.out.println(getName()+" "+ varClass.getI());
System.exit(0);
}
}
}
public static void main(String[] args) {
for(int i=0;i<10;i++){
new CreateMutexThread().start();
}
}
}
class VarClass{
int i=0;
synchronized public void setI(){
i++;
i++;
}
Synchronized public int getI(){
return i;
}
}
同步:
一个线程的结果是另一个线程运行的条件时用到。
class Order {
private static int i = 0;
private int count = i++;
public Order() {
if(count == 10) {
System.out.println("Out of food, closing");
System.exit(0);
}
}
public String toString() { return "Order " + count; }
}
class WaitPerson extends Thread {
private Restaurant restaurant;
public WaitPerson(Restaurant r) {
restaurant = r;
start();
}
public void run() {
while(true) {
while(restaurant.order == null)
synchronized(this) {
try {
wait();
} catch(InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(
"Waitperson got " + restaurant.order);
restaurant.order = null;
}
}
}
class Chef extends Thread {
private Restaurant restaurant;
private WaitPerson waitPerson;
public Chef(Restaurant r, WaitPerson w) {
restaurant = r;
waitPerson = w;
start();
}
public void run() {
while(true) {
if(restaurant.order == null) {
restaurant.order = new Order();
System.out.print("Order up! ");
synchronized(waitPerson) {
waitPerson.notify();
}
}
try {
sleep(100);
} catch(InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Restaurant {
Order order; // Package access
public static void main(String[] args) {
Restaurant restaurant = new Restaurant();
WaitPerson waitPerson = new WaitPerson(restaurant);
Chef chef = new Chef(restaurant, waitPerson);
}
}
同步例2:
public class TestThread {
static ShareDate shareDate=new ShareDate();
public static void main(String[] args) {
SetValue setValue=new SetValue(shareDate);
setValue.start();
new GetValue(shareDate,setValue).start();
}
}
class ShareDate{
private String[] strValue=new String[10];
private int index=0;
synchronized public String getStrValue(){
index--;
return strValue[index+1];
}
synchronized public void setStrValue(String value){
index++;
this.strValue[index-1]=value;
}
synchronized public int getIndex(){
return index;
}
}
class GetValue extends Thread{
private ShareDate shareDate;
private SetValue setValue;
private int index;
public GetValue(ShareDate shareDate,SetValue setValue){
this.shareDate=shareDate;
this.setValue=setValue;
}
public void run(){
while(true){
index=shareDate.getIndex();
System.out.println("GetValue"+index);
if (index==0){
synchronized(setValue){
setValue.notify();
}
try {
sleep(100);
} catch (InterruptedException e) {
// TODO 自动生成 catch 块
e.printStackTrace();
}
yield();
}else if (index>=0 && index<=9){
System.out.println(shareDate.getStrValue());
}
}
}
}
class SetValue extends Thread{
private ShareDate shareDate;
int index;
public SetValue(ShareDate shareDate){
this.shareDate=shareDate;
}
public void run(){
int i=0;
while(true){
this.index=shareDate.getIndex();
System.out.println("SetValue"+index);
if (index>=9){
synchronized (this){
try {
this.wait();
} catch (InterruptedException e) {
// TODO 自动生成 catch 块
e.printStackTrace();
}
}
}else{
shareDate.setStrValue(""+i);
i++;
}
}
}
}
三部分:总结
1、 线程的创建(两种创建方式)
2、 线程的属性与方法
3、 线程的状态
线程的状态
线程的状态可归纳为以下四种:
- New: 线程对象已经创建完毕,但尚未启动(start),因此还不能运行。
- Runnable: 处在这种状态下的线程,只要分时机制分配给它CPU周期,它就能运行。也就是说,具体到某个时点,它可能正在运行,也可能没有运行,但是轮到它运行的时候,谁都不能阻止它;它没有dead,也没有被阻塞。
- Dead: 要想中止线程,正常的做法是退出run( )。在Java 2以前,你也可以调用stop( ),不过现在不建议用这个办法了,因为它很可能会造成程序运行状态的不稳定。此外还有一个destroy( )(不过它还没有实现,或许将来也不会了,也就是说已经被放弃了)。后面我们会讲怎样用其它办法来实现stop( )的功能。
- Blocked: 就线程本身而言,它是可以运行的,但是有什么别的原因在阻止它运行。线程调度机制会直接跳过blocked的线程,根本不给它分配CPU的时间。除非它重新进入runnable状态,否则什么都干不了。
进入阻塞状态
如果线程被阻塞了,那肯定是出了什么问题。问题可能有以下几种:
- 你用sleep(milliseconds)方法叫线程休眠。在此期间,线程是不能运行的。
- 你用wait( )方法把线程挂了起来。除非收到notify( )或notifyAll( )消息,否则线程无法重新进入runnable状态。这部分内容会在后面讲。
- 线程在等I/O结束。
- 线程要调用另一个对象的synchronized方法,但是还没有得到对象的锁。
或许你还在旧代码里看到过suspend( )和resume( ),不过Java 2已经放弃了这两个方法(因为很容易造成死锁),所以这里就不作介绍了。
课后习题:
l 了解守护线程
l 线程共享使用资源
l 线程互斥使用资源
l 线程同步
-
Java多线程大闯关--线程的通信
2019-11-26 21:34:05这个系列的博客主要是对Java高级编程中多线程相关的知识点做一个梳理,内容主要包括程序、进程、线程、并行、...Java多线程大闯关–走进多线程. Java多线程大闯关...这个系列的博客主要是对Java高级编程中多线程相关的知识点做一个梳理,内容主要包括程序、进程、线程、并行、并发等相关概念的理解,创建多线程的几种方式,Thread类,线程的同步机制,线程通信等几个大的模块。该系列博客的文章如下,推荐按顺序阅读:
- Java多线程大闯关–走进多线程.
- Java多线程大闯关–线程的调度、优先级、生命周期.
- Java多线程大闯关–线程的同步机制.
- Java多线程大闯关–线程通信.
- Java多线程大闯关–volatile.
线程通信的几个关键方法
先写一个线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印。
package com.learnjiawa.mutithread; /** * 线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印 * 面试题:sleep() 和 wait()的异同? * 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。 * 2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait() * 2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中 * 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁, * wait()会释放锁。 */ class Number implements Runnable{ private int number = 1; private Object obj = new Object(); @Override public void run() { while(true){ synchronized (obj) { obj.notify(); if(number <= 100){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + number); number++; try { //使得调用如下wait()方法的线程进入阻塞状态 obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ break; } } } } } public class CommunicationTest { public static void main(String[] args) { Number number = new Number(); Thread t1 = new Thread(number); Thread t2 = new Thread(number); t1.setName("线程1"); t2.setName("线程2"); t1.start(); t2.start(); } }
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
- notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
需要说明的是:
- wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
- wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常
- wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。
生产者/消费者问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
这里可能出现两个问题:
- 生产者比消费者快时,消费者会漏掉一些数据没有取到。
- 消费者比生产者快时,消费者会取相同的数据。
package com.learnjiawa.mutithread; /** * 线程通信的应用:经典例题:生产者/消费者问题 * * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品, * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员 * 会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品 * 了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。 * * 分析: * 1. 是否是多线程问题?是,生产者线程,消费者线程 * 2. 是否有共享数据?是,店员(或产品) * 3. 如何解决线程的安全问题?同步机制,有三种方法 * 4. 是否涉及线程的通信?是 */ class Clerk{ private int productCount = 0; //生产产品 public synchronized void produceProduct() { if(productCount < 20){ productCount++; System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品"); notify(); }else{ //等待 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } //消费产品 public synchronized void consumeProduct() { if(productCount > 0){ System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品"); productCount--; notify(); }else{ //等待 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Producer extends Thread{//生产者 private Clerk clerk; public Producer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println(getName() + ":开始生产产品....."); while(true){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } clerk.produceProduct(); } } } class Consumer extends Thread{//消费者 private Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println(getName() + ":开始消费产品....."); while(true){ try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } clerk.consumeProduct(); } } } public class ProductTest { public static void main(String[] args) { Clerk clerk = new Clerk(); Producer p1 = new Producer(clerk); p1.setName("生产者1"); Consumer c1 = new Consumer(clerk); c1.setName("消费者1"); Consumer c2 = new Consumer(clerk); c2.setName("消费者2"); p1.start(); c1.start(); c2.start(); } }
参考文献
[1]Bruce Eckel.Java编程思想(第4版)[M].机械工业出版社,2008:650-757.
更多
对我的文章感兴趣,持续更新中…
- Github.
- 个人博客
- 网站开发
- 简书
- Email: zouhuayu96@qq.com
-
Java多线程大闯关--线程的同步机制
2019-11-26 21:29:13这个系列的博客主要是对Java高级编程中多线程相关的知识点做一个梳理,内容主要包括程序、进程、线程、并行、...Java多线程大闯关–走进多线程. Java多线程大闯关... -
Java多线程大闯关--走进多线程
2019-11-26 13:17:21这个系列的博客主要是对Java高级编程中多线程相关的知识点做一个梳理,内容主要包括程序、进程、线程、并行、...Java多线程大闯关–走进多线程. Java多线程大闯关... -
Java多线程大闯关--线程的调度、优先级、生命周期
2019-11-26 13:18:28这个系列的博客主要是对Java高级编程中多线程相关的知识点做一个梳理,内容主要包括程序、进程、线程、并行、...Java多线程大闯关–走进多线程. Java多线程大闯关... -
关服提示线程相关错误
2020-12-09 09:30:20似乎是插件线程管理问题 <code>File "D:\Project\Python\MCSManager_8.6.11_Win_x86\server\server_core\DevServer\utils\plugin_thread.py", line 33 in run</code></p>该提问来源于开源项目:Fallen... -
第3关:使用线程锁(Lock)实现线程同步
2020-12-01 16:57:54本关任务:使用Lock,实现对于某一块代码的互斥访问。 package lam; import java.util.concurrent.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ... -
Java多线程大闯关--volatile关键字
2019-11-27 12:25:10这个系列的博客主要是对Java高级编程中多线程相关的知识点做一个梳理,内容主要包括程序、进程、线程、并行、...Java多线程大闯关–走进多线程. Java多线程大闯关... -
关与线程中全局变量调用问题
2013-06-17 09:54:08全局变量一定要定义在头文件的公共区,如果生明在C文件的区域,线程不可调用。 -
线程
2019-07-30 18:24:011、多线程 如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程” 多个线程交替占用CPU资源,而非真正的并行执行 2、多线程好处 ...必须最后完成执行,因为它执行各种关... -
shutdownNow关不掉线程
2015-12-07 15:01:48否则像下面的写法,catch会吞掉InterruptedException,到时判断检测不出来以至于shutdownNow关不掉线程 shutdownNow的原理是为每个线程调用 interruput方法,然后有run方法里的判断语句来检查Thread.interrupted... -
SSH 控制 session 开和关 以及 线程thread的管理
2008-12-17 14:40:20问个关于session 和线程thread 的问题: 1、用hibernate 可以控制session 何时开、何时关闭以及事务的开和关, 但是使用SSH 之后,用spring 的 HibernateDaoSupport 和事务 TransactionProxyFactoryBean 代替了... -
线程的基本概念、线程的基本状态以及状态之间的关 系
2017-12-03 16:50:00一个程序中可以有多条执行线索同时执行,一个线程就是程序中的一条执行线索,每 个线程上都关联有要执行的代码,即可以有多段程序代码同时运行, 每个程序至少 都有一个线程,即main方法执行的那个线程。如果只是一... -
C# 定时关窗 & 跨线程操作窗口
2018-10-19 16:11:30跨线程关闭其他窗口 会报异常: 解决方法: 在你要操作的窗口的类的构造函数中指明允许跨线程操作。 public partial class AlarmInfoWindow : Form { public AlarmInfoWindow() { Initialize... -
1. Java多线程的想关理论
2018-12-27 15:53:351. Java多线程的想关理论 1.1 并行和并发 引子:前面讲到程序的运行流程的时候,说程序在没有流程控制前提下,代码都是从上而下逐行执行的。 需求:现在需要设计一个程序想要完成边运行游戏,边播放歌的功能,怎么... -
java 多线程 线程安全及非线程安全的集合对象
2018-09-05 10:04:42一、概念: 线程安全:就是当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程... 对于线程不安全的问题,一般会使用synchronized关... -
QT中关线程的问题
2015-08-11 14:23:50一、前段时间写了个多线程的代码,当关线程的时候遇到当发送了关线程后,线程不能及时关掉,然后,影响后面的其他的线程的通信。 之前的办法如下: void MyThread::stop() { mutex.lock; t_stop = ... -
中关村黑马程序员训练营 ------- 多线程间通讯及多线程等待唤醒机制
2015-03-27 20:17:36中关村黑马程序员训练营 ——————- 多线程间通讯及多线程等待唤醒机制.——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-多线程间通讯: 其实就是多个线程在操作同一个资源, 但是操作的动作... -
线程的起与关
2007-12-07 10:39:00CWinThread* m_pWinThread; m_pWinThread=AfxBeginThread(MyThreadHandler, this);...外部可以这样去终止线程: TerminateThread(m_pWinThread->m_hThread,GetExitCodeThread(m_pWinThread,0)); -
用关中断和互斥量来保护多线程共享的全局变量
2016-08-08 14:58:24第60节:用关中断和互斥量来保护多线程共享的全局变量 2016-03-15 10:17:29 来源:eefocus 关键字: 关中断 互斥量 多线程共享 全局变量 开场白: 在前面一些章节中,我提到为了防止中断函数把某些共享数据... -
关一线程的挂起与VCL数据同步定义错误!
2013-07-11 16:41:25利用DELPHI的线程对象定义了二个线程,但里面几乎没任何操作,竞然挂起不了,提示“拒绝访问(5)”的错误;再就是在create让线程中的控件与主进程是控件同步,结果执行程序也报错,返内存溢出的错误。相关代码如下... -
多线程
2019-10-02 16:12:47多线程允许程序员编写最大的程序度利用CPU的高效率;开发中,使用线程分为四个步骤:(1)定义一个线程,同时指明这个线程所需执行的代码,即期望完成的功能。...通常它必须最后完成执行,因为它执行各种关... -
Java线程
2019-08-08 10:57:28线程:一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 多线程:多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销...1.新建状态:使用 new 关... -
命令查看多线程所有线程ID
2020-08-25 22:25:46方法一:PS 在ps命令中,“-T”选项可以开启线程查看。下面的命令列出了由进程号为<pid>的进程创建的所有线程。 ps -T -p <...在top运行时,你也可以通过按“H”键将线程查看模式切换为开或关 ... -
mfc线程
2016-09-29 20:19:001、生成线程 方式1、 HANDLE hthread; //线程句柄 hthread=CreateThread(NULL,0,(LPTHREAD_...//如果后面不需要操作这个线程就可以把线程句柄关掉,但是线程不会被关,线程函数结束,线程才结束 方式2、 AfxB...
-
leetcode算法第三题
-
ASHRAE 2011 Liquid Cooling Whitepaper.pdf
-
[记录]VisualStudio Color Theme
-
ApacheFlink漫谈系列-概述
-
在高密度软件定义的WiFi网络中实现负载平衡
-
【硬核】一线Python程序员实战经验分享(1)
-
沿RF锁相辅助的光纤环路链路上任意中间点的精确时延感测和工作台频率分配
-
电商PC前后端分离项目Spring Boot后台实战第一期
-
全局绝热搜索算法的电路模型
-
需求分析与建模最佳实践
-
FPS游戏逆向-UE4虚幻四游戏逆向
-
宏宇社:国外lead入门教程(四)lead任务必备软件
-
基于杜鹃搜索的磷虾群算法解决工程优化问题
-
docker基本使用教程, 以及docker部署flask框架示例
-
2018年下半年 信息系统监理师 上午试卷 综合知识 软考真题【含答案和答案解析】
-
2019年上半年 网络工程师 上午试卷 综合知识 软考真题【含答案和答案解析】
-
MHA 高可用 MySQL 架构与 Altas 读写分离
-
C++MFC开发远程控制软件教程(VS2013)
-
Jenkins软件开发持续集成及自动构建
-
异常色散光纤激光器中的线性耗散孤子