视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
Java多线程总结
2025-10-02 19:21:12 责编:小OO
文档
1.线程的创建与启动

在Java中,多线程的实现有两种方式:

继承java.lang.Thread类

实现java.lang.Runnable接口

1.1继承Thread类创建线程

继承Thread类创建并启动线程的步骤:

1、定义Thread的子类,并重写该类的run()方法,run()方法的方法体就表示线程需要完成的任务。run()被称为线程执行体。

2、创建Thread的子类的实例,即创建线程对象。

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

1.2实现Runnable接口创建线程

实现Runnable接口创建并启动线程的步骤:

1、定义Runnable接口的实现类,并重写该接口的run()方法,run()方法的方法体就表示线程需要完成的任务。run()被称为线程执行体。

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

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

PS:使用Runnable创建的多个线程对象可以共享Runnable的实现类的实例属性。

2.线程的生命周期

在线程的生命周期中有五种状态。

2.1新建状态和就绪状态

当程序使用new关键字创建一个线程之后,该线程就处于新建状态。

当线程对象调用了start()方法之后,该线程就处于就绪状态。处于就绪状态的线程并没有开始运行,只是可以运行而已,至于该线程何时开始运行就取决于JVM里线程调度器的调度。

PS:不能直接调用线程的run()方法,不然就变成了普通方法的调用,而不是执行线程执行体。

2.2运行状态和阻塞状态

处于就绪状态的线程获得了CPU,开始执行run()方法的方法体,则该线程处于运行状态。

当一个线程开始运行后,在某个时刻失去了CPU,则该线程处于阻塞状态。

当发生如下情况时,线程会进入阻塞状态(从运行状态转入阻塞状态):

1、线程调用sleep()方法主动放弃占用的处理器资源

2、线程调用了一个阻塞式IO方法

3、线程试图获取一个同步监视器,但是该同步监视器正被其他线程所拥有。

4、线程在等待某个通知

5、线程调用suspend()方法将该线程挂起。这个方法容易造成死锁。

针对上面几种情况,当发生如下情况时可以解除阻塞(从阻塞状态转入就绪状态):

1、调用sleep()方法的线程过了指定的时间

2、线程调用的阻塞式IO方法已经返回

3、线程成功获取它试图获取同步监视器

4、线程正在等待某个通知时,其他线程发出了一个通知

5、处于挂起状态的线程调用了resume()方法

PS:线程调用yield()方法可以从运行状态转入就绪状态

2.3死亡状态

线程会以三种方式结束,结束后就处于死亡状态:

1、线程的run()执行完成,线程正常结束

2、线程抛出一个未捕获的Exception或Error

3、线程调用stop()方法来结束该线程,该方法容易造成死锁

为了测试某个线程是否死亡,可以调用线程对象的isAlive()方法,当线程处于就绪,运行,阻塞三种状态时,返回true,当线程处于新建,死亡两种状态时,返回false。

3.线程控制 

Java的线程提供一些便捷的工具,通过这些工具可以很好地控制线程的执行。

3.1join线程

Thread提供让一个线程等待另一个线程完成的方法——join()方法。当在某个线程的执行过程中调用另一个线程的join()方法时,调用线程将被阻塞,直到被join()方法的线程执行完为止。

3.2后台线程

有一种线程是在后台运行的,它的任务是为其他线程提供服务,这种线程叫做后台线程(Daemon Thread),又称为守护线程或精灵线程。

后台线程有个十分重要的特点:如果所有的前台线程死亡,那么后台线程自动死亡。

调用线程对象的setDaemon(true)方法可将指定线程设置为后台线程。

Thread类还提供一个isDaemon()方法,用来判断指定线程是不是后台线程,是后台线程就返回true,否则返回false。

主线程(main线程)默认是前台前程。

前台,后台线程有一个重要的特点:前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。

PS:setDaemon(true)设置线程为后台线程必须在start()方法之前调用。

3.3线程睡眠

如果需要让当前正在后执行的线程暂停一段时间,并进入阻塞状态,可以通过调用Thread类的静态sleep()方法实现。

3.4线程让步

yield()方法是一个和sleep()方法相似的方法,它也是Thread类的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是让该线程转入就绪状态。

当某个线程调用了yield()方法暂停后,只有优先级与该线程相同,或者优先级高于该线程的处于就绪状态的线程才会获得执行的机会。

3.5线程的优先级

每个线程都具有一定的优先级,优先级高的线程有更多的机会获得执行,优先级低的有更少的机会获得执行。

主线程(main线程)默认优先级是普通优先级,每个线程的默认优先级都与创建它的父线程相同。

Thread类提供setPriority(int newPriority)方法和getPriority()方法来设置和获取指定线程的优先级,其中setPriority(int newPriority)方法中的参数取值范围是1-10,也可以使用Thread类的三个静态常量

MAX_PRIORITY:值是10

MIN_PRIORITY:值是1

NORM_PRIORITY:值是5

PS:Java提供10个线程优先级,但不是所有的操作系统都支持这10个优先级,所以我们应该尽量避免直接有数字来设置优先级,应该多用三个静态常量来设置优先级,这样程序的移植行更好。

