黑马程序员——Java多线程

本文详细介绍了Java多线程的基本概念、线程的创建与启动方法、线程生命周期、控制线程的方式以及线程同步机制。通过实例演示了如何使用Thread类、Runnable接口、Callable接口和FutureTask来创建和管理线程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

一、理解多线程  

多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。   

线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单。  
多个线程的执行是并发的,也就是在逻辑上“同时”,而不管是否是物理上的“同时”。如果系统只有一个CPU,那么真正的“同时”是不可能的,但是由于CPU的速度非常快,用户感觉不到其中的区别,因此我们也不用关心它,只需要设想各个线程是同时执行即可。  
多线程和传统的单线程在程序设计上最大的区别在于,由于各个线程的控制流彼此独立,使得各个线程之间的代码是乱序执行的,由此带来的线程调度,同步等问题,将在以后探讨。

一般而言,进程包含如下3个特征:

独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。

动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念。进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。

并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响的。

二、线程的创建和启动

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上是执行一段程序流(一段顺序执行的代码)。Java使用线程执行体来代表程序流。

1.继承Thread类创建线程类

通过继承Thread类来创建并启动多线程的步骤如下:

(1)定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务。

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

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

下面是继承Thread类来创建并启动线程的例子:

public class FirstThread extends Thread{

private int i;

public void run(){
for( ; i<100; i++){
System.out.println(this.getName()+" ..."+i);
}
}

public static void main(String[] args){
for(int i=0; i<100; i++){
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20){
new FirstThread().start();
new FirstThread().start();
}
}
}


}

2.实现Runnable接口创建线程类

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

(1)定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

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

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

下面是实现Runnable接口来创建并启动线程的例子:

public class SecondThread implements Runnable{


private int i;

@Override
public void run() {
for( ; i<100; i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}

public static void main(String[] args){
for(int i=0 ; i<100; i++){
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20){
SecondThread st = new SecondThread();
new Thread(st, "新线程1").start();
new Thread(st, "新线程2").start();
}
}
}
}

3.使用Callable和Future创建线程类

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

(1)创建Callble接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值。

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

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

(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

下面程序通过实习Callble接口来实现线程类,并启动该线程:

public class ThirdThread implements Callable<Integer>{


@Override
public Integer call(){
int i=0;
for(; i<100; i++){
System.out.println(Thread.currentThread().getName()+"的循环变量i的值:"+i);
}
return i;
}

public static void main(String[] args){
ThirdThread rt = new ThirdThread();
FutureTask<Integer> task = new FutureTask<Integer>(rt);
for(int i=0; i<100; i++){
System.out.println(Thread.currentThread().getName()+"的循环变量i的值:"+i);
if(i==20){
new Thread(task, "有返回值的线程").start();
}
}
try{
System.out.println("子线程的返回值:"+task.get());
}catch(Exception ex){
ex.printStackTrace();
}
}
}

三、线程的生命周期

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程生命周期中,它要经过新建、就绪、运行、阻塞和死亡5种状态。

启动线程使用start()方法,而不是run()方法!永远不要调用线程对象的run()方法!调用start()方法来启动线程,系统会把该run()方法当成线程执行体来处理;但如果直接调用线程对象的run()方法,则run()方法立即就会被执行,而且在run()方法返回之前其它线程无法并发执行。

当发生如下情况时,线程将会进入阻塞状态:

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

2.线程调用了一个阻塞IO方法,在该方法返回之前,该线程被阻塞。

3.线程视图获得一个同步监视器,但该同步监视器正被其它线程所持有。

4.线程在等待某个通知。

5.程序调用了线程的suspend()方法将该线程挂起。

针对上面几种情况,当发生如下特定的情况时可以解除上面的阻塞,让该线程重新进入就绪状态:

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

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

3.线程成功的获得了视图取得的同步监视器。

4.线程正在等待某个通知时,其它线程发出了一个通知。

5.处于挂起状态的线程被调用了resume()恢复方法。

线程会以如下3种方式结束,结束后就处于死亡状态。

1.run()或call()方法执行完成,线程正常结束。

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

3.直接调用该线程的stop()方法来结束该线程——该方法容易导致死锁。

四、控制线程

1.join线程

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

如下程序:

public class JoinThread extends Thread{

public JoinThread(String name){
super(name);
}

public void run(){
for(int i=0; i<100; i++){
System.out.println(this.getName()+" "+i);
}
}

public static void main(String[] args) throws Exception{
new JoinThread("新线程").start();
for(int i=0; i<100; i++){
if(i==20){
JoinThread jt = new JoinThread("被Join的线程");
jt.start();
jt.join();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}

2.线程睡眠:sleep

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

如下程序:

public class SleepTest {

public static void main(String[] args) throws Exception{
for(int i=0; i<10; i++){
System.out.println("当前时间:"+new Date());
Thread.sleep(10000);
}
}

}

3.线程让步:yield

yield()方法可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态。

如下程序:

public class YieldTest extends Thread{

public YieldTest(String name){
super(name);
}

public void run(){
for(int i=0; i<50; i++){
System.out.println(this.getName()+" "+i);
if(i==20){
System.out.println(this.getName()+"让步");
Thread.yield();
}
}
}

public static void main(String[] args) throws Exception{
YieldTest yt1 = new YieldTest("高级");
//yt1.setPriority(Thread.MAX_PRIORITY);
yt1.start();
YieldTest yt2 = new YieldTest("低级");
//yt2.setPriority(Thread.MIN_PRIORITY);
yt2.start();
}

}

4.改变线程优先级

每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行计划,而优先级低的线程则获得较少的执行机会。

每个线程默认的优先级与创建它的父线程的优先级相同。

Thread类提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级,其参数范围是1~10之间,也可以是Thread类的3个静态常量。

如下代码片段:

Thread.currentThread().setPriority(6);

五、线程同步

1.同步代码块,语法格式如下:

synchronized(obj)

{

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

}

2.同步方法

与同步代码块对应,Java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字来修饰某个方法,则该方法称为同步方法。语法格式如下:

public synchronized void methodName()

{

//执行语句

}

3.同步锁

class X

{

   private final ReentrantLock lock = new ReentrantLock();

  public void m()

  {

    lock.lock();

   try{

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

  }catch(Exception e){

 }

  finally{

   lock.unlock();

 }

  }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值