黑马程序员_java中的线程

本文详细介绍了Java中线程的概念、创建方式、生命周期及其管理,包括线程的互斥与同步、等待与唤醒机制等内容。

------- <a href="http://www.itheima.com" target="blank">android培训</a>、<a href="http://www.itheima.com" target="blank">java培训</a>、期待与您交流! ----------


在JVM上执行的一个Java程序,是操作系统中的一个进程。同一个Java程序中的各个并发执行的代码片断,是操作系统中的线程。每个线程均设计成具有部分程序功能,并且能与其他线程并发执行。这种能力称为多线程(multithreading)。


一、线程概述:

线程可以认为是由三部分组成的:
(1)虚拟CPU,封装在java.lang.Thread类中,它控制着整个线程的运行;
(2)执行的代码,传递给Thread类,由Thread类控制顺序执行;
(3)处理的数据,传递给Thread类,是在代码执行过程中所要处理的数据。


二、创建线程的方式:

(1)继承Thread类:

Thread类的构造方法:
public Thread( ThreadGroup group, Runnable target, String name)
group:线程所属的线程组;
target:线程体run()方法所在的对象,必须实现接口Runnable ;
        public interface Runnable{
                public abstract void run();
        }
name:线程的名称。


注:类Thread本身也实现了接口Runnable,因此,上述构造方法中各参数都可以为null,如

public Thread ();
public Thread (String name);
public Thread (Runnable target);
public Thread (Runnable target,String name);


Thread类常用方法:

Static native Thread currentThread();//获当前在运行的线程;
final String getName();//以字符串形式返回线程的名称;
final int getPriority();//返回线程的优先级;
void start() ;//启动线程对象;
void sleep (long millis);//使线程暂时休眠millis毫秒,让低优先级的线程暂时获取处理器资源
void run();//对线程来说run方法的功能等同于程序中的main方法的作用。在一个线程被建立并初始化以后,Java的运行时系统就自动调用run方法,正是通过run方法才使得建立线程的目的得以实现。 


Thread类本身只是线程的虚拟CPU,线程所执行的代码(或者说线程所要完成的功能)是通过run方法(包含在一个特定的对象中)来完成的,方法run()称为线程体。run方法的结束即意味着线程的结束。通常,run方法是一个循环。


用Thread类创建线程的具体方法:

①创建一个类扩展Thread类;
重写Thread类的run()方法,在此方法中写要在这个线程中要执行的代码;

用关键字new创建所定义的线程类的一个对象;

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


(2)继承Runnable接口步骤如下:

①自定义类实现Runnable接口;
定义方法run();
使用Thread类的另一构造函数:
        Thread(Runnable, String) ;
    用实现了Runnable接口的类的对象中所定义的run()方法, 来覆盖新创建的线程对象的run()方法;
使用start()启动线程。


三、线程的生命周期:

线程有五个状态:创建、可运行、运行中、阻塞、死亡。


控制线程生命周期的方法:

1.创建状态(new Thread) :执行下列语句时,线程就处于创建状态:

   Thread myThread = new MyThreadClass( ); //当一个线程处于创建状态时,它仅仅是一个空的线程对象,系统不为它分配资源。

2.运行状态( Runnable )并非运行中状态(Running)

   Thread myThread = new MyThreadClass( );

   myThread. start( ) ;
3.阻塞状态(Blocked):进入不可运行状态的原因有如下几条:

   (1) 调用了sleep()方法;

   (2) 调用了suspend()方法;

   (3) 为等候一个条件变量,线程调用wait()方法;

   (4) 输入输出流中发生线程阻塞;
4. 死亡状态(Dead):线程的终止一般可通过两种方法实现:自然撤消(线程执行完)或是被停止(调用stop()方法)。

   注:不推荐通过调用stop()来终止线程的执行,而是让线程执行完。


sleep方法:

sleep方法使线程处于睡眠状态,其格式为:
public static void sleep(long millis)  throws InterruptedException
时间一到,线程就会被唤醒,且收到InterruptedException异常。sleep()方法须放在try{}catch()中。
当一个线程所睡眠的时间一到或者虽然睡眠时间未到,但其它线程调用了该线程的interrupt()方法,该线程都将立即唤醒,返回运行态且收到
InterruptedException异常,从而线程继续往下执行。


四、线程的互斥与同步

线程的互斥:

通常,一些同时运行的线程需要共享数据。如果线程只是读取这个共享的数据,则不必阻止多个线程同时访问该数据。但是,在多个线程共享同一个对象,并且一个或多个线程会修改该对象时,每个线程就必须要考虑其它与它一起共享数据的线程的状态与行为,否则就不能保证共享数据的一致性,从而也就不能保证程序的正确性。


