线程简介

Java语言提供了并发机制,可以在程序中执行多个线程,每一个线程完成一个功能,并与其他线程并发执行,这种机制称为多线程。
多线程在Windows操作系统中的运行模式,Window操作系统就是多任务操作系统,它以进程为单位,一个进程是一个包含有自身地址的程序,每个独立执行的程序称为进程,也就是正在执行的程序,系统可以分配给每个进程一段有限的使用CPU的实践(也可以称为CPU时间片),CPU在这段时间内执行某个进程,然后下一个时间片又跳至另一个进程中去执行,由于CPU转换够快,所以使得每个进程好像同时执行了一样。
一个线程则是进程中的执行流程,一个进程中可以同时包括多个线程,每个线程可以得到一小段程序的执行时间,这样一个进程就可以具有多个并发执行的线程,在单线程中,程序代码按调用顺序依次往下执行,如果需要一个进程同时完成多段代码的操作,就需要产生多线程。

实现进程的两种方式

继承Tread类

Tread类是java.lang包中的一个类,从这个类中实例化的对象代表线程,程序员启动一个新线程需要建立Tread实例,Thread类中常用的两个构造方法如下:

  1. public Thread():创建一个新的线程对象。
  2. public Thread()(String threadName):创建一个名称为threadName的线程对象。

继承Thread类创建一个新的线程的语法如下:

  1. public class ThreadTest extends Thread{
  2. }

完成线程真正功能的代码放在类的run()方法中,当一个类继承Tread类后,就可以在该类中覆盖run()方法,将实现线程功能的代码写入run()方法中,然后同时调用Thread类中的start()方法执行线程,也就是调用run()方法。
Thread对象需要一个任务来执行,任务是指线程在启动是执行的工作,该工作的功能代码被写在run()方法中,run()方法必须使用下列语法结构:

  1. public void run(){
  2. }

当执行一个线程程序的时候,就自动产生一个线程,主方法正是在这个线程上运行的,当不再启动其他线程时,该线程就为单线程程序。主方法线程是由java虚拟机负责,而自己的线程就需要自己来启动,代码如下:

  1. public static void main(String[] args){
  2. new ThreadTest.start();
  3. }

下面是一个例子:

  1. public class ThreadTest extends Thread {
  2. private int count=10;
  3. public void run(){
  4. while (true){
  5. System.out.println(count+"");
  6. if (--count==0){
  7. return;
  8. }
  9. }
  10. }
  11. public static void main(String[] args) {
  12. new ThreadTest().start();
  13. }
  14. }

实现Runnable接口

到目前为止,线程都是通过扩展Thread类来创建的,但是如果你的程序需要继承其他的类,也就是非Thread类,而且还需要使用当前类实现多线程,就可以通过Runnable接口来实现,实现Runnable接口的语法如下:

  1. public class ThreadTest extends Object implements Runnable

注意:实际上Thread类实现了Runnable接口,其中的run()方法正是对Runnable接口中的run()方法的具体实现。

实现Runnable接口的程序会先创建一个Thread对象,并将Runnabled对象和Thread对象相关联,Thread类中有两个构造方法:

  1. public Thread(Runnable target)
  2. public Thread(Runnable target,String name)

这两个构造方法的参数都存在Runnable实例,使用以上的构造方法就可以把Runnable

实例和Thread实例相关联。

使用Runnable接口启动新的线程的步骤如下:

  1. 建立Runnable对象
  2. 使用参数为Runnable对象的构造方法创建Thread实例
  3. 调用start()方法启动线程

例子如下:

  1. public class RunnableTest implements Runnable {
  2. private int count = 15;
  3. public void run() {
  4. while (true) {
  5. System.out.println(count + "");
  6. if (--count == 0) {
  7. return;
  8. }
  9. }
  10. }
  11. public static void main(String[] args) {
  12. RunnableTest runnableTest = new RunnableTest();
  13. Thread t1 = new Thread(runnableTest);
  14. t1.start();
  15. }
  16. }

两种方式的优劣

  • 一般都是用实现Runnable接口的方式来进行多线程程序的编写,因为java只能单继承,如果你要继承其他的类,而且还要实现子线程的话,就不能使用继承与Thread类,只能使用实现Runnable接口的方式
  • 如果一个类继承了Thread类,则不适合资源共享,如果实现了Runnable接口的方式,非常适合进行资源共享
  • 如果继承与Thread类,线程代码是放在Thread子类run()方法中,如果是实现Runnable接口的话,线程代码是放在接口的子类的run()方法中。

线程的生命周期

