一、意义
使用多线程的目的是为了提高CPU资源的利用效率。在单线程应用中程序必须等待当前任务的完成才能继续执行下一项任务,CPU在等待的时间内就闲置了,多线程的使用可减少闲置时间。
二、主线程
当Java程序启动时,会立即开始运行主线程。其他所有的线程都是从主线程产生的,主线程必须是最后才结束执行的线程,因为它需要执行各种关闭动作。可以通过currentThread静态方法获取主线程的引用。
class Solution { public static void main(String[] args) { Thread t = Thread.currentThread(); System.out.println("Current thread: " + t);//Thread[main,5,main] try { for (int i = 0; i < 10; i++) { System.out.println(i); Thread.sleep(500); } } catch (InterruptedException exc) { System.out.println("Current thread interrupted"); } } }
默认情况下,主线程的名字是main,优先级是5,main也是主线程所属的线程组的名字。线程组是将线程作为一个整体来控制状态的数据结构。
三、创建线程
可通过继承Thread类或实现Runnable接口来创建线程。
实现Runnable接口创建线程时,只需要实现run方法,run方法定义线程的代码,线程随run方法结束而结束。在创建了新线程之后,只有调用start方法线程才会运行,本质上start方法执行对run方法的调用。但是Runnable接口只定义了run方法,执行run类的代码必须借助Thread实例。
扩展Thread类创建线程时, 必须重写run方法或定义Runnable接口实例。
class ThreadA extends Thread { @Override public void run() { try { for (int i = 0; i < 10; i++) { System.out.println(i); Thread.sleep(500); } } catch (InterruptedException exc) { System.out.println(getName() + " interrupted"); } } ThreadA(String name) { super(name); start(); } } class ThreadB implements Runnable { private Thread t; @Override public void run() { try { for (int i = 0; i < 10; i++) { System.out.println(i); Thread.sleep(500); } } catch (InterruptedException exc) { System.out.println(t.getName() + " interrupted"); } } ThreadB(String name) { t = new Thread(this, name); t.start(); } } class Solution { public static void main(String[] args) { ThreadA tA = new ThreadA("A"); ThreadB tB = new ThreadB("B"); } }
四、等待线程
当某个线程需要耗费大量时间运算,而其他线程又需要等待该线程结束后才能继续获取数据。这种情况下可直接调用该线程的join方法,程序会停留于调用join方法处直至调用该方法的线程结束才会继续执行后面的代码。
class MyThread extends Thread { @Override public void run() { try { for (int i = 0; i < 10; i++) { System.out.println(i); Thread.sleep(500); } } catch (InterruptedException exc) { System.out.println(getName() + " interrupted"); } } MyThread(String name) { super(name); } } class Solution { public static void main(String[] args) { MyThread tA = new MyThread("thread A"); MyThread tB = new MyThread("thread B"); MyThread tC = new MyThread("thread C"); try { tA.start(); tA.join();//停留直至tA结束 tB.start(); tB.join(1000);//停留1秒后无论tB是否结束都继续执行后面的代码 tC.start(); tC.join();//停留直至tC结束 } catch (InterruptedException exc) { System.out.println("Thread interrupted"); } System.out.println("Thread A is alive " + tA.isAlive()); System.out.println("Thread B is alive " + tB.isAlive()); System.out.println("Thread C is alive " + tC.isAlive()); } }
五、优先级
Java为线程指定了优先级,优先级决定了相对于其他线程会如何处理某个线程。优先级是整数,但绝对值没有意义。线程的优先级必须在MIN_PRIORITY到MAX_PRIORITY之间,在线程中是作为静态常量定义的。优先级决定何时从一个运行的线程切换到下一个,称为上下文切换。发生规则如下:
- 线程自愿放弃控制。线程显式放弃控制权、休眠或再I/O之前阻塞,都会导致这种情况的出现。发生这种情况时,会检查其他线程,并且即将运行的线程中优先级最高的会获得CPU资源。
- 线程被优先级更高的线程取代。没有放弃控制权的低优先级线程无论在做什么,都会被高优先级的线程取代。只要高优先级线程运行,就会取代低优先级线程,称为抢占式多任务处理。
具有相同优先级的线程竞争CPU资源时,Windows中会以循环的方式自动获得CPU资源,其他操作系统中优先级相等的线程必须主动放弃控制权其他线程才能运行。理论上优先级更高的线程会获得更多的CPU时间,具有相同优先级的线程应当得到相同的CPU时间,但不同环境的多任务方式不同,为了安全起见,具有相同优先级的线程应当时不时放弃控制权,以确保所有线程在非抢占式操作系统中有机会运行。
六、同步
多线程使程序可以进行异步行为,所以必须提供一种在需要时强制同步的方法。例如当两个线程进行通信并共享某个复杂的数据结构,当一个线程向数据结构中写入数据时,必须阻止其他线程向数据结构中写入数据,否则可能会发生冲突。
同步的关键是监视器,监视器是用作互斥锁的对象。在给定时刻只有一个线程可以拥有监视器,一旦线程进入监视器,也就是取得锁,其他所有线程就必须等待,直至该线程退出监视器,其他所有尝试进入加锁监视器的线程都会被挂起。Java中每个类都有自己隐式的监视器,每当对象的同步方法被调用时,线程就会自动进入对象的隐式监视器。
class Callme { synchronized void call(String msg) {//同步方法 System.out.print("[" + msg); try { Thread.sleep(1000); System.out.println("]"); } catch (InterruptedException exc) { System.out.println("Callme interrupted"); } } } class Caller extends Thread { private String msg; private Callme target; Caller(Callme target, String msg) { this.target = target; this.msg = msg; start(); } @Override public void run() { target.call(msg); } } class Solution { public static void main(String[] args) { Callme target = new Callme(); Caller obA = new Caller(target, "Caller A"); Caller obB = new Caller(target, "Caller B"); Caller obC = new Caller(target, "Caller C"); try { obA.join(); obB.join(); obC.join(); } catch (InterruptedException exc) { System.out.println("Caller interrupted"); } } }
但是假设某个类并没有对多线程进行设计,即类中没有同步方法。由于我们并不是该类的创建者,无法访问其源代码,也就无法使用上述方法为类中的方法添加synchronized修饰符。那么可以将需要同步的部分放到synchronized代码块中。
class Caller extends Thread { private String msg; private Callme target; Caller(Callme target, String msg) { this.target = target; this.msg = msg; start(); } @Override public void run() { synchronized (target) {//同步代码块 target.call(msg); } } }