1.1 线程概述
在计算机中,所有应用都是有CPU执行的,一个CPU在某个时间点只能运行一个程序——即一个进程。
操作系统的每一个进程都只是存在一个线程,即当一个Java程序启东市,线程运行main()方法中的代码。要在程序中实现多段代码交替运行,这需要创建多个线程——即多线程。多线程进程一样,也是有CPU轮流执行的,是不给CPU运行的速度很块。
1.2 线程的创建
Java提供两种多线程实现方法:
1) 继承java.lang包下的Tread类,覆盖Thread的run()方法,在run()中实现运行在线程上的代码;
2) 实现java.lang.Runnable接口,在run()中实现运行在线程上的代码。
1.2.1 继承Thread类的创建多线程
JDK提供一个线程类Thread,继承该类,重写run方法,可实现多线程,并且提供一个start()方法用于启动线程,程序启动后,线程会自动调用run()方法。
1 public class Example01 { 2 3 public static void main(String[] args) { 4 MyThread myThread=new MyThread(); 5 myThread.start(); 6 while(true){ 7 System.out.println("main()方法在运行"); 8 } 9 10 } 11 public static class MyThread extends Thread{ 12 public void run() { 13 while(true){ 14 System.out.println("MyThread类的run()方法在运行"); 15 } 16 } 17 } 18 19 20 }
结果分析:两个while循环中的打印语句轮流执行。即main()方法和MyTread()类的中的Run()语句可以同时运行。
1.2.2 实现Runable接口创建多线程
继承Thread类的创建多线程有一定局限性,一个类一旦继承某个父类就无法继承其他类(Java只支持单继承)。
1 public class Example02 { 2 public static void main(String[] args) { 3 MyThread myThread=new MyThread();//创建MyThread的实例对象 4 Thread thread=new Thread(myThread);//创建线程对象 5 thread.start(); 6 while(true){ 7 System.out.println("main()方法在运行"); 8 } 9 } 10 public static class MyThread implements Runnable{ 11 public void run() {//线程代码块,当调用Start()方法时,线程从此处开始执行 12 while(true){ 13 System.out.println("MyThread类的run()方法在运行"); 14 } 15 } 16 } 17 }
结果分析:两个while循环中的打印语句轮流执行。即main()方法和MyTread()类的中的Run()语句可以同时运行。
1.2.3 两种多线程比较
以售票大厅为类:售票大厅有四个窗口,共计售卖100张票。
A) 继承Thread类的创建多线程
1 public class Example03 { 2 public static class TicketWindow extends Thread{ 3 private int tickets=100; 4 public void run() { 5 while(true) { //通过死循环打印语句 6 if(tickets>0) { 7 Thread th=Thread.currentThread();//获取当前线程 8 String th_name=th.getName(); //获取当前线程的名字 9 System.out.println(th_name+"正在发售第"+tickets--+"张票"); 10 } 11 } 12 } 13 } 14 public static void main(String[] args) { 15 new TicketWindow().start();//创建一个线程对象TicketWindow()并开启 16 new TicketWindow().start();//创建一个线程对象TicketWindow()并开启 17 new TicketWindow().start();//创建一个线程对象TicketWindow()并开启 18 new TicketWindow().start();//创建一个线程对象TicketWindow()并开启 19 } 20 }
运行结果:Thread-0正在发售第100张票
Thread-3正在发售第100张票
Thread-2正在发售第100张票
Thread-1正在发售第100张票
Thread-2正在发售第99张票
Thread-3正在发售第99张票
Thread-0正在发售第99张票
Thread-3正在发售第98张票
Thread-2正在发售第98张票
Thread-1正在发售第99张票
Thread-2正在发售第97张票
Thread-3正在发售第97张票
Thread-0正在发售第98张票
…….
分析可知:线程会自动编号,,用户创建的第一个线程为“Thread-0”,从0开始编号。但是四个线程都是从100章票开始往下减,独立出来自己的资源,且四个线程每次执行的次序都是随机的。(但是这种运行结果是不合理的)。
B) 实现Runable接口创建多线程
1 public class Example04 { 2 public static class TicketWindow implements Runnable{ 3 private int tickets=100; 4 public void run() { 5 while(true) { //通过死循环打印语句 6 if(tickets>0) { 7 Thread th=Thread.currentThread();//获取当前线程 8 String th_name=th.getName(); //获取当前线程的名字 9 System.out.println(th_name+"正在发售第"+tickets--+"张票"); 10 } 11 } 12 } 13 } 14 public static void main(String[] args) { 15 TicketWindow tw =new TicketWindow();//创建TicketWindow实例对象tw 16 new Thread(tw,"窗口1").start();//创建线程对象并命名为窗口1,开启线程 17 new Thread(tw,"窗口2").start(); 18 } 19 }
分析:代码只创建了一个ThickWindow对象,然后创建了四个线程,四个线程都去调用这个TicketWindow对象中的run()方嘎,确保四个线程访问的是同一个tickets变量,共享100章车票。
综上;Runnable接口相对于Threadgen有显著好处
1)适合多个相同程序去处理同个资源的情况,把线程同程序代码、数据有效的分离;
2)可以避免由于Java的单继承性带来的局限性;
1.3 线程的调度
Java虚拟机会按照特定的机制为程序中的每个线程分配CPU的使用权,这种机制被称作线程的调度。
线程的电镀有两种模型:分时调度模型和抢占式调度模型
A) 分时调度模型:所有线程轮流获得CPU的使用权,并且平均分配每个下次占用的CPU的时间片
B) 抢占式调度模型:可让运行迟中优先级高的线程先占用CPU,对于优先级相同的线程。随机选取一个线程使其占用CPU
1.3.1 线程的优先级
对线程进行调度,可以设置线程的优先级。线程优先级用1-10的整数表示,数字越大优先级越高。
表5-1 Thread类的优先级常量
Threadl类的静态常量 |
功能描述 |
Static int MAX_PRIORITY |
表示线程的最高优先级,10 |
Static int MIN_PRIORITY |
表示线程的最低优先级,0 |
Static int NORM_PRIORITY |
表示线程的普通优先级,5 |
处于就绪状态的每个线程独有自己的优先级,main线程具有普通优先级。可以通过Thread类的setPriority(int newPriority)方法对其进行设置,该方法中的参数newPriority为1-10的整数或者Thread类的三个静态常量。
1 public class Example5 { 2 public static void main(String[] args) { 3 Thread maxPriority=new Thread(new MaxPriority(),"优先级最高的线程");//创建线程对象 4 Thread minPriority=new Thread(new MinPriority(),"优先级最低的线程"); 5 //设置优先级 6 maxPriority.setPriority(10); 7 minPriority.setPriority(Thread.MIN_PRIORITY); 8 //开启两个线程 9 minPriority.start(); 10 maxPriority.start(); 11 } 12 //定义类MaxPriority实现Runnable接口 13 public static class MaxPriority implements Runnable{ 14 public void run() {//线程代码块,当调用Start()方法时,线程从此处开始执行 15 for(int i=0;i<10;i++) { 16 System.out.println(Thread.currentThread().getName()+"正在输出"+i); 17 } 18 } 19 } 20 //定义类MinPriority实现Runnable接口 21 public static class MinPriority implements Runnable{ 22 public void run() {//线程代码块,当调用Start()方法时,线程从此处开始执行 23 for(int i=0;i<10;i++) { 24 System.out.println(Thread.currentThread().getName()+"正在输出"+i); 25 } 26 } 27 } 28 }
1.3.2 线程休眠、让步、插队
A)线程休眠:Java提供一个静态方法sleep(long millis),该方法可以让当前的正在执行的线程暂停一段时间,进入休眠等待状态。
Sleep(long millis)方法声明抛出InterruptedException异常,英雌在调用该方法时应该不活异常,或者声明抛出异常。
B)线程让步:Java提供yield()方法来实现,该方法和sleep()方法相似,都可让当前正在运行的线程暂停。它的作用是是将线程转换成就绪状态,让系统调度器可以重新调度一次。
C)线程插队:在线程中也提供一个方法join()使得线程可以“插队”。当在某个线程调用其它线程的join()方法时,调用的线程将被堵塞,直到被join()方法加入的线程执行完成后它才可以继续执行。
join()方法声明抛出InterruptedException异常,英雌在调用该方法时应该不活异常,或者声明抛出异常。
1 public class Example06 { 2 public static void main(String[] args) throws Exception { 3 Thread t=new Thread(new MaxPriority(),"线程一");//创建线程对象 4 t.start(); 5 for(int i=0;i<10;i++) { 6 System.out.println(Thread.currentThread().getName()+"正在输出"+i); 7 if(i==2) { 8 t.join(); 9 } 10 Thread.sleep(500); 11 } 12 } 13 //定义类MaxPriority实现Runnable接口 14 public static class MaxPriority implements Runnable{ 15 public void run() {//线程代码块,当调用Start()方法时,线程从此处开始执行 16 for(int i=0;i<10;i++) { 17 System.out.println(Thread.currentThread().getName()+"正在输出"+i); 18 try { 19 Thread.sleep(500); 20 } catch (InterruptedException e) { 21 // TODO Auto-generated catch block 22 e.printStackTrace(); 23 } 24 } 25 } 26 } 27 }
分析运行结果:发现当main中的循环变量变为2是,调用t线程的join()方法,直到t线程执行完毕以后再执行main线程。