java多线程

java多线程

[TOC]

创建线程

class MyRunnable implements Runnable{
  public void run(){
    //线程执行代码
  }
}
Runnable r = new MyRunnable();
//创建线程并启动
Thread t = new Thread(r);
t.start();

直接调用Thread类或Runnable类的run方法并不会 创建线程,只会在本线程内执行

中断线程

  • 当线程的run方法执行完毕后或者出现了没有捕获的异常时,线程将终止。
  • 当对一个线程调用interrupt方法时,线程中的中断标志将被置位,这是每个线程都有的boolean标志,每个线程都应不时检查这个标志,已判断线程是否中断。
  • 使用Thread.currentThread()方法获取当前线程,Thread.currentThread().isInterrupted方法返回当前线程被置位的状态。但是当线程被阻塞(sleep或wait)时将无法检测中断状态,当对阻塞状态的线程调用interrupt方法时线程会抛出interrupted Exception异常。
  • interrupted测试当前线程是否中断,执行方法会将线程的中断状态置为false。

线程状态

  • New(新创建)
  • Runnable(可运行)
  • Blocked(被阻塞)
  • Waiting(等待)
  • Timed Waiting(计时等待)
  • Terminated(被终止)
  1. 新创建线程

    使用new操作刚刚创建的线程,此时线程还未开始运行。****

  2. 可运行线程

    一旦调用了Thread的start方法线程就处于runable状态,一个可运行的线程可能正在运行也可能没有运行,取决于操作系统的上下文切换。一旦一个线程开始运行,可能随时被中断,目的是为了让其他线程获得运行机会,操作系统抢占式调度系统给每一个可运行线程一个时间片来执行任务,时间片用完时,系统剥夺线程的运行权,并给其他线程运行机会。

  3. 被阻塞或等待的线程

    当线程处于被阻塞或等待状态时,它暂时不活动,不运行任何代码切消耗资源,直到线程调度器重新激活它。

    • 当一个线程试图获取一个内部对象锁,而该锁被其他线程持有,则该线程进入阻塞状态。当其他线程释放锁,并且调度去允许本线程持有时,该线程恢复为非阻塞状态。
    • 当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。
    • Thread.sleep、Object.wait、Thread.join、Lock.tryLock、Condition.await方法将线程进入等待状态。

    当一个线程被阻塞或等待时,另一个线程被调度为可运行状态,当一个线程重新被激活时,调度器检查是否比当前运行线程具有更高优先级,如果优先级更高则从当前运行线程中选一个剥夺当前线程优先级,选择一个新的线程运行。

  4. 线程有俩种原因被终止:

    • run方法正常退出而自然死亡。
    • 因为没有捕获的异常终止运行而意外死亡。

线程属性

线程优先级

  • 每一个线程有一个优先级,默认情况,一个线程继承它的父线程的优先级,可以用setPriority方法提高或降低任何一个线程的优先级。可以将优先级设置为在MIN_PRIORITY(1) 至MAX_PRIORITY(10)之间,NORM_PRIORITY=5。
  • 线程优先级高度依赖于操作系统调度,java优先级被映射到操作系统的优先级。优先个数也许更多,也许更少。在linux上线程优先级被忽略,所有线程具有相同优先级

守护线程

可以通过调用t.setDaemon(true);将线程转换为守护线程。守护线程的唯一用途就是为其他线程提供服务。当只剩下守护线城时虚拟机就退出了,因为没必要继续运行程序了。

未捕获异常处理器

  • 线程的run方法不能抛出任何被检测的异常,不被检测的异常将导致线程终止。但是不需要任何catch子句来处理可以被传播的异常,相反在线程死亡前异常被传递到一个用于未捕获异常的处理器。该处理器必须属于一个实现Thread.UncaughtExceptionHandler接口的类。这个接口只有一个方法:
