java核心-多线程(4)-线程类基础知识
1.并发
<1>使用并发的一个重要原因是提高执行效率。由于I/O等情况阻塞,单个任务并不能充分利用CPU时间。所以在单处理器的机器上也应该使用并发。
<2>为了实现并发,操作系统层面提供了。但是进程的数量和开销都有限制,并且多个进程之间的数据共享比较麻烦。另一种比较轻量的并发实现是使用线程,一个进程可以包含多个线程。线程在进程中没有数量限制, 数据共享相对简单。线程的支持跟语言是有关系的。Java 语言中支持多线程。
<3>Java 中的多线程是抢占式的。这意味着一个任务随时可能中断并切换到其它任务。所以我们需要在代码中足够的谨慎,防范好这种切换带来的副作用。
2.基础
<1>Runnable 它可以理解成一个任务。它的run()方法就是任务的逻辑,执行顺序;
<2>Thread 它是一个任务的载体,虚拟机通过它来分配任务执行的时间片。Thread中的start方法可以作为一个并发任务的入口。不通过start方法来执行任务,那么run方法就只是一个普通的方法;
<3>线程的状态
见下图
<4>Callable<T> 它是一个带返回的异步任务,返回的结果放到一个Future对象中。
<5>Future<T> 它可以接受Callable任务的返回结果。在任务没有返回的时候调用get方法会阻塞当前线程。cancel方法会尝试取消未完成的任务(未执行->直接不执行,已经完成->返回false,正在执行->尝试中断)。
<6>FutureTask<T> 同时继承了Runnable, Callable 接口。
<7>Java 1.5之后,不再推荐直接使用Thread对象作为任务的入口。推荐使用Executor管理Thread对象。Executor是线程与任务之间的的一个中间层,它屏蔽了线程的生命周期,不再需要显式的管理线程。并且ThreadPoolExecutor 实现了此接口,我们可以通过它来利用线程池的优点。
<8>线程池涉及到的类有:Executor, ExecutorService, ThreadExecutorPool, Executors, FixedThreadPool, CachedThreadPool, SingleThreadPool。
<9>Executor 只有一个方法,execute来提交一个任务;
<10>ExecutorService 提供了管理异步任务的方法,也可以产生一个Future对象来跟踪一个异步任务。
主要的方法如下:
• submit 可以提交一个任务
• shutdown 可以拒绝接受新任务
• shutdownNow 可以拒绝新任务并向正在执行的任务发出中断信号
• invokeXXX 批量执行任务
<11>ThreadExecutorPool 线程池的具体实现类。线程池的好处在于提高效率,能避免频繁申请/回收线程带来的开销。
它的使用方法复杂一些,构造线程池的可选参数有:
1. corePoolSize : int 工作的Worker的数量。
2. maximumPoolSize : int 线程池中持有的Worker的最大数量
3. keepAliveTime : long 当超过Workder的数量corePoolSize的时候,如果没有新的任务提交,超过corePoolSize的Worker的最长等待时间。超过这个时间之后,一部分Worker将被回收。
4. unit : TimeUnit keepAliveTime的单位
5. workQueue : BlockingQueue 缓存任务的队列, 这个队列只缓存提交的Runnable任务。
6. threadFactory : ThreadFactory 产生线程的“工厂”
7. handler : RejectedExecutionHandler 当一个任务被提交的时候,如果所有Worker都在工作并且超过了缓存队列的容量的时候。会交给这个Handler处理。Java 中提供了几种默认的实现,AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy。
<12>Executors类提供了几种默认线程池的实现方式。
1. CachedThreadExecutor 工作线程的数量没有上限(Integer的最大值), 有需要就创建新线程。
2. FixedThreadExecutor 预先一次分配固定数量的线程,之后不再需要创建新线程。
3. SingleThreadExecutor 只有一个线程的线程池。如果提交了多个任务,那么这些人物将排队,每个任务都在上一个人物执行完之后执行。所有任务都是按照它们的提交顺序执行的。
<13>sleep(long) 当前线程 中止 一段时间。它不会释放锁。Java1.5之后提供了更加灵活的版本。TimeUnit 可以指定睡眠的时间单位。
<14>优先级 绝大多数情况下我们都应该使用默认的优先级。不同的虚拟机中对应的优先级级别的总数,一般用三个就可以了 MAX_PRIORITY, NORM_PRIORITY, MIN_PRIORITY。
<15>让步 Thread.yield()建议相同优先级的其它线程先运行,但是不保证一定运行其它线程。
<16>后台线程 一个进程中的所有非后台线程都终止的时候整个进程也就终止,同时杀死所有后台线程。与优先级没有什么关系。
<17>join() 线程 A 持有线程T,当在线程T调用T.join()之后,A会阻塞,直到T的任务结束。可以加一个超时参数,这样在超时之后线程A可以放弃等待继续执行任务。
<18>捕获异常 不能跨线程捕获异常。比如说不能在main线程中添加try-catch块来捕获其它线程中抛出的异常。每一个Thread对象都可以设置一个UncaughtExceptionHandler对象来处理本线程中抛出的异常。线程池中可以通过参数ThreadFactory来为每一个线程设置一个UncaughtExceptionHandler对象。
3.访问共享资源
<1>在处理并发的时候,将变量设置为private非常的重要,这可以防止其它线程直接访问变量。
<2>synchronized 修饰方法在不加参数情况下,使用对象本身作为锁。静态方法使用Class对象作为锁。同一个任务可以多次获得对象锁。
<3>显式锁 Lock,相比synchronized更加灵活。但是需要的代码更多,编写出错的可能性也更高。只有在解决特殊问题或者提高效率的时候才用它。
<4>原子性 原子操作就是永远不会被线程切换中断的操作。很多看似原子的操作都是非原子的,比如说long,double是由两个byte表示的,它们的所有操作都是非原子的。所以,涉及到并发异常的地方都加上同步吧。除非你对虚拟机十分的了解。
<5>volatile 这个关键字的作用在于防止多线程环境下读取变量的脏数据。这个关键字在c语言中也有,作用是相同的。
<6>原子类 AtomicXXX类,它们能够保证对数据的操作是满足原子性的。这些类可以用来优化多线程的执行效率,减少锁的使用。然而,使用难度还是比较高的。
<7>临界区 synchronized关键字的用法。不是修饰整个方法,而是修饰一个代码块。它的作用在于尽量利用并发的效率,减少同步控制的区域。
<8>ThreadLocal 这个概念与同步的概念不同。它是给每一个线程都创建一个变量的副本,并保持副本之间相互独立,互不干扰。所以各个线程操作自己的副本,不会产生冲突。
4.终结任务
<1>一个线程不是可以随便中断的。即使我们给线程设置了中断状态,它也还是可以获得CPU时间片的。只有因为sleep()方法而阻塞的线程可以立即收到InterruptedException异常,所以在sleep中断任务的情况下可以直接使用try-catch跳出任务。其它情况下,均需要通过判断线程状态来判断是否需要跳出任务(Thread.interrupted()方法)。
<2>synchronized方法修饰的代码不会在收到中断信号后立即中断。ReentrantLock锁控制的同步代码可以通过InterruptException中断。
<3>Thread.interrupted方法调用一次之后会立即清空中断状态。可以自己用变量保存状态。
5.线程协作
<1>wait/notifyAll wait/notifyAll是Object类中的方法。调用wait/notifyAll方法的对象是互斥对象。因为Java中所有的Object都可以做互斥量(synchronized关键字的参数),所以wait/notify方法是在Object类中的。
<2>wait与sleep 不同在于sleep方法是Thread类中的方法,调用它的时候不会释放锁;wait方法是Object类中的方法,调用它的时候会释放锁。
调用wait方法之前,当前线程必须持有这段逻辑的锁。否则会抛出异常,不能继续执行。
<3>wait方法可以将当前线程放入等待集合中,并释放当前线程持有的锁。此后,该线程不会接收到CPU的调度,并进入休眠状态。有四种情况肯能打破这种状态:
1. 有其它线程在此互斥对象上调用了notify方法,并且刚好选中了这个线程被唤醒;
2. 有其它线程在此互斥对象上调用了notifyAll方法;
3. 其它线程向此线程发出了中断信号;
4. 等待时间超过了参数设置的时间。线程一旦被唤醒之后,它会像正常线程一样等待之前持有的所有锁。直到恢复到wait方法调用之前的状态。还有一种不常见的情况,spurious wakeup(虚假唤醒)。就是在没有notify,notifyAll,interrupt的时候线程自动醒来。查了一些资料并没有弄清楚是为什么。不过为了防止这种现象,我们要在wait的条件上加一层循环。
<4> 当一个线程调用wait方法之后,其它线程调用该线程的interrupt方法。该线程会唤醒,并尝试恢复之前的状态。当状态恢复之后,该线程会抛出一个异常。
notify 唤醒一个等待此对象的线程。
notifyAll 唤醒所有等待此对象的线程。
6.错失信号
<1>当两个线程使用notify/wait或者notifyAll/wait进行协作的时候,不恰当的使用它们可能会导致一些信号丢失。
见下图
信号丢失是这样发生的:
当T2执行到Point1的时候,线程调度器将工作线程从T2切换到T1。T1完成T2条件的设置工作之后,线程调度器将工作线程从T1切换回T2。虽然T2线程等待的条件已经满足,但还是会被挂起。
<2>解决的方法比较简单:
见下图
将竞争条件放到while循环的外面即可。在进入while循环之后,在没有调用wait方法释放锁之前,将不会进入到T1线程造成信号丢失。
<3>Condition 他是concurrent类库中显式的挂起/唤醒任务的工具。它是真正的锁(Lock)对象产生的一个对象。其实用法跟wait/notify是一致的。await挂起任务,signalAll()唤醒任务。
<4>生产者消费者队列 Java中提供了一种非常简便的容器,BlockingQueue。已经帮你写好了阻塞式的队列。除了BlockingQueue,使用PipedWriter/PipedReader也可以方便的在线程之间传递数据
7.死锁
<1>死锁有四个必要条件,打破一个即可去除死锁。
四个必要条件:
1. 互斥条件。 互斥条件:一个资源每次只能被一个进程使用。
2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
3. 不剥夺条件:线程已获得的资源,在末使用完之前,不能强行剥夺。
4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
8.其他工具
<1>CountDownLatch 同步多个任务,强制等待其它任务完成。它有两个重要方法countDown,await以及构造时传入的参数SIZE。当一个线程调用await方法的时候会挂起,直到该对象收到SIZE次countDown。一个对象只能使用一次。
<2>CyclicBarrier 也是有一个SIZE参数。当有SIZE个线程调用await的时候,全部线程都会被唤醒。可以理解为所有运动员就位后才能起跑,早就位的运动员只能挂起等待。它可以重复利用。
<3>DelayQueue 一个无界的BlockingQueue,用来放置实现了Delay接口的对象,在队列中的对象只有在到期之后才能被取走。如果没有任何对象到期,就没有头元素。
<4>PriorityBlockingQueue 一种自带优先级的阻塞式队列。
<5>ScheduledExecutor 可以把它想象成一种线程池式的Timer, TimerTask。
<6>Semaphore 互斥锁只允许一个线程访问资源,但是Semaphore允许SIZE个线程同时访问资源。
<7>Exchanger 生产者消费者问题的特殊版。两个线程可以在都‘准备好了’之后交换一个对象的控制权。
<8>ReadWriteLock 读写锁。 读-读不互斥,读-写互斥,写-写互斥。
以上来自《think in java》
java核心-多线程(4)-线程类基础知识的更多相关文章
- Java并发多线程 - 并发工具类JUC
安全共享对象策略 1.线程限制 : 一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改 2.共享只读 : 一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问, 但是任何线程都 ...
- java: Thread 和 runnable线程类
java: Thread 和 runnable线程类 Java有2种实现线程的方法:Thread类,Runnable接口.(其实Thread本身就是Runnable的子类) Thread类,默认有ru ...
- Java做acm所需要的基础知识之排序问题
Java做acm所需要的基础知识. 以前做acm的题都是用C/C++来写代码的,在学习完Java之后突然感觉Java中的方法比C/C++丰富很多,所以就整理一下平时做题需要用到的Java基础知识. 1 ...
- java学习笔记15--多线程编程基础2
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note15.html,转载请注明源地址. 线程的生命周期 1.线程的生命周期 线程从产生到消亡 ...
- Java:多线程,线程同步,同步锁(Lock)的使用(ReentrantLock、ReentrantReadWriteLock)
关于线程的同步,可以使用synchronized关键字,或者是使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象.本文探讨Lock对象. synchronize ...
- 【C++】类-基础知识
类-基础知识 目录 类-基础知识 1. 语法定义 2. 类的实现 3. 三个基本的函数 3.1 构造函数 功能 形式 调用时机 默认构造函数 3.2 复制构造函数 功能 形式 调用时机 3.3 析构函 ...
- java核心-多线程(1)-知识大纲
Thread,整理一份多线程知识大纲,大写意 1.概念介绍 线程 进程 并发 2.基础知识介绍 Java线程类 Thread 静态方法&实例方法 Runnable Callable Futur ...
- java核心-多线程-Java多线程编程涉及到包、类
Java有关多线程编程设计的类主要涉及两个包java.lang和java.util.concurrent两个包 java.lang包,主要是线程基础类 <1>Thread <2> ...
- java核心技术-多线程之线程基础
说起线程,无法免俗首先要弄清楚的三个概念就是:进程.线程.协程.OK,那什么是进程,什么是线程,哪协程又是啥东西.进程:进程可以简单的理解为运行在操作系统中的程序,程序时静态代码,进程是动态运行着的代 ...
随机推荐
- 第五周之Hadoop学习(五)
在上周已经完成Hadoop的Java编程环境下的配置,这周则是通过对Eclipse的环境编程对Hadoop的API进行简单的调用 参考地址:https://blog.csdn.net/u0105237 ...
- Python组合类型笔记
Python中常用的三种组合数据类型,分别是: - 集合类型 - 序列类型 - 字典类型 1. 集合类型: -集合用大括号{}表示,元素间用逗号分隔 -建立集合类型用{}或set() -建立空集合类型 ...
- 学习不一样的vue5:vuex(完结)
学习不一样的vue5:vuex(完结) 发表于 2017-09-10 | 分类于 web前端| | 阅读次数 4029 首先 首发博客: 我的博客 项目源码: 源码(喜欢请star) 项目预览 ...
- Write-up-NODE-1
关于 下载地址:点我 哔哩哔哩:哔哩哔哩 一天研究OBS终于不闪屏了 顺便在这里记录一下,上网查了很久.刚刚开始是不闪屏了,但是锁屏后就唤醒不了了,只能强制关机. 然后又上网找了很久,重启了N次,终于 ...
- java swing简介
java应用程序用户界面开发包 Swing是一个用于开发Java应用程序用户界面的开发工具包.它以抽象窗口工具包(AWT)为基础使跨平台应用程序可以使用任何可插拔的外观风格.Swing开发人员只用很少 ...
- django-文件上传Media url的配置
一:问题 当开启一个项目的时候,通常会遇到文件(图片,音频等)上传的需要,最常见的比如图片的上传,用户头像,后台管理添加图片,而图片的在是数据库中的存储主要是以该文件的相对路径,在django中可以使 ...
- Leet Code 8.字符串转换整数
实现一个atoi函数,使其能将字符串转成整数,根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止.当我们寻找到的第一个非空字符为正或负号时,则将该符号与后面尽可能多的连续数字组合起来,作 ...
- VOC2012数据集提取自己需要的类的图片和对应的xml标签
根据需要修改路径和自己需要的类即可. import os import os.path import shutil fileDir_ann = r'/home/somnus/tttt/VOC2012/ ...
- postgres登录失败Connection refused与SSL off失败
连接失败问题 使用postgres数据库连接工具测试,遇到两次失败 第一个登录失败问题 Connection to 192.168.XX.XX:5432 refused. Check that the ...
- 、第1节 kafka消息队列:8、9、kafka的配置文件server.properties的说明
10.kafka的配置文件说明 Server.properties配置文件说明 #broker的全局唯一编号,不能重复 broker.id=0 #用来监听链接的端口,producer或consumer ...