您的位置:首页 > 编程语言 > Java开发

Java多线程基本知识小结

2015-07-28 22:33 204 查看
一 线程概述

单线程的程序如同只雇佣一个服务员的餐厅,他必须做完一件事情后才可以做下一件事情;

多线程的程序如同雇佣多个服务员的餐厅,他们可以同时做多件事情。

并发性:同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。

并行性:同一时刻有多条指令在多个处理器(CPU)上同时执行;

操作系统可以同时执行多个任务,每个任务就是进程;进程可以同时执行多个任务,每个任务就是线程。

二 线程的创建和启动

1. 继承Thread类创建线程类

⑴ 定义Thread类的子类,重写run()方法(该run()方法称为线程执行体)。

⑵ 创建Thread子类的实例。

⑶ 调用线程对象的start()方法启动该线程。

public class ThreadName extends Thread {

public void run() {

// do something here

}

}

2. 实现Runnable接口创建线程类(可共享)

⑴ 定义Runnable接口的实现类,重写该接口的run()方法。

⑵ 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

public class ThreadName implements Runnable {

public void run() {

// TODO Auto-generated method stub

}

public void main(String args[]) {

Thread thread = new Thread(new ThreadName());

thread.start();

}

}

3. 使用Callable和Future创建线程

Java5提供了Callable接口(Runnable接口的增强版,非Runnable的子接口,因此不能直接作为Thread的Target),它提供一个call()方法作为线程执行体。

① call()方法可以有返回值。

② call()方法可以声明抛出异常。

Java5提供了Future接口代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现Runnable接口(因此可作为Thread类的target)。

所以创建并启动有返回值的线程的步骤如下。

⑴ 创建Callable接口的实现类,实现call()方法(线程执行体,有返回值)。

⑵ 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,并封装了该Callable对象的call()方法的返回值。

⑶ 使用FutureTask对象作为Thread对象的target创建并启动新线程。

⑷ 调用FutureTask对象的get()方法获取子线程执行结束后的返回值(注意该方法将导致主线程被阻塞,直到call()方法结束后并返回为止)。

创建线程的三种方式对比

㈠ 采用实现Runnable、Callable接口的方式创建多线程-----

ˉ线程类只是实现了Runnable接口或Callable接口,还可以继承其他类

ˉ多线程可共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现面向对象的思想

ˉ劣势:稍复杂,访问当前线程使用Thread.currentThread()方法

㈡ 采用继承Thread类的方式创建多线程-----

ˉ劣势:线程类已经继承Thread类,不能再继承其他父类

线程的生命周期



1. 线程的运行

启动线程使用start()方法,而不是run()方法(如果直接调用线程对象的run()方法,则立即会被执行,系统会把线程对象当成一个普通对象,而run()方法也是普通方法,非线程执行体)

另外要注意,多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。

控制线程的一些便捷的工具方法

join():

当在某个程序执行流中调用其他线程的join()方法时,此执行流的线程将被阻塞,直到被join()方法加入的线程执行完为止。

Daemon 线程:

必须在线程启动前(start())调用setDaemon()方法,将指定线程设置成后台线程。一个后台线程所创建的的任何线程都将被自动设置成后台线程。

sleep():

让当前正在执行的线程暂停一段时间。

yield():

让当前正在执行的线程暂停,但它不会阻塞该线程,只是将该线程转入就绪状态(可运行)。只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。

改变线程的优先级:

Thread类提供了setPriority()、getPriority()方法来设置和返回指定线程的优先级。

为了具有更好的可移植性,可以使用Thread类的如下3个静态常量:

MAX_PRIORITY:值为10

MIN_PRIORITY:值为1

NORM_PRIORITY:值为5

线程同步

㈠ 线程代码块

synchronized(obj) {

// some codes here

}

//同步代码块结束,该线程释放同步锁

㈡ 同步方法

public synchronized void method() {

// some codes here

}

