程序:是对数据描述与操作的。 代码的集合。是应用程序执行的脚本,是静态的。
- 在一个操作系统中,每个 独立执行的程序 都可以称为一个 进程,也就是 “ 正在运行的程序” ,强调了 “运行” 的状态。
- 目前大部分计算机安装的都是多任务操作系统,即能同时执行多个应用程序。
- 表明看进程是并发执行的,实际上这些 进程并不是同时运行的,操作系统会为每一个进程分配一段有限的CPU时间,CPU在这段时间执行某个进程,在下段时间切换到另一个进程。
- 在一个 进程 ( 正在运行的程序 )中可以有 多个 “执行单元” 同时运行,它们可以看做 程序执行的一条条路径,被称为 “线程”。
- 操作系统中 每一个进程至少存在一个线程。
- 在一个Java程序启动时,会产生一个进程,该进程默认创建。一个线程,在该线程上运行main()方法中的代码。
- 如果希望 程序中实现“多段程序代码” 交替运行的效果,则 需要创建多个线程,即 多线程 程序。
- 单线程执行时只有一个路径,CPU就一直按按照这个路径执行,而多线程有多个路径,CPU在多个路径上切换执行。
- 继承Thread类,重写run( )方法,在run方法中实现线程中的代码。
- 实现Runnable接口,同样要 重写run( )方法 中实现在线程上的代码。
继承Thread类,创建线程的步骤 为:
①定义类,声明继承Thread类。
②重写Thread的run( )方法,run()方法的方法体为线程对应的子任务。
③创建自定义类的对象,调用start( )方法。start( )方法的作用:启动线程和调用run( )方法。
由于线程的运行顺序是随机调度的,因此会出现不同的结果。
public class Demo { public static void main(String[] args) { //此代码为多线程"的程序 System.out.println("Demo类的主线程正在运行。"); //创建两个线程 MyThread myThread1 = new MyThread(); MyThread myThread2 = new MyThread(); //启动线程 myThread1.start(); myThread2.start(); System.out.println("Demo的主线程运行结束。"); } } class MyThread extends Thread{ @Override public void run() { System.out.println("线程"+Thread.currentThread().getName()+"的run()方法正在运行。"); } }
通过继承Thread类有一定局限性,因为Java只支持单继承,一旦继承了某个父类就不能再继承Thread类,这时可通过实现Runnable接口来避免这个问题。
Thead类提供了另一个 构造方法 Thread(Runable target)。Runnable接口中只有一个run( )方法,因此只需传递一个实现Runnable接口的实例对象就能创建线程。
创建的线程会调用实现Runnable接口中的run( )方法,不用调用Thread类的 run( ) 方法。
实现Runnable接口,创建线程的步骤 为:
①定义类,实现Runnable接口。
②重写Runnable接口的run( )方法,run()方法的方法体为线程对应的子任务。
③创建自定义类的对象,创建Thread类,Thread类的构造方法的参数为“自定义的对象”。
④调用 start( ) 方法,开始多线程。
调用start( )方法。start( )方法的作用:启动线程和调用run( )方法。
public class Demo2 { public static void main(String[] args) { //此代码为多线程的程序 System.out.println("Demo2类的主线程正在运行。"); //创建自定义的实现了Runnable接口的类 MyThread2 obj = new MyThread2(); //创建两个线程,第二个参数为线程指定的名称 Thread t1 = new Thread(obj, "Thread 1 "); Thread t2 = new Thread(obj, "Thread 2 "); //启动线程 t1.start(); t2.start(); System.out.println("Demo2类的主线程运行结束。"); } } class MyThread2 implements Runnable { @Override public void run() { System.out.println("线程"+Thread.currentThread().getName()+"的run()方法正在运行。"); } }
当Thread对象创建完成时,线程的生命周期就开始了。当run()方法中代码正常执行完毕或抛出一个未捕获的异常或错误时,线程的生命周期将结束。
线程的生命周期分为五个阶段:
① 新建状态 (New)
② 就绪状态 (Runnable)
③ 运行状态 (Running)
④ 阻塞状态 (Blocked)
⑤ 死亡状态 (Terminated)
- 创建一个线程对象后,该线程对象就处于新建状态。此时不能运行,和其他Java对象一样,仅为其分配了内存,没有任何线程的动态特征。
- 当线程对象调用了start( )方法后,该线程就进入“就绪状态”,此时具备了运行的条件,能获得CPU的使用权,还需要等待系统的调度。
- 就绪状态的线程获得了CPU的使用权,开始执行run()方法,则线程处于运行状态。当一个线程启动后,它不可能一直处于运行状态。
一个正在执行的线程在某些特殊情况下,如执行耗时的输入/输出操作时,会放弃CPU的使用权,进入 阻塞状态。只有当引起 阻塞的原因被消除后,线程才可以转入就绪状态。
当线程试图获取某个对象的 同步锁 时,如果该锁被其他线程所持有时,线程会由运行状态转为阻塞状态。
当调用一个阻塞的IO方法,要等待这个阻塞的IO方法返回,此时 线程会由运行状态转为阻塞状态。
当线程调用了某个对象的wait()方法,此时 线程会由运行状态转为阻塞状态 。需要使用notify()方法唤醒该线程。
当线程调用了Thread的sleep(long millis)方法时,此时 线程会由运行状态转为阻塞状态。等到休眠时间到了才能进行“就绪状态”。
当线程调用了另一个线程的join()方法时,此时 线程会由运行状态转为阻塞状态,需要等新加入的线程运行结束后才能结束阻塞。
- 当线程的run()方法中代码正常执行完毕或者抛出一个未捕获的异常或者错误时,线程进入死亡状态。线程将不在拥有运行的资格,也不能再转化到其他状态。
Java虚拟机会按照特定的机制为程序中的 每个线程分配CPU的使用时间,这种机制被称为 线程的调度。
线程调度有两种模型 :①分时调度模型 ②抢占式调度模型
分时调度模型:所有线程轮流,平均分配CPU时间。
抢占式调度模型:可运行池中优先级高的线程,相同优先级的随机选择一个线程使其占用CPU,它失去了使用权之后,再随机选择其他线程。
线程的优先级用1-10之间的整数来表示,数字越大优先级越高。可以用Thread类中的 setPriority(int newPriority) 方法进行设置。优先级高的先运行,优先级低的后运行。
public class Demo3 { public static void main(String[] args) { MaxP max = new MaxP(); MinP min = new MinP(); Thread maxT = new Thread(max, "优先级高的程序"); //设置了优先级,其先执行 Thread minT = new Thread(min, "优先级低的程序"); //其后执行 /** * 正常而言,优先级高的“线程”先执行,优先级低的“线程”后执行。 */ maxT.setPriority(10); //设置优先级 minT.setPriority(1); maxT.start(); minT.start(); System.out.println("主线程结束。"); } } class MaxP implements Runnable { public static void main(String[] args) { System.out.println("主线程正在运行。"); } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+": "+i); } } } class MinP implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+": "+i); } } }
如果希望人为控制线程,使 线程暂停,将CPU让给其他线程,可以使用sleep(long millis)。
public class Demo4 { public static void main(String[] args) throws InterruptedException { System.out.println("主线程正在运行。"); //创建自定义线程对象 MyThread mt = new MyThread(); //创建线程 Thread thread = new Thread(mt); //启动线程 thread.start(); for (int i = 0; i < 10; i++) { if (i == 5) { //线程休眠,后进入阻塞状态,休眠结束后,重新进入“就绪状态” Thread.sleep(9000); //当i==5时Thead.sleep(),该for循环休眠了9秒 } System.out.println("主线程输出: "+i); } System.out.println("主线程运行结束。"); } } class MyThread implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("MyThread的输出: "+i); } } }
- 线程让步 可以通过yield()方法来实现,该方法和sleep()方法有点相似,都可以让当前 “正在运行” 的线程暂停 。
- 区别在于yield()方法不会阻塞该线程,它只是将线程转换为就绪状态,重新进入“线程排队”状态。
而sleep() 会让线程进入阻塞状态,睡眠结束后,该线程才重新进入就绪状态,进行“线程排队”。
- 两个方法功能有点相似 : 都可以让当前“正在运行”的线程暂停,最后使线程进入就绪状态,后进行“线程排队”。
- yield() 方法不会阻塞该线程,它只是将线程转换为就绪状态,重新进入“线程排队”状态。
而 sleep() 会让线程进入阻塞状态,睡眠结束后,该线程才重新进入就绪状态,进行“线程排队”。- yield() 告诉操作系统当前线程愿意放弃CPU执行时间片,重新进入“就绪状态”,进行“线程排队”。但是否会马上执行该“就绪状态”的线程得看操作系统的分配了。
- sleep() 使得该线程进入休眠状态,休眠中为“阻塞状态”。当休眠结束时,线程重新进入“就绪状态”,进行“线程排队”。
多线程的并发执行虽然可以提高效率,但是当多个线程去访问同一个资源时,也会引发一些安全问题。
如:线程A读取资源A时,线程B正在修改资源A。那么线程A会读取到/该读取到修改之前的资源A,还是修改后的资源A呢?
(可用多线程的同步来避免此类问题)多线程同步的两种常用方法 :
① 同步代码块 : synchronized(lock) { 操作共享资源代码块 }
② 同步方法 : synchronized 返回值类型 方法名(参数列表)
//同步代码块例子 public class Demo5 { public static void main(String[] args) { //自定义线程对象 SaleTicket sale = new SaleTicket(); //创建四个线程,模拟四个窗口售票 Thread t1 = new Thread(sale, "窗口1"); Thread t2 = new Thread(sale, "窗口2"); Thread t3 = new Thread(sale, "窗口3"); Thread t4 = new Thread(sale, "窗口4"); t1.start(); t2.start(); t3.start(); t4.start(); } } class SaleTicket implements Runnable { int num = 50; @Override public void run() { while (true) { //创建“同步代码块” synchronized ("锁") { if (num > 0) { System.out.println(Thread.currentThread().getName() + "售出了第" + num + "号票。"); num--; } else { System.out.println("票售完了。"); break; } } } } }
public class Demo6 { public static void main(String[] args) { TicketThread2 t = new TicketThread2(); Thread t1 = new Thread(t, "窗口1"); Thread t2 = new Thread(t, "窗口2"); Thread t3 = new Thread(t, "窗口3"); Thread t4 = new Thread(t, "窗口4"); t1.start(); t2.start(); t3.start(); t4.start(); } } class TicketThread2 implements Runnable { //总票数 int num = 50; @Override public void run() { while (true) { //调用售票的方法 sale(); if (num <= 0) { System.out.println("票卖完了。"); break; } } } private synchronized void sale() { //该方法是同步的 if (num > 0) { System.out.println(Thread.currentThread().getName()+"售出了"+num+"号票。"); //获得线程名字等配合其他信息使用 num --; } } }
线程通信 指的是一个线程完成了自己的任务时,要通知另外一个线程去完成另外一个任务。
要完成线程之间的通信,就需要控制多个线程按照一定的顺序轮流执行。
Object类中提供了wait(), notify() 和notifyALL()方法用于解决线程之间的通信问题。
这三个方法的调用者都应该是同步锁对象,否则会抛出IllegalMonitorStateException异常
方法名称 功能描述 void wait() 使当前线程放弃同步锁进入等待,直到其他线程进入此同步锁,并调用notify()或notifyAll()方法唤醒该线程为止。 void notify() 唤醒同步锁上等待的第一个调用wait()方法的线程。 void notifyAll() 唤醒此同步锁上调用wait()方法的所有线程。