【Java学习】并发-1

一、线程

1、线程的六种状态

①New(新建)
②Runnable(可运行)
③Blocked(阻塞)
④Waiting(等待)
⑤Timed waiting(计时等待)
⑥Terminated(终止)

【New】

当用new创建新线程(例如:new Thread(r)),这个线程还没有开始运行,就是新建状态。当一个线程处于新建状态时,程序还没有开始运行线程中的代码。

【Runnable】

一旦调用start方法,线程就处于可运行状态。一个可运行的线程可能正在运行也没有运行(需要由操作系统提供具体的运行时间,中途可能暂停)。

具体的运行时间根据服务器操作系统使用的是抢占式调度还是协作式调度。

抢占式调度:操作系统提供一个时间片段给线程运行,线程只能在该时间片段运行,非时间片段只能等待。

协作式调度:一个线程只有在调用yield方法或者被阻塞或等待时才失去控制权。

【Blocked & Waiting &Timed waiting】

当线程处于阻塞或等待状态时,它暂时是不活动的。不运行任何代码,消耗最少的资源。要由线程调度器重新激活该线程。

如何操作成非活动状态:

a、当一个线程试图获取一个内部的对象锁(不是java.util.concurrent的lock),而这个锁目前被其他线程占有,该线程就会被阻塞。当所有其他线程都释放了这个锁,并且线程调度器允许该线程持有这个锁时,他将变为非阻塞状态。

b、当线程等待另一个线程通知调度器出现一个条件时,这个线程会进入等待状态(如:调用Object.wait、Thread.join、等待java.util.concurrent库中lock或Condition时)。实际上,阻塞状态与等待状态实际结果并没有太大区别。

c、有几个方法有超时参数,调用这些方法会让线程进入计时等待状态,这一状态将一直保持到超时期满或者接收到适当通知(例如:Thread.sleep、Object.wait、Thread.join、Lock.tryLock以及Condition.await)。

【Terminated】

线程会由于以下两个原因之一而终止:

a、run方法正常退出,线程自然终止

b、因为一个没有捕获的异常终止了run方法,使线程意外终止

具体来说,可以调用线程的stop方法杀死一个线程。该方法抛出一个ThreadDeath错误对象,这会杀死线程(但这个方法已经被废弃,勿用)。

2、线程的属性

①中断线程

a、interrupt:请求终止一个线程

b、Thread.currentThread得到当前线程

c、isInterrupted检查线程是否中断

d、interrupted也是检查是否中断,但他是个静态方法,且会清除该线程的中断状态

注:如果线程被阻塞(sleep或wait),无法检查,会抛InterruptedException异常。如果线程已经是中断状态不要调用sleep,他会终止中断,并抛InterruptedException异常。

适用场景:线程发生异常设置中断,以便后续流程处理

②守护线程

t.setDaemon(true);

用途:为其他线程提供服务(清空过时缓存的线程也是守护线程,当程序只剩下守护线程,虚拟机会退出)。

③线程名

t.setName("My Thread");

④未捕获异常的处理器

线程的run方法不能抛出任何检查型异常,但是非检查型异常可能会导致线程终止,并导致线程死亡。不过对于可传播的异常,并没有任何catch子句。实际上,在线程死亡之前,异常会传递到一个用于处理未捕获异常的处理器。这个处理器必须属于一个实现了Thread.UncaughtExceptionHandler接口的类。

可以用setUncaughtExceptionHandler方法为任何线程安装一个处理器。已可以用Thread类的静态方法setDefaultUncaughtExceptionHandler为所有线程安装一个默认处理器。替代处理器可使用日志API将未捕获异常的报告发送到一个日志文件。未设置处理器,处理器为null。但是,如果没有未单个线程安装处理器,那么处理器就是该线程的ThreadGroup对象(尽量不要使用线程组)。

⑤线程优先级(已废除)

3、同步

①锁对象

公平锁

ReentrantLock lock = new ReentrantLock();
lock .lock();
try {
    //todo
}finally {
    lock .unlock();
} 

适用场景:当多个线程竞争一个资源,但该资源同时操作只支持一个线程运行时。

优点:一个时间段只能有一个线程进入该代码片段

缺点:比常规锁慢;如果线程调度器选择忽略一个已经为锁等待很长时间的线程,它就没有机会得到公平处理。

②条件对象

当业务中处理代码需要某些前置时,条件满足才继续运行,不满足继续等待

private Condition sufficientFunds;
public class() {
    sufficientFunds = instance.newCondition();
}

public void method() throws InterruptedException {
    instance.lock();
    try {
        while (!(OK to proceed)){
            sufficientFunds.await();
        }
        //todo
        sufficientFunds.signalAll();
    }finally {
        instance.unlock();
    }
}

await:将该线程放在这个条件的等待集中

signalAll:解除该条件等待集中所有线程的阻塞状态(解除所有线程的阻塞,但还是需要遵循有锁的代码只能一个线程访问)

signal:解除该条件等待集中随机一个线程(最好不要使用)。

注意:必须在有锁的情况才能使用条件对象

疑问:如果所有的线程都是等待阻塞了,进程不就挂了吗?慎用?

③synchronized关键字

内部锁

public synchronized void method() throws InterruptedException {
    while (!(OK to proceed)){
        wait();
    }
    //todo
    notifyAll();
}
notifyAll:解除在这个对象调用wait放的的那些线程的阻塞状态。{该方法只能在同步方法或同步块中调用。如果当前线程不是对象锁的所有者,该方法会抛出一个IllegalMonitorStateException异常}-{警示}
notify:随机选择一个在这个对象调用wait方法的线程,解除其阻塞状态。{警示}
wait:导致一个线程进度等到状态,直到它得到通知。{警示}
wait(long millis)
wait(long millis, int nanos):导致一个线程进入等待状态,直到它得到通知或者经过了指定的时间。{警示}。纳秒数不能超过1000000.

④同步块

 

限制:

a、不能中断一个正在尝试获得锁的线程

b、不能指定尝试获得锁的超时时间

c、每个锁仅有一个条件可能是不够的

小结:

a、锁用来保护代码片段,一次只能有一个线程执行被保护的代码

b、锁可以管理试图进入被保护代码段的线程

c、一个锁可以有一个或多个相关联的条件对象

d、每个条件对象管理那些已经进入被保护代码段但还不能运行的线程

建议:

a、最好既不适用Lock/Condition也不使用synchronized关键字。在许多情况下,可以使用java.util.concurrent包中的某种机制,它会为你处理所有的锁定(例如如何使用阻塞队列来同步完成一个共同任务的线程)。还应当研究并行流。

b、如果synchronized关键字适合你的程序,那么尽量使用这种做法,这样可以减少编写的代码量,还能减少出错的概率。

c、如果特别需要Lock/Condition结构提供的额外能力,则使用Lock/Condition。

 

posted @ 2020-12-16 10:24  启*敬  阅读(108)  评论(0编辑  收藏  举报