void uncaughtException(Thread t,Throwable e)
  • 可以用setUncaughtExceptionHandler方法为任何线程安装一个处理器。也可以用Thread类的静态方法setDefaultUncaughtExceptionHandler为所有线程安装一个默认处理器,替换处理器可以使用日志API发送未捕获异常的报告到日志文件。

  • 默认处理器为空,但是如果不为独立的线程安装处理器,此时的处理器就是该线程的ThreadGroup对象。

    线程组(ThreadGroup)是一个可以统一管理的线程集合。默认情况所有线程属于相同的线程组,但是也可以建立其他的组,现在引入了更好的特性用于线程集合的操作,不建议在自己的程序中使用线程组。

  • ThreadGroup类实现了Thread.UncaughtExceptionHandler接口,它的uncaughtException方法做如下操作:

    1. 如果该线程组有父线程组,那么父线程组的uncaughtException方法被调用。
    2. 否则如果Thread.getDefaultExceptionHandler方法返回一个非空的处理器,则调用该处理器。
    3. 否则如果Throwable是ThreadDeath的一个实例,什么都不做。
    4. 否则,线程的名字以及Throwable的栈踪迹被输出。

同步

锁对象

如果使用锁就不能使用带资源的try语句。

  • 有两种机制防止代码块受并发访问的干扰,java提供一个synchronized关键字达到这个目的,jdk1.5引入了ReentrantLock类,用于显式锁定。
//ReentrantLock显式锁定
myLock.lock();
try{
  
}finally{
  myLock.unlock();
}
  • 当一个对象有自己的ReentrantLock对象时,如果俩个线程试图访问同一个对象,那么将触发锁机制,如果俩个线程访问俩个不同的对象则都不会发生阻塞。

可重入锁

  • synchronized和ReentrantLock都是可重入锁,比如一个方法是synchronized,递归调用自己,第一个获得锁后第二次依然可以获得锁,在锁的内部对象有一个计数器用来跟踪锁的数量,unlock或synchronized方法执行完毕后对计数器-1。
public class Widget {
    public synchronized void doSomething() {
        ...
    }
}
     
public class LoggingWidget extends Widget {
    public synchronized void doSomething() {
        System.out.println(toString() + ": calling doSomething");
        super.doSomething();//若内置锁是不可重入的,则发生死锁
    }
}
//当执行子类方法时,先获取了一次Widget的锁,在执行super的时候,又要获取一次如果是,如果不可重入线程就会死亡。
  • 为每个锁关联一个获取计数器和一个所有者线程,当计数值为0的时候,这个所就没有被任何线程只有.当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1,如果同一个线程再次获取这个锁,技术值将递增,退出一次同步代码块,计算值递减,当计数值为0时,这个锁就被释放.

条件对象

  • 一个锁对象可以有一个或多个相关的条件对象,可以使用newCondition方法获得一个条件对象。

    例如:private Condition sufficientFunds;

  • 如果条件对象发现条件不满足时,它调用await方法,当前线程现在被阻塞了,并放弃了锁。等待或者锁的线程和调用await方法的线程存在本质上的不同,一个线程调用await方式,它进入该条件的等待集。当锁可用时,该线程不能马上解除阻塞。相反,它处于阻塞状态,直到另一个线程调用同一条件上的signalAll方法为止。

    当另一个线程转账时,它应该调用sufficientFunds.signalAll();

    这一调用重新激活因为这一条件而等待的所有线程,当这些线程从等待集中移出时,它们再次成为可运行的,调度器将再次激活它们。同时它们试图重新进入该对象,一旦锁成为可用的,它们中的某个将从await调用返回,获得该锁并从被阻塞的地方继续执行。此时应该重新检测条件对象,由于无法确保该条件被满足,signalAll方法仅仅是通知正在等待的线程:此时有可能已经满足条件,值得再去检测该条件。

  • 至关重要的是最终需要某个线程调用signalAll方法,当一个线程调用await时,它没有办法重新激活自身,只能靠其他线程来重新激活,否则将陷入死锁状态中。

  • 另一个方法signal则是随机解除等待集中某个线程的阻塞状态,这比解除所有线程阻塞更加有效,但是随机选择的线程如果发现仍然不能运行,那么它再次被阻塞,如果没有其他线程再次调用signal,系统就死锁了。

while(account[from] < amount){
    condition.await();
}

synchronized 关键字

  • 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。
  • 锁可以管理试图进入被保护代码段的线程。
  • 锁可以拥有一个或多个相关的条件对象
  • 每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。
  1. java中每一个对象都有一个内部锁,如果一个方法用synchronized关键字声明,那么对象锁就保护整个方法。
  2. 内部对象锁只有一个相关条件,wait方法添加一个线程到等待集中,notifyALL/notify方法解除等待线程的阻塞状态。等价于await与signalAll。