线程具有生命周期,其中包含7种状态,分别是:

  • 出生状态:线程被创建时处于的状态,实例调用start()方法之前都处于出生状态。
  • 就绪状态:当用户调用start()方法后,线程处于就绪状态。
  • 运行状态:当线程得到系统资源后就进入运行状态。
  • 等待状态:当处于运行状态下的线程调用Thread类中的wait()方法时,该线程便进入等待状态,进入等待状态的线程必须调用Thread类中的notify()方法才能被唤醒,而notifyAll()方法是将所以处于等待状态下的线程唤醒。
  • 休眠状态:当线程调用Thread类中的sleep()方法时,则会进入休眠状态。
  • 阻塞状态:如果一个线程在运行状态下发出输入/输出请求,该线程将进入阻塞状态,在其等待输入/输出结束时线程进入就绪状态,对于阻塞的线程来说,即使系统资源空闲,线程依然不能回到运行状态。
  • 死亡状态:当线程的run()方法执行完毕时,线程进入死亡状态。

下面是线程的生命周期状态图:

使线程处于就绪状态有以下几种方法:

  1. 调用sleep()方法
  2. 调用wait()方法
  3. 等待输入/输出完成

当线程处于就绪状态后,使线程再次进入运行状态有以下几种方法:

  1. 线程调用notify()方法
  2. 线程调用notifyAll()方法
  3. 线程调用interrupt()方法
  4. 线程的休眠时间结束
  5. 输入/输出结束

操作线程的方法

线程的休眠

一种控制线程行为的方法是调用sleep()方法,sleep()方法需要一个参数用于指定该线程休眠的时间,该时间以毫秒为单位,语法如下:

  1. try{
  2. Thread.sleep2000);
  3. }catchInterruptedException e){
  4. e.printStackTrace();
  5. }

使用了sleep()方法的线程在一段时间内会醒来,但是并不能保证它醒来后进入运行状态,只能保证它进入就绪状态。

线程的加入

当某个线程使用join()方法加入到另外一个线程时,另一个线程会等待该线程执行完毕后在继续执行。例子如下:

  1. public class JoinTest implements Runnable {
  2. private int count = 15;
  3. public void run() {
  4. while (true) {
  5. try {
  6. System.out.println(count + "");
  7. if (--count == 0) {
  8. return;
  9. }
  10. }catch (Exception e){
  11. e.printStackTrace();
  12. }
  13. }
  14. }
  15. public static void main(String[] args) {
  16. try {
  17. JoinTest joinTest = new JoinTest();
  18. Thread t1 = new Thread(joinTest);
  19. t1.start();
  20. t1.join();
  21. System.out.println("执行了子线程在执行主线程");
  22. }catch (InterruptedException e){
  23. e.printStackTrace();
  24. }
  25. }
  26. }

线程的中断

以前是使用stop()方法来停止线程,现在的JDK已经废除了stop()方法,现在提倡在run()方法中使用无限循环的形式,然后使用一个布尔型标记控制循环的停止。例子如下:

  1. public class Interrupted implements Runnable{
  2. private boolean isContinue=false;
  3. public void run(){
  4. while(true){
  5. //····
  6. if(isContinue)
  7. break;
  8. }
  9. }
  10. public void setContinue(){
  11. this.isContinue=true;
  12. }
  13. }

