Java核心技术笔记-第14章(1)

多线程

1.启动新线程

new Thread(new Runnable(){
    public void run(){
        ...
    }
}).start();    //建立一个新线程,并触发run方法

现在已经不再推荐使用下面这种:

new Thread(){
    public void run(){
        ...
    }
}.start();

2.中断线程(书上写的有点乱,可能是翻译的原因。在网上另外找了一个对中断的介绍:http://www.infoq.com/cn/articles/java-interrupt-mechanism Thank you!)
(1)中断并不意味着线程一定终止。在一个线程被请求(请求可以由任何一个线程发起)中断时,该线程将自己决定采取何种操作(或者不理睬该中断请求)。如:线程t1通过调用interrupt方法将线程t2的中断状态置为true,t2可以在 合适的时候 调用interrupted或isInterrupted来检测状态并做相应的处理。
(2)每一个线程都有一个标志位,用来标识该线程是否处于中断状态。
(3)三个方法
[1]public static boolean interrupted Thread的静态方法,用来判断一个线程是否被中断,同时清除状态位(置为false)
[2]public boolean isinterrupted() 判断一个线程的中断状态,但是不会改变线程的中断状态。
[3]public void interrupt() 中断线程
(4)当可能阻塞的方法声明中有抛出InterruptedException则暗示该方法是可中断的。如:BlockingQueue#put,BlockingQueue#take,Object#wait, Thread#sleep
(5)如果线程处于被阻塞状态(sleep, wait, join状态),且线程没有被锁,调用interrupt方法就会抛出一个InterruptedException异常,仅此而已;如果线程处于正常活动状态,调用interrupt方法就会将该线程的中断状态标志位置为true,仅此而已
参考:https://www.zhihu.com/question/41048032 Thank you!
(6)中断处理(要使调用者可以知道线程的状态 或者 知道抛出了异常)
[1]如果遇到的是可中断的阻塞方法抛出InterruptedException,可以继续向方法调用栈的上层抛出该异常,如果是检测到中断,则可清除中断状态并抛出InterruptedException,使当前方法也成为一个可中断的方法。
[2]若有时候不太方便在方法上抛出InterruptedException,比如要实现的某个接口中的方法签名上没有throws InterruptedException,这时就可以捕获可中断方法的InterruptedException并通过Thread.currentThread.interrupt()来重新设置中断状态。如果是检测并清除了中断状态,亦是如此。

3.线程状态
(1)New新建状态
刚刚创建一个线程对象,如new Thread(r),还没有开始运行时,此时线程就处于新建状态。
(2)Runnable可运行状态
一旦调用start方法,线程就处于可运行状态。有可能正在运行,也有可能正在运行。
(3)被阻塞状态(Bolcked),等待状态(Waiting),计时等待(Timed Waiting):暂时不活动,不运行任何代码并且消耗最少的资源,直到线程调度器重新激活它。
如何达到该状态:[1]线程试图获取一个内部的对象锁,而该锁被其他线程持有,则该线程进入阻塞状态。[2]调用Object的wait方法或者Thread的join方法时会进入等待状态。[3]有的方法有一个超时参数,会进入计时等待。
(4)被终止状态(Teiminated),出现的原因:[1]正常退出run方法 [2]因为一个没有捕获的异常终止了run方法而意外死亡。

4.线程属性
(1)线程优先级
[1]每一个线程都有一个优先级,并且可以使用setPriority方法进行设置。优先级从MIN_PRIORITY(Thread中定义为1)到MAX_PRIORITY(Thread中定义为10),中间值NORM_PRIORITY为5.
[2]不要过度依赖于线程优先级,因为最终优先级还是会依赖于具体的平台系统。
[3]优先级过高的线程可能会使低优先级的线程饿死。
(2)守护线程
[1]唯一用途是为其他线程服务。当只剩下守护线程时,虚拟机就退出了。
[2]通过t.setDaemon(true)将一个线程设置为守护线程。
[3]守护线程不应该访问任何固有资源,因为它随时可能会中断。
(3)未捕获异常处理器:由run方法不能抛出任何被检测的异常,而我们又不想在run方法中捕获异常时,此时就用到未捕获异常处理器。如下面无法捕获异常:

public class Test {      //代码来自:http://blog.csdn.net/yubo_725/article/details/45560283

    public static void main(String[] args) {  
        try {  
            //通过try catch语句捕获异常  
            new TestThread().start();  
        } catch (Exception e) {  
            //如果成功捕获异常,则会打印下面的语句  
            System.out.println("线程运行异常");      //不会打印,也就是说捕获不到
        }  
    }  

    static class TestThread extends Thread {  

        @Override  
        public void run() {  
            super.run();  
            //在子线程中模拟一个异常  
            System.out.println("1/0 = " + 1 / 0);  
        }  

    }  

}  

解决方法:
定义一个处理器类,实现UncaughtExceptionHandler接口,重写uncaughtException方法。然后将该处理器设置为线程的为捕获异常处理器。如:

public class MyCrashHandler implements UncaughtExceptionHandler {    //代码来自:http://blog.csdn.net/yubo_725/article/details/45560283

    @Override  
    public void uncaughtException(Thread arg0, Throwable arg1) {  
        //当程序发生未捕获的异常时,会执行该方法  
        System.out.println("抱歉,程序出异常了...");  
    }  

}  
public class Test {  

    public static void main(String[] args) {  
        //为当前线程设置默认的未捕获异常处理器  
        Thread.setDefaultUncaughtExceptionHandler(new MyCrashHandler());  
        //执行子线程,产生异常  
        new TestThread().start();  
    }  

    static class TestThread extends Thread {  

        @Override  
        public void run() {  
            super.run();  
            //在子线程中模拟一个异常  
            System.out.println("1/0 = " + 1 / 0);  
        }  

    }  

}  

5.同步
(1)可以使用synchronized关键字或者ReentrantLock类防止并发访问的干扰。如:

Lock bankLock = new ReentrantLock();    //定义在类的数据成员中,这样每个对象都会有一个的锁。如果两个线程访问同一个对象,那么锁就会以串行方式提供服务;如果两个线程访问两个不同的对象,那么两个线程都不会被阻塞。
public void transfer(int from, int to, int amount)
{
    bankLock.lock();      //lock和unLock之间的代码称为临界区
    try
    {
        ...
    }
    finally
    {
        bankLock.unLock();
    }
}

(2**)条件对象**:当一个线程获得了锁之后,却发现有个条件无法满足,无法继续执行该线程,此时就用到了条件对象。(可以有多个条件对象)

private Condition sufficientFunds = bankLock.newCondition();    //首先在类的数据成员中定义一个条件对象
...
public void transfer(int from, int to, int amount) throws InterruptedException
{
    bankLock.lock();
    try
    {
        while(accounts[from]<amount)   //转账余额不足。注意要使用while(...)形式
        {
            sufficientFunds.await();   //此时该线程被阻塞,释放锁,等待被其他线程唤醒(只能被其他线程唤醒)
        }
        //进行转账操作
        ...
        sufficientFunds.signalAll();   //由于转账完成后,可能会使其他线程的条件得到满足,所以调用signalAll方法解除所有等待线程的阻塞,然后被解除阻塞的线程竞争锁,得到锁之后判断余额是否满足,不满足则再次await
    }
    finally
    {
        bankLock.unLock();
    }
}

注:另外还有一个signal方法,随机解除某个线程的阻塞

(3)synchronized关键字
[1]每一个对象都有一个内部锁,对于synchronized修饰的方法,线程必须先获得内部锁才可以使用该方法。
[2]内部锁只有一个相关条件,使用wait, notifyAll/notify 来阻塞,解除阻塞一个线程,他们都是Object类的final方法。只是使用不同的名字与Condition类的await,signalAll/signal方法区分。
[3]使用synchronized无需再定义ReentrantLock对象和条件对象,例如:

public synchronized void transfer(int from, int to, int amount) throws InterruptedException
{
        while(accounts[from]<amount)   
        {
            wait();   //直接使用wait方法
        }
        //进行转账操作
        ...
        notifyAll();  
}

(4)同步阻塞(通常不推荐使用)

public void transfer(Vector<Double> accounts, int from, int to, int amount)
{
    synchronized(accounts)      //此时accounts就只能由一个线程访问执行语句块中的代码,但是前提是accounts对自己的所有可修改方法都使用内部锁
    {
        //对accounts执行操作
        ...
    }
}

(5)volatile(只想对某个变量同步访问,且只有赋值和查询操作):如果一个变量的值被一个线程设置,却被另一个线程查询,此时可以将该变量设置为volatile类型,如:private volatile boolean v;
(6)也可以使用final,如:final Map<String,Double> accounts = new HashMap<>();其他线程会在构造函数完成构造之后才看到accounts变量
(7)java.util.concurrent.atomic包中提供了很多方法来保证一些操作的原子性,而不必使用锁。

(8)线程局部变量:如果想要为 每个线程 构造一个实例,可以使用ThreadLocal

public static final ThreadLocal<SimpleDateFormat> dateFormat= new ThreadLocal<SimpleDateFormat>(){
    protected SimpleDateFormat initialValue(){
        return new SimpleDateFormat("yyyy-MM-dd");
    }
};
String dateStamp = dateFormat.get().format(new Date());  //在一个线程中首次调用get方法时,会用initialValue方法来初始化

(9**)锁测试与超时**,使用tryLock来测试锁,尽量使用带有超时参数的tryLock方法

if(myLock.tryLock())   //如果无法获取锁则返回false,然后执行else语句块。tryLock还有一个带有超时参数的方法:如果无法获取到锁,则等待超时参数所设置的时间(如果在等待过程中收到中断请求则会抛出InterruptedException异常),如果依旧无法获取到锁,则返回false
    {
        myLock.lock();
        try{...}
        finally{myLock.unLock();}
    }
    else
    {
        ...
    }

注:[1]lock和tryLock的区别,如果无法获取到锁,lock方法将会导致线程被禁用而进入休眠状态,而tryLock则会立刻返回false(如果可以获取锁则立即获取),并执行else语句块
[2]另外还有一个lockInterruptibly,相当于超时设为无限的tryLock方法,如果在等待过程中受到其他线程的中断请求,则会抛出InterruptedException;如果已经中断,再次调用lockInterruptibly也会抛出该异常
[3]lock方法并不理会中断线程的请求,带超时参数的tryLock和lockInterruptibly则会响应中断(因为他们都会等待)。

(10)读写锁ReentrantReadWriteLock:如果很多线程从一个数据结构读取数据,而有很少线程修改该数据结构的数据的话,这个类是非常有用的,它将会允许对读者线程共享访问,而写者进程依旧互斥访问。
使用方法:

private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();
public double getTotalBalance()   //对所有的获取方法加读锁
{
    readLcok.lock();
    try{...}
    finally
    {
        readLcok.unLock();
    }
}
public void transfer(...)   //对所有的修改方法加写锁
{
    writeLock.lock();
    try{...}
    finally
    {
        writeLock.unLock();
    }
}

(11)stop被弃用是因为该方法会直接终止其他线程的行为,从而破坏对象。而suspend方法被弃用是因为很容易造成死锁,如一个线程调用suspend方法将另一个线程挂起,此时被挂起的线程拥有的锁是不可用的(因为该锁被挂起的线程占用),如果调用suspend的线程恰好需要这个锁,那么将会导致死锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值