一、引入线程
1.多线程和多进程的区别
(1)两者粒度不同,进程是由操作系统来管理,而线程则是在一个进程内
(2)每个进程是操作系统分配资源和处理器调度的基本单位,拥有独立的代码、内部数据和状态
而一个进程内的多线程只是处理器调度的基本单位,共享该进程的资源,线程间有可能相互影响
(3)线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担小
2.Thread类:Java的线程是通过java.lang.Thread类来实现,一个Thread对象代表一个线程
3.Runnable接口:只有一个方法run(),所有实现Runable接口的用户类都必须具体实现run()
当线程被调度并转入运行状态时,它所执行的就是run()方法中定义的操作,所以,一个实现Runnable接口的类实际上是定义一个新线程的操作
Thread类实现了Runnable接口
二、线程的实现
1.实现多线程:继承Thread类构造线程、实现Runnable接口构造线程
2.继承Thread类:必须覆写Thread类中的run()方法,run()方法中定义了线程要执行的代码
定义语法:
class 类名称 extends Thread { 属性: 方法: //覆写Thread类中的run方法 public void run() { 线程主题; } }继承Thread类创建和执行多线程步骤:
(1)定义一个类扩展Thread
(2)覆盖run() 方法,这个方法中实现线程中要执行的操作
(3)创建一个这个线程类的对象
(4)调用start() 方法启动线程对象
例:
class CountingThread extends Thread { private String name; public CountingThread(String name) { this.name = name; } public void run() { System.out.println("Thread start:" + this.name); for (int i = 0; i < 9; i++) { System.out.println(name + " run:" + (i + 1) + " "); } System.out.println("Thread finish:" + this.name); } } class ThreadDemo { public static void main(String[] args) { CountingThread thread1 = new CountingThread("Thread A"); CountingThread thread2 = new CountingThread("Thread B"); thread1.start(); thread2.start(); } }不能直接调用run() 方法的原因:当调用start() 方法时,系统启动线程,并分配虚拟CPU开始执行这个线程的run() 方法后,立即返回,而不是等到run()方法执行后返回
3.实现Runnable接口
(1)定义一个类实现Runnable接口:implements Runnable
(2)覆写其中的run() 方法
(3)创建Runnable 接口实现类的对象
(4)创建Thread类的对象(以Runnable子类对象为构造方法参数)
(5)用start() 方法启动线程
例:
class CountingThread implements Runnable { private String name; public CountingThread(String name) { this.name = name; } public void run() { System.out.println("Thread start:" + this.name); for(int i = 0; i < 10; i++) { System.out.println(name + " run: i = " + i); } System.out.println("Thread end:" + this.name); } } public class RunnableDemo { public static void main(String[] args) { CountingThread ct1 = new CountingThread("Thread A"); CountingThread ct2 = new CountingThread("Thread B"); Thread thread1 = new Thread(ct1); Thread thread2 = new Thread(ct2); thread1.start(); thread2.start(); } }4.两种实现方式的对比
(1)使用 Runnable 接口,可以避免由于Java的单继承带来的局限
(2)实现Runnable接口适合多个相同程序代码的线程去处理同一资源的情况
所以在开发中建议使用Runnable接口实现多线程
三、线程的调度
1.线程的生命周期
(1)新建状态:Thread myThread = new MyThreadClass()
当一个线程处于新建状态时,它仅仅是一个空的线程对象,系统不为它分配资源
(2)就绪状态:也成为可运行状态(Runnable),方法:start()
处于新建状态的线程被启动后,将进入线程队列排队等待CPU时间片
此时它已经具备了运行的条件,一旦轮到它来享用CPU资源,就可以脱离创建它的主线程独立开始自己的生命周期
(3)运行状态:当就绪状态的线程被调度并获得处理器资源时,便进入运行状态
当线程对象被调度执行时,它将自动调用本对象的 run() 方法,并顺序执行
(4)阻塞状态:一个正在执行的线程如果在某些特殊情况下,不能执行线程的状态,将让出CPU
(5)死亡状态:一个正常运行的线程完成了全部工作,即执行完了run()方法的最后一个语句
或线程被提前强制性执行,如通过执行 stop() 方法终止线程
2.线程的优先级:Java将线程的优先级分为10个等级,分别用1~10表示,数字越大表明级别越高
Thread类中静态常量:
定义 描述 表示的常量 public static final int MIN_PRIORITY 最低优先级 1 public static final int NORM_PRIORITY 普通优先级,默认优先级 5 public static final int MAX_PRIORITY 最高优先级 10 方法:setPriority(int p) :改变线程的优先级
getPriority():获得线程的优先级
优先级高的线程会获得较多的运行机会
四、线程的基本控制
1.线程睡眠:public static void sleep(long millis) throws InterruptedException
当前线程将睡眠millis毫秒,可以使优先级低的线程得到执行的机会
2.线程状态测试:
线程由start()方法启动后,直到其被终止之间的任何时刻,都处于活动状态
可以通过Thread中的 isAlive() 方法来获取线程是否处于活动状态
定义:public final boolean isAlive()
3.线程加入:public final void join() throws InterruptedException
有一个A线程正在运行,希望插入一个B线程,并要求B线程先执行完毕,然后再继续A的执行
4.线程礼让:public static void yield()
不能指定暂停多长时间,会将CPU的占有权交给具有相同优先级的线程,否则继续运行原线程
注意:只能让同优先级的线程有执行机会
5.守护线程:不是应用程序的核心部分,当一个应用程序的所有非守护线程终止运行时,即使仍然有守护线程在运行,应用程序也将终止
守护线程一般被用于在后台为其他线程提供服务
方法:public final boolean isDaemon() 判断一个线程是否是守护线程
public final void setDaemon() 将一个线程设为守护线程
五、多线程的同步与死锁
1.对象互斥锁:阻止多个线程同时访问一个条件变量,使用synchronized来声明一个操作共享数据的一段代码块或一个方法
(1)同步代码块:任何时刻只能有一个线程能获得此代码块的访问权
synchronized(<同步对象名>) {
<需要同步的代码>
}
(2)同步方法:任何时刻该方法只能被一个线程执行
synchronized <方法返回值类型> <方法名>(<参数列表>) {
<方法体>
}
2.线程间交互同步
等待通知机制:(1)在生产者没有生产之前,通知消费者等待,生产者生产后,马上通知消费者消费
(2)在消费者消费后,通知生产者已经消费完,需要生产
方法:
方法名称 描述 public final void wait() throws InterruptedException 释放已持有的锁,进入等待队列 public final void wait(long timeout) throws InterruptedException 指定最长的等待时间,单位为毫秒 public final void notify() 唤醒第一个等待的线程并把它移入锁申请队列 public final void notifyAll() 唤醒全部等待的线程并将它们移入锁申请队列 注意:
wait() 和 notify()/notifyAl()必须在已经持有锁的情况下执行,它们只能出现在synchronized作用的范围内