class Bank{
    private double[] accounts;
    public synchronized void transfer(int from,int to,int amount) throws InterruptedException{
        while(accounts[from] < amount){//条件
            wait();//如果条件不满足会将当前线程添加到等待集中
            accounts[from]  -= amount;
            account[to] += amount;
            notifyAll();//转账完成后通知其他等待中的线程可以检查条件了
        }
    }
}
  1. 如果Bank类有一个静态同步方法,那么当该方法被调用时,Bank.class对象会被锁住,因此没有其他线程可以调用同一个类的这个或任何其他的同步静态方法。

内部锁和条件的局限性

  • 不能中断一个正在试图获得锁的线程。
  • 试图获得锁时不能设定超时。
  • 每个锁仅有单一的条件,可能是不够的。
  • 最好既不适用lock/Condition也不适用synchronized,许多情况下可以用java.util.concurrent包中的一种机制。

监视器概念

  • 监视器是只包含私有域的类。
  • 每个监视器类的对象有一个相关锁。
  • 使用该锁对所有的方法进行加锁。
  • 该锁可以有任意多个相关条件。
  • java中每一个对象有一个内部锁和内部的条件。
  • 如果一个方法用synchronized声明,表现的就像是一个监视器方法,通过调用wait/notifyAll/notify来访问条件变量。

Volatile域

Volatile关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。

volatile变量不能提供原子性,例如:public void flipDone(){ done = !done },不能确保翻转域中的值。

线程局部变量

使用ThreadLocal辅助类为各个线程提供各自的实例。

锁测试与超时

  • tryLock方法尝试获取锁,成功后返回true,否则返回false,而且线程可以立刻离开,同时可以为tryLock设置超时参数。
  • 在等待一个条件时也可以提供一个超时: condition.await(100,TimeUnit.MILLISECONDS),如果一个线程被另一个线程通过调用signalAll或signal激活,或者已超时,或者线程被中断,await方法将返回。

读写锁

  1. ReentrantLock 通用锁,读写都阻塞。
  2. ReentrantReadWriteLock 读写锁
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();

阻塞队列

  • 当试图向队列添加元素而队列已满,或是香葱队列移出元素而队列为空的时候,阻塞队列导致线程阻塞。
  • 工作者线程可以周期性地将结果存储在阻塞队列中,其他工作者线程移出中间结果处理,队列会自动地平衡负载,如果第一个线程集运行的比第二个慢,第二个线程集在等待结果时会阻塞,如果第一个线程集运行的快,他将等待第二个队列集赶上来。

阻塞队列方法

方法 正常动作 特殊情况动作
add 添加元素 队列满抛出IllegalStateException
element 返回头元素 队列空抛出NoSuchElementException
offer 添加元素返回true 队列满了返回false
peed 返回队列头元素 队列空返回null
poll 移出并返回头元素 队列空返回null
put 添加一个元素 队列满则阻塞
remove 移出并返回头元素 队列空抛出NoSuchElementException
take 移出并返回头元素 如果队列空则阻塞

java.util.concurrent包提供了阻塞队列的几个变种:

  • ArrayBlockingQueue(int capacity,boolean fair) 带有容量和公平性设置的阻塞队列,用循环数组实现。
  • LinkedBlockingQueue 单项链表队列
  • LinkedBlockingDeque,容量没有上边界,但可指定最大容量,并有可选参数指定是否需要公平性,若设置了公平性,则等待时间最长的线程会有限处理,公平性会降低性能。
  • PriorityBlockingQueue<E>,优先级队列,而不是先进先出,元素会按照优先级顺序移出,该队列没有容量上限,但是如果队列是空的取元素操作会阻塞,堆实现。
  • LinkedTransferQueue类实现了TransferQueue接口,允许生产者线程等待,直到消费者准备就绪可以接受一个元素,如果生产者调用qtransfer(item); 这个调用会阻塞,直到另一个线程将元素删除。
  • delayQueue 包含delayed元素无边界有阻塞时间队列,只有那些延迟已经超过时间的元素可以从队列中移出。
  • blockingQueue<E> 普通阻塞队列
  • BlockingDeque<E> 双向阻塞队列

