多线程

本总结参考java核心技术中文版 第7版

为什么要多任务

传统的单线程程序是一个程序执行完后再执行下一个,同一个时间段就只能干一件事情。不言而喻效率不高,因为普通的程序很难让cpu全负荷运转起来,cpu大量的时间在闲置。一个程序也很难满足用户的需求。

多任务的解决方案

我们考虑是否程序能同时执行多个程序,最终的解决方案是把cpu调用程序的物理过程分成若干小的时间片断,这样就可以可已在每个时间片断上调用不同的程序。从电脑的内部看cpu的使用效率大大提高了。从用户角度根本感觉不到这些及其细小的时间片断的切换,制造出了一种可同时进行多任务处理的假象。举例:XP操作系统就是一个最典型的多线程程序,如:一边听歌一边编程。

注:多核处理器情况暂不讨论,原理类似。

在这样的方式实现多多任务处理,一个关键问题是操作系统是如何中断程序的。分为两种方式:抢占式多任务协作式多任务

线程与进程和多任务有什么关系

要想实现一个多任务程序,线程和进程这两个概念非常关键,他们也是我们程序员真正可以控制的地方。

进程是指在系统中正在运行的一个应用程序;线程是操作系统分配cpu时间片资源的基本单元,或者说进程之内独立执行的一个单元。对于操作系统而言,其调度单元是线程。一个进程至少包括一个线程,通常将该线程称为主线程。一个进程从主线程的执行开始进而创建一个或多个附加线程,就是所谓基于多线程的多任务。

java核心技术第七版谈到多线程和多进程的本质区别,是每个进程有它自己的变量的完备集,线程则共享相同的数据。

java多线程编程

默认情况下,我们编写的java程序是单线程的,也就是说代码是一条接着一条执行,直到程序结束,不能同时干多件事。通过上面的原理,我们要实现多任务就要把程序改成多线程的。

java中线程由Thread类表示,一个线程就是一个Thread对象。调用Thread对象的start方法就开始了一个线程。

 下面是在主线程中运行另一个线程的简单过程:

1)将新线程中要执行的任务放到一个实现了Runnable接口的类的run方法中,这个run方法就是Runnalbe接口的唯一的一个方法。



public interface Runnable

{

    void run();

)

我们只要这样一个实现类

Class MyTask implements Runnable

{

    public void run()

    {

        task code

    }

}

2)创建任务类对象:

Runnable r = new MyTask();

3)由Runnable对象构造一个Thread对象;

Thread t = new Thread(r);

4)启动线程

t.start();

这通过实现Runnable接口生成的多线程程序,还可以通过继承Thread类覆盖run方法来实现。不过这个方法现在不建议使用了。

我们看下这个过程中,我用到了那些java API。 java.lang.Thread 1.0

  • Thread(Runnable target)
构造一个新的线程
  • void start()
启动这个线程,调用自己的run()方法。这个方法将立即返回,并且新线程将并发运行。
  • void run()
调用关联Runable的run方法。

 java.lang.Runnable 1.0

  • void run()

必须重载这个方法,并且在这个方法中添加执行相应任务的相关代码。

中断线程

正常情况线程(除主线程外)在它的run方法返回时终止。我们可以同一些方法让线程非正常终止,让线程进入到死亡状态。

线程状态

线程有以下4种状态

New (新生)

Runnable (可运行)

Blocked (被阻塞)

Dead (死亡)

    1)新生线程

    当用new操作符创建一个线程时,线程还没有开始运行。

    2)可运行线程

    一旦调用了start方法,该线程就成为可运行的。实际上线程在等待时间片运行和正在一个时间片上运行,这两种情况都称作是可运行线程。

    3)被阻塞线程

    当发生以下任意情况,线程进入阻塞状态:

  •     线程通过调用sleep方法进入睡眠状态
  •     线程调用一个在I/O上被阻塞的操作
  •     线程试图得到一个锁,而该锁正被其他线程持有
  •     线程等待某个触发条件

    4)死线程

    有两个原因会导致线程死亡:

  •     因为run方法正常退出而自然死亡;
  •     因为一个未捕获的异常终止了run方法而使线程猝死。