如果线程使用了sleep()方法或wait()方法进入就绪状态,可以使用Thread类中interrupt()方法使线程离开run()方法。同时结束进程,但是程序会抛出InterruptException异常,用户可以在处理该异常的时候完成线程的中断业务处理,如终止while循环。例子如下:

  1. public class InterruptedTest extends Thread {
  2. public InterruptedTest(String name){
  3. super(name);
  4. }
  5. public void run(){
  6. try {
  7. int i=0;
  8. while (!isInterrupted()) {
  9. Thread.sleep(100); // 休眠100ms
  10. i++;
  11. System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);
  12. }
  13. } catch (InterruptedException e) {
  14. System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");
  15. }
  16. }
  17. public static void main(String[] args) {
  18. try {
  19. Thread t1 = new InterruptedTest("t1"); // 新建“线程t1”
  20. System.out.println(t1.getName() +" ("+t1.getState()+") is new.");
  21. t1.start(); // 启动“线程t1”
  22. System.out.println(t1.getName() +" ("+t1.getState()+") is started.");
  23. // 主线程休眠300ms,然后主线程给t1发“中断”指令。
  24. Thread.sleep(300);
  25. t1.interrupt();
  26. System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
  27. // 主线程休眠300ms,然后查看t1的状态。
  28. Thread.sleep(300);
  29. System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }

线程礼让

Thread类中提供了一种礼让方法,使用yield()方法,它只是给当前正处于运行状态的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。

yield()方法使具有同样优先级的线程有进入可执行状态的机会,当当前线程放弃执行权时会再度回到就绪状态。

对于支持多任务的操作系统来说,不需要调用yield()方法,因为操作系统会为线程自动分配CPU时间片来执行。

线程的优先级

如果有很多进程处于就绪状态,系统会根据优先级决定首先使用哪个线程进入运行状态,但这并不意味着低优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收线程的优先级就较低。

Thread类中包含的成员变量代表了线程的某些优先级,如Thread.MIN_PRIORITY(常数1)、Thread.MAX_PRIORITY(常数10)、Thread.NORM_PRIORITY(常数5),其中每个线程的优先级都在Thread.MIN_PRIORITY(常数1)~Thread.MAX_PRIORITY(常数10)之间,在默认情况下,其优先级都是Thread.NORM_PRIORITY(常数5),每个新产生的线程都继承了父线程的优先级。

线程的优先级可以使用setPriority()方法调整,如果使用该方法设置的优先级不在1~10之间,将产生IllegalArgumentException异常。

对于处于优先级状态下的运行顺序,举个例子。有四个线程,优先级5的A和B,优先级4的C,优先级3的D,首先是优先级5的线程A首先得到CPU时间片,当该时间结束时,轮换到与线程A相同优先级的线程B,当线程B的运行时间结束后,会继续轮换到线程A,直到线程A和B都执行完毕,才会轮到线程C,当线程C完毕之后,才能轮到线程D。

线程同步

线程同步机制的产生是由于在多线程编程中资源访问冲突,线程同步是为了防止资源访问的冲突。

线程安全

以下是资源冲突出现问题的例子:

  1. public class ThreadSafeTest implements Runnable{
  2. int num=10;
  3. public void run(){
  4. while (true){
  5. if(num>0){
  6. try{
  7. Thread.sleep(100);
  8. }catch (Exception e){
  9. e.printStackTrace();
  10. }
  11. System.out.println("tickets"+num--);
  12. }
  13. }
  14. }
  15. public static void main(String[] args) {
  16. ThreadSafeTest threadSafeTest=new ThreadSafeTest();
  17. Thread a=new Thread(threadSafeTest);
  18. Thread b=new Thread(threadSafeTest);
  19. Thread c=new Thread(threadSafeTest);
  20. Thread d=new Thread(threadSafeTest);
  21. a.start();
  22. b.start();
  23. c.start();
  24. d.start();
  25. }
  26. }

造成这个结果的原因是:这四个线程都执行run()方法,在num变量为1时,线程1、线程2、线程3、线程4都对num这个变量有存储功能,当线程1执行run()方法时,还没有来得及做递减操作,就指定它调用sleep()方法进入就绪模式,这时线程2、线程3、线程4都进入run()方法,发现num变量依然大于0,但此时线程1休眠时间已到,将num变量值递减,同时线程2、线程3、线程4也对num变量进行递减操作,从而产生了负值。

线程同步机制

同步块

在Java中提供了同步机制,可以有效地防止资源冲突,同步机制使用synchronized关键字。其语法如下:

  1. synchronized(Object){
  2. }

以下是上面线程安全的解决方案:

  1. public class ThreadSafeTest implements Runnable{
  2. int num=10;
  3. public void run(){
  4. while (true){
  5. synchronized (""){
  6. if(num>0){
  7. try{
  8. Thread.sleep(100);
  9. }catch (Exception e){
  10. e.printStackTrace();
  11. }
  12. System.out.println("tickets"+num--);
  13. }
  14. }}
  15. }
  16. public static void main(String[] args) {
  17. ThreadSafeTest threadSafeTest=new ThreadSafeTest();
  18. Thread a=new Thread(threadSafeTest);
  19. Thread b=new Thread(threadSafeTest);
  20. Thread c=new Thread(threadSafeTest);
  21. Thread d=new Thread(threadSafeTest);
  22. a.start();
  23. b.start();
  24. c.start();
  25. d.start();
  26. }
  27. }

打印结果没有出现负数是因为将资源放在了同步块中,这个同步块也被称为临界区。这样讲共享资源放置在synchronized定义的区域中,这样其他线程也获取这个锁时,必须等待被释放时才能进入该区域,Object为任意一个对象,每个对象都存在一个标志位,并具有两个值,分别是0和1,一个线程运行到同步块时首先检查该对象的标志位,如果为0状态,表明此同步块中存在其他线程运行,这个该线程就处于就绪状态,直到处于同步块中的线程执行完同步块中的代码为止,这时该对象的标志位被设置为1,该线程才能执行同步块中的代码,并将Object对象的标志位设置为0,防止其他线程执行同步块中的代码。

同步方法

同步方法就是在方法前面修饰synchronized关键字的方法,其语法如下:

  1. synchronized void f(){
  2. }

当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行,必须将每个能访问共享资源的方法修饰为synchronized,否则就会出错,以下为例子:

  1. public class ThreadSafeTest implements Runnable{
  2. int num=10;
  3. public synchronized void doit(){
  4. if(num>0){
  5. try{
  6. Thread.sleep(100);
  7. }catch (Exception e){
  8. e.printStackTrace();
  9. }
  10. System.out.println("tickets"+num--);
  11. }
  12. }
  13. public void run(){
  14. while (true){
  15. doit();
  16. }
  17. }
  18. public static void main(String[] args) {
  19. ThreadSafeTest threadSafeTest=new ThreadSafeTest();
  20. Thread a=new Thread(threadSafeTest);
  21. Thread b=new Thread(threadSafeTest);
  22. Thread c=new Thread(threadSafeTest);
  23. Thread d=new Thread(threadSafeTest);
  24. a.start();
  25. b.start();
  26. c.start();
  27. d.start();
  28. }
  29. }