线程安全的集合

java.util.concurrent包提供了映射表、有序集合和队列的高效实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet和ConcurrentLinkedQueue。

与大多数集合不同,size方法不必在常量时间内操作。确定这样的集合当前的大小通常需要遍历。

集合返回弱一致性的迭代器。这意味着迭代器不一定能反映出被构造之后的所有的修改,但是它们不会将一个值返回俩次。

普通集合如果在迭代器构造之后发生改变,java.util包中的迭代器将抛出一个ConcurrentModificationException。

  • ConcurrentLinkedQueue<E> 线程安全无边界非阻塞队列。
  • ConcurrentSkipListSet<E> 线程安全有序集合。
  • ConcurrentHashMap
  • ConcurrentSkipListMap 线程安全的hash table。具有类似redis setnx这种方法。

Callable与Future

Runnable:没有参数没有返回值。

Callable:有参数有返回值,只有一个方法call。

Future:保存异步计算的结果,可以启动一个计算,将Future对象交给某个线程,然后忘掉它,Future对象的持有者在结果计算好之后就可以获得它。

如果运行该计算的线程被中断,get与get带有超时的方法都将抛出InterruptedException。

Future的方法:

方法名 描述
V get() 调用被阻塞直到返回结果
V get(long timeouot,TimeUnit unit) 阻塞调用直到超时,超时抛出InterruptedException
boolean isDone() 是否完成
boolean isCancelled() 是否取消
void cancel(boolean mayInterrupt) 取消,如果还没有开始将不再开始。

FutureTask包装器可以Callable转换成Future和Runnable,它同时实现二者的接口。

执行器

构建一个新的线程是有一定代价的,如果程序中创建大量临时线程,应该使用线程池,一个线程池包含许多准备运行的空闲线程,将Runnable对象交给线程池,就会有一个线程调用run方法。当run方法退出时,线程不会死亡,二十在池中准备为下一个请求提供服务。

执行器类有许多静态工厂方法用来构建线程池

  • newCachedThreadPool:必要时创建新线程;空闲线程只会被保留60秒。对于每个任务,如果有空闲线程可用立即执行任务,如果没有则创建一个线程。
  • newFixedThreadPool:该池包含固定数量的线程;空闲线程会一直被保留。如果任务数超过线程数,将任务放到队列中。
  • newSingleThreadExecutor:只有一个线程的“池”,该线程顺序执行每一个提交的任务。
  • newScheduledThreadPool:用于预定执行而构建的固定线程池,替代java.util.Timer
  • newSingleThreadScheduledExecutor:用于预定执行而构建的单线程“池”

线程池

使用submit方法将Runnable对象或Callable对象提交给线程池。

添加任务至线程池

Future<?> submit(Runnable task),返回结果Future<?>可以调用isDone、cancel、isCancelled方法,但是get方法在完成的时候只是简单地返回null。

Furure<T> submit(Runnable task,T result) get方法在完成的时候返回result对象。

Future<T> submit(Callable<T> task) 返回的Future对象将在计算结果准备好的时候得到它。

线程池会在方便的时候今早执行提交的任务,submit方法会返回一个Future对象,可用来查询该任务状态。

关闭线程池

当用完一个线程池的时候调用shutdown,该方法启动该池的关闭序列,被关闭的线程池不在接受新的任务,所有任务都完成后,线程池中的线程死亡。

或者调用shutdownNow,取消尚未开始的任务,并中断正在运行的线程。

获取线程池线程数量

必须将pool对象强制转换为ThreadPoolExecutor类对象,调用getLargestPoolSize方法。

预定执行

ScheduledExecutorService接口具有为预定执行或重复执行任务而设计的方法,可以预定Runnablehuocallable在初始的延迟之后只运行一次,也可以预定一个Runnable对象周期性地运行。

控制任务组

invokeAny方法提交所有对象到一个Callable对象的集合中,并返回某个已经完成了的任务的结果。无法找到返回的究竟是哪个任务的结果。当其中一个任务完成时,运行流程就停止了。

invokeAll方法提交所有对象到一个Callable对象的集合中,并返回一个Future对象的列表,包含所有任务的返回结果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容