在Java语言中,引入了“对象互斥锁”的概念(又称为监视器、管程)来实现不同线程对共享数据操作的同步。这个标记用来保证在任一时刻,只能有一个线程访问该对象。即,“对象互斥锁”阻止多个线程同时访问同一共享资源。
有两种方法可以实现“对象互斥锁”:
(1)用关键字volatile来声明一个共享数据(变量);
(2)用关键字synchronized来声明一个操作共享数据的方法或一段代码。
格式1:
synchronized(临界区对象){
  //访问临界区对象的“关键”代码片断,又称同步代码块
}
格式2:
同步化方法。在方法的前面加上synchronized。如:
public synchronized void add() {…………}
对于实例方法,这种格式其实是第一种格式的简化写法:
public void add(){
  synchronized(this){…………}
}


线程间的同步

线程之间需要相互协作,共同完成一些任务这就是线程之间的同步。
如经典的生产者和消费者协同问题:
生产者线程负责将数据写入大小固定的缓冲区,而消费者线程则从缓冲区取出数据来使用。生产者线程必须在缓冲区未满时,才可写入数据,而消费者线程只有在缓冲区非空时才能取走数据。这样就要求生产者在缓冲区已满的情形下必须等待,而一旦缓冲区可写数据时,又将其唤醒继续写入数据;同样地,消费者也有一个等待和唤醒的过程。由此可见,线程的等待和唤醒也是一种实现线程间同步的方法。


出于代码的安全性和健壮性的考虑,java放弃了使用原来的suspend、resume方法来处理线程的挂起等待和唤醒继续,取而代之则是从Object类继承而来的:
wait() ;//这个方法可以使线程一直等待,直到其他线程调用notify(或notifyAll)将其唤醒。
notify() ;//唤醒一个等待同一个管程锁的线程,如果同一管程锁下有多个线程处于睡眠等待状态,则随机地挑出一个等待线程将其唤醒。
notifyAll();//唤醒所有等待同一管程锁的线程,这些唤醒后的线程同样也要拥有管程才能继续执行。
注:这三个方法必须在synchronized代码块中调用,否则会出现IllegalMonitorStateException异常。


死锁问题:

若一个Java程序的所有线程都因为申请不到它们所需要的资源而全部进入“阻塞”状态时,该Java程序将被挂起,程序再不能继续前进,这种现象称为死锁。当不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,从而形成了死锁。若不配套使用使wait()/notifyAll(),就很容易造成死锁。


五、守护线程

Java程序中可以包含守护线程和非守护线程。
守护线程 : 是专门为其它线程提供服务的线程,又称为服务线程。如Java的垃圾收集机制的某些实现就使用了守护线程。
非守护线程 : 包括常规的用户线程或诸如用于处理GUI事件的事件调度线程。
守护线程与其它线程的区别:守护线程不会阻止程序的终止,如果守护线程是唯一运行着的线程,程序会自动退出。
main()所在的主线程是否运行结束取决于用户线程。
通过调用线程的setDaemon方法可以实现用户线程与守护线程之间的转换。

public void setDaemon(boolean on)
当on为true时,将线程转成守护线程,on为false时转成用户线程。



### 关于Java线程的学习笔记 #### 1. 实现多线程的方式 在Java中,可以通过继承`Thread`类或实现`Runnable`接口来创建多线程程序[^1]。 对于通过继承`Thread`类的方法: ```java class MyThread extends Thread { public void run() { System.out.println("MyThread running"); } } ``` 而采用实现`Runnable`接口的形式则更为灵活,因为这不会占用已有的继承机会,并允许共享同一个对象实例给多个线程使用: ```java class MyRunnable implements Runnable { @Override public void run() { System.out.println("MyRunnable running"); } } // 使用方式如下: new Thread(new MyRunnable()).start(); ``` #### 2. 创建并启动线程 一旦定义好了线程体之后,就可以利用`Thread`的构造函数传入该线程体,并调用其`start()`方法开启新线程执行任务。需要注意的是应该避免直接调用`run()`方法,因为它会在当前线程内顺序执行而不是新开一个独立的工作单元。 ```java public class Main { public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.start(); // 正确做法 // 错误示范:thread.run(); } } ``` #### 3. 线程同步机制 当涉及到资源共享时,可能会遇到竞态条件等问题。为了确保数据一致性,在访问临界区资源前应当加锁保护。可以借助`synchronized`关键字或者更高级别的并发工具包如`ReentrantLock`来进行操作控制。 ```java synchronized (lockObject) { // 对共享变量的操作... } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值