4.线程同步

4.1线程安全问题

多个线程同时访问一个对象时,可能会出现线程安全问题

4.2同步监视器

为了解决线程安全问题,Java的多线程支持引入了同步监视器。

4.2.1同步代码块

使用同步监视器的一个方法是同步代码块。

synchronized(obj){

...//此处代码就是同步代码块

}

synchronized后面圆括号里面的obj就是同步监视器,上面代码的含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。

任何时刻只能有一个线程获取对同步监视器的锁定。

4.2.2同步方法

使用同步监视器的一个方法是同步方法。

对同步方法而言,无须显示地指定同步监视器,同步方法的同步监视器是this,也就是同步方法本身。

public synchronized void play(){

...//同步方法体

}

使用同步方法可以非常方便地实现线程安全类,线程安全类具有以下特征:

1、该类的对象可以被多个线程安全访问

2、每个线程调用该对象的任意方法后将得到正确的结果

3、每个线程调用该对象的任意方法后,该对象依然保持合理状态

4.2.3释放同步监视器的锁定

释放同步监视器的锁定的方法:

1、当前线程的同步方法、同步代码块执行结束

2、当前线程的同步方法、同步代码块中遇到break、return终止了该方法、该代码块的继续执行

3、当前线程的同步方法、同步代码块中出现了未处理的Error或Exception,导致该方法、该代码块异常结束

4、当前线程执行同步方法、同步代码块时,程序执行了同步监视器对象的wait()方法,则当前线程暂停

如遇到以下几种情况,线程不会释放同步监视器

1、当前线程执行同步方法、同步代码块时,程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行

2、当前线程执行同步代码块时,其他线程调用该线程的suspend()方法将该线程挂起。这个方法容易造成死锁。

4.3同步锁

从Java5开始,Java提供了一种功能强大的心爱你策划给你同步机制——通过显示定义同步锁对象来实现同步,在这种机制下,同步锁使用Lock对象充当。

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

Lock是充值多线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁)。

Lock、ReadWriteLock是Java5提供的两个根接口,并为Lock提供了ReentrantLock(可重入锁)实现类,为ReadWriteLock提供了ReentrantReadWriteLock实现类。

使用ReentrantLock的常用格式如下

[java] view plaincopy

1.import java.util.concurrent.locks.ReentrantLock;  

2.  

3.public class LockTest {  

4.    private final ReentrantLock lock=new ReentrantLock();  

5.    public void play(){  

6.        lock.lock();  

7.        try{  

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

9.        }finally{  

10.            lock.unlock();  

11.        }  

12.    }  

13.}  

ReentrantLock锁具有可重入性,也就是说,一个线程可以对已经被加锁的ReentrantLock锁再次加锁,一段被锁保护的代码可以调用另一个被相同锁保护的方法。

4.4死锁

当两个线程互相等待对方释放同步监视器时,就会出现死锁。所以多线程编程时要采取措施避免死锁。

5.线程通信

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

5.1传统的线程通信

为了实现线程间的通信,可以借助Object类提供的wait()、notify()、notifyAll()3个方法,但这3个方法必须有同步监视器对象来调用。

1、对于使用synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步对象中直接调用这3个方法

2、对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用这3个方法

关于这3个方法的含义

1、wait()方法:导致当前线程等待,知道其他线程调用该同步监视器的notify()方法或notifyAll()方法

2、notify()方法:唤醒在此同步监视器上等待的单个线程

3、notifyAll()方法:唤醒在此同步监视器上等待的所有线程

5.2使用Condition控制线程通信

如果程序不使用synchronized关键字来保证同步,而直接使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也不能使用前面的3个方法来进行通信了。

当使用Lock对象来保证同步时,Java提供一个Condition类来保证协调,使用Condition可以让那些得到Lock对象却无法继续执行的线程释放Lock对象,Condition对象也可以唤醒其他处于等待的线程。

这时,Lock替代了同步方法或同步代码块,Condition替代了同步监视器的功能。

Condition实例被绑定在一个Lock对象上。要获取特定Lock实例的Condition实例,调用Lock对象的newCondition方法即可。Condition提供了如下3个方法。

1、await()方法:类似于隐式同步监视器上的wait()方法,导致当前小昵称等待,直到其他线程调用该Condition的signal()方法或signalAll()方法来唤醒该线程

2、signal()方法:唤醒在此Lock对象上等待的单个线程

3、signalAll()方法:唤醒在此Lock对象上等待的所有线程

5.3使用阻塞队列控制线程通信

Java5提供了一个BlockingQueue接口,虽然它也是Queue的子接口,但它的主要作用不是作容器,而是作为线程同步的工具。BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中加入元素时,如果队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果队列已空,则该线程被阻塞。程序的两个线程通过交替向BlockingQueue中放入元素、取出元素,就可以很好地控制线程的通信了。

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

1、put(E e)方法:尝试把E元素放入BlockingQueue中

2、take()方法:尝试从BlockingQueue的头部取出元素下载本文

显示全文
专题