线程属性

线程属性包括:线程优先级、守护线程、线程组、以及未捕获异常处理器这个几方面内容。

同步

在实际的多线程应用中,通常会由两个或多个线程需要对相同的对象进行共享访问。这将会产生被腐蚀的对象。这种情况也通称竞争条件。

    1)竞争条件产生的原因

         一条语句编译成机器语言可能变成多条,并且不是原子操作。

    2)锁对象

        JDK5.0开始,有两种机制来保护代码块不受并行访问的干扰。synchronized关键字和ReentrantLock类。

//用ReentrantLock保护代码块的基本结构如下:

myLock.lock();  //a ReentrantLock object

try

{

    critical section

}

finally

{

    myLock.unlock(); //make sure the lock is unlocked even if an exception is thrown

}

    3)条件对象

        条件对象是用来控制已经获得锁,并且进入临界区的线程。

public void transfer(int from, int to , int amout)

{

    bamlLock.lock();

    try

    {

        while(accounts[from] < amout)

            sufficientFunds.await();

        //transfer funds

        ....

       sufficientFunds.signalAll();

    }finally

    {

        bankLock.unlock();

    }

}



private Condition ufficientFunds = bankLock.newCondition();

java.util.concurrent.locks.Lock 5.0

  • Condition newCondition()

              返回与该锁相关的一个条件对象

java.util.concurrent.locks.Condition 5.0

  • void await()

              把该线程放到条件的等待集中(就是等待池)

  • void signalAll()

               解除该条件的等待集中所有线程的阻塞状态。

  • void signal()

               在该条件的等待集中随机选择一个线程,解除其阻塞状态

 

synchronized关键字 class Bank {     public synchronized void transfer(int from, int to, int amount) throws InterruptedException     {         while(accouts[from] < amount)             wait();         accounts[from] -= amount;         accounts[to] += amount;         notifyAll();     }     public synchronized double getTotalBalance(){...}     private double accounts[]; }

可以看到使用synchronized关键字来编码要简洁得多。当然,为了理解代码,你必须知道每个对象都有一个隐式得锁,并且每个锁都有一个隐

式条件。

在编码中使用那一种?

同步块

如果要处理遗留代码,需要知道内置得同步原语。别忘了每个对象都有一个锁。实际上线程有两种方法可以获得锁:1、调用一个同步方法。2

进入一个同步块。

synchronized(obj) {     critical section }

Volatile域

volatile关键字为对一个实例的域的同步访问提供了一个免锁(lock-free)机制。如果你把域声名为volatile,那么编辑器和虚拟机就知道

该域可能会被另一个线程并发更新。

例如:假设一个对象一个布尔标记,由一个线程设置它的值,而由另一个线程来查询它,那么有以下两个方法:

    1)使用锁     public synchronized boolean isDone() {return done;}     private boolean done;     2)将域声名为volatile     public boolean isDone(){return done;}     private volatile boolean done;

借着介绍Volatile关键字的作用,总结下一个域都有那些情况是线程安全的:    域是volatile的    域是final的,并且在构造器调用完成后被访问。    对域的访问有锁保护

死锁

学习多线程就必须要明白什么死锁现象,这是因为锁和条件不能解决多线程中的所有问题。

我觉得死锁的根源就是调用obj.wait()这样的方法造成的,我就不明白为什么不满足条件就退出就完了,为什么一定要等待。书里没说明白。

锁测试和超时 没弄明白

读写锁 实际是对ReentrantLock的细化,ReentrantReadWriteLock类有两个细化了的锁 ReentrantReadWriteLock.readLock() ReentrantReadWriteLock.writeLock()

阻塞队列 阻塞队列本质还是一个队列,只不过为了用在两个线程间通讯加上了些阻塞的特性。 BlockingQueue LinkedBlockingQueue ArrayBlockingQueue PriorityBlockingQueue

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值