㈢ 释放同步监视器的锁定

ˉ当前线程的同步方法、同步代码块执行结束,释放。

ˉ当前线程在同步方法、同步代码块中遇到break、return终止继续执行,释放。

ˉ当前线程在同步方法、同步代码块中出现未处理的Error或Exception,释放。

ˉ当前线程在同步方法、同步代码块,执行同步监视器的wait()方法,释放。

如下情况,线程不会释放同步监视器

ˉ线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程执行体,不释放。

ˉ线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,不释放。

㈣ 同步锁(Lock)

Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock实现允许更灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象。

某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock、ReadWriteLock是Java5新提供的两个跟接口,并为Lock提供了ReentrantLock(可重入锁)实现类;为ReadWriteLock提供了ReentrantWriteLock实现类。

Class X

{

//定义锁对象

private final ReentrantLock lock =new ReentrantLock();

//..

//定义需要保证线程安全的方法

public void m()

{

// 加锁

lock.lock();

try

{

// 需要保证线程安全的代码

}

// 使用finally块来保证释放锁

finally

{

lock.unlock();

}

}

}

㈤ 死锁

只有当下列四个条件同时满足时,才会发生死锁:

1. 互斥条件:线程使用的资源中至少又一个是不能共享的

2. 至少有一个进程持有一个资源,并且他在等待获取一个当前被别的进程持有的资源。

3. 资源不能被进程抢占。所有的进程必须把资源释放作为普通事件。

4. 必须有循环等待,即,一个线程等待其他线程持有的资源,后者又在等待另一个进程持有的资源,这样一直下去,直到又一个进程在等待第一个进程持有的资源,使得大家都被锁住。

要发生死锁,必须这四个条件同时满足,所以,只要破坏其中任意一个,就可以打破死锁。其中第四个条件是最容易被打破的。

线程通信

㈠ 传统的线程通信

当线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,可以通过一些机制来保证线程协调运行。

Object类提供的wait()、notify()、notifyAll()3个方法。

wait():

导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。

notify():

唤醒在此同步监视器上等待的单个线程。

notifyAll():

唤醒在此同步监视器上等待的所有线程。

㈡ 使用Condition控制线程通信

Condition实例被绑定在一个Lock对象上。通过调用Lock对象的newCondition()方法即可。Condition类提供如下3个方法。

await():

类似于隐式同步监视器上的wait()方法。

signal():

唤醒在此Lock对象上等待的单个线程(任意)。

signalAll():

唤醒在此Lock对象上等待的所有线程。

Class X

{

//定义锁对象

private final Lock lock = newReentrantLock();

private final Condition cond =lock.newCondition();

//..

//定义需要保证线程安全的方法

public void m()

{

// 加锁

lock.lock();

try

{

// 需要保证线程安全的代码

cond.await();

///////

cond.signalAll();

}

// 使用finally块来保证释放锁

finally

{

lock.unlock();

}

}

}

㈢ 使用阻塞队列(BlockingQueue)控制线程通信

BlockingQueue提供如下两个支持阻塞的方法。

ˉput(E e):尝试把E元素放入BlockingQueue中,如果该队列的元素已满,则阻塞该线程。

ˉtake():尝试从BlockingQueue的头部取出元素,如果该队列已空,则阻塞该线程。

线程组和未处理的异常

ThreadGroup类、Thread.UncaughtExceptionHandler对象

线程池

线程相关类

㈠ ThreadLocal类

ˉT get():返回此线程局部变量中当前线程副本中的值

ˉvoid remove():删除此线程局部变量中当前线程的值

ˉvoid set(T value):设置此线程局部变量中当前线程副本中的值

㈡ 包装线程不安全的集合

ˉ使用Collections提供静态方法把这些集合包装成线程安全的集合。

ˉ如HashMap m = Collections.synchronizedMap(new HashMap());

㈢ 线程安全的集合类
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: