线程之基础

1.简介

Java提供了线程类Thread来创建多线程的程序。其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象。每个Thread对象描述了一个单独的线程。要创建一个线程,有3种方法:

  • 需要从Java.lang.Thread类派生一个新的线程类,重写它的run()方法。
  • 实现Runnable接口,重写Runnable接口中的run()方法。
  • 实现Callable接口,重写Callable接口中的call()方法。

  为什么Java要提供两种方法来创建线程呢?它们都有哪些区别?相比而言,哪一种方法更好呢?

  在Java中,类仅支持单继承,也就是说,当定义一个新的类的时候,它只能扩展一个外部类.这样,如果创建自定义线程类的时候是通过扩展 Thread类的方法来实现的,那么这个自定义类就不能再去扩展其他的类,也就无法实现更加复杂的功能。因此,如果自定义类必须扩展其他的类,那么就可以使用实现Runnable接口的方法来定义该类为线程类,这样就可以避免Java单继承所带来的局限性。

  还有一点最重要的就是使用实现Runnable接口的方式创建的线程可以处理同一资源,从而实现资源的共享.

2.继承Thread类

public class MainThread {
    public static void main(String[] args) {
//System.out.println(Thread.currentThread().getName());//main MyThread t1
= new MyThread("Window1"); t1.start(); MyThread t2 = new MyThread("Window2"); t2.start(); } }
public class MyThread extends Thread {
    private int n = 10;

    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (n > 0) {
            sales();
        }
    }

    private void sales() {
        if (n > 0) {
            System.out.println(Thread.currentThread().getName() + " sales NO." + n);
            n--;
        }
    }
}

结果:

Window2 sales NO.10
Window1 sales NO.10
Window2 sales NO.9
Window1 sales NO.9
Window2 sales NO.8
Window2 sales NO.7
Window2 sales NO.6
Window2 sales NO.5
Window2 sales NO.4
Window2 sales NO.3
Window2 sales NO.2
Window2 sales NO.1
Window1 sales NO.8
Window1 sales NO.7
Window1 sales NO.6
Window1 sales NO.5
Window1 sales NO.4
Window1 sales NO.3
Window1 sales NO.2
Window1 sales NO.1

从结果可以看到,每个线程分别对应10张电影票,之间并无任何关系,这就说明每个线程之间是平等的,没有优先级关系,因此都有机会得到CPU的处理。但是结果

显示这两个线程并不是依次交替执行,而是在两个线程同时被执行的情况下,有的线程被分配时间片的机会多,票被提前卖完,而有的线程被分配时间片的机会比较

少,票迟一些卖完。

  可见,利用扩展Thread类创建的多个线程,虽然执行的是相同的代码,但彼此相互独立,且各自拥有自己的资源,互不干扰。

针对某一个类只是用一次的情况

匿名内部类的类形式

public class MainThread {
    public static void main(String[] args) {
//        匿名内部类类形式
        new Thread() {
            public void run() {
                for (int i = 0; i < 40; i++) {
                    System.out.println(Thread.currentThread().getName() + "  类形式");
                }
            }
        }.start();
    }
}

字节码文件

final class MainThread$1 extends Thread {
    MainThread$1() {
    }

    public void run() {
        for(int i = 0; i < 40; ++i) {
            System.out.println(Thread.currentThread().getName() + "  类形式");
        }

    }
}

3.实现Runnable接口

public class MainThread {
    public static void main(String[] args) {
        MyThread w1 = new MyThread();
        Thread t1=new Thread(w1,"Window1");
        t1.start();
        
        MyThread w2 = new MyThread();
        Thread t2=new Thread(w2,"Window2");
        t2.start();
    }
}
public class MyThread implements  Runnable {
    private int n = 10;//每个线程都拥有10张票

    public MyThread() {
    }

    @Override
    public void run() {
        while (n > 0) {
            sales();
        }
    }

    private void sales() {
        if (n > 0) {
            System.out.println(Thread.currentThread().getName() + " sales NO." + n);
            n--;
        }
    }
}

结果:同上(大同小异)

由于这两个线程也是彼此独立,各自拥有自己的资源,即10张票,因此程序输出的结果和 1 结果大同小异。均是各自线程对自己的10张票进行单独的处理,互不影响。

可见,只要现实的情况要求保证新建线程彼此相互独立,各自拥有资源,且互不干扰,采用哪个方式来创建多线程都是可以的。因为这两种方式创建的多线程程序能够实现相同的功能。

针对某一个类只是用一次的情况

匿名内部类的接口形式

public class MainThread {
    public static void main(String[] args) {
//        匿名内部类接口形式
        new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 40; i++) {
                    System.out.println(Thread.currentThread().getName() + "  接口形式");
                }
            }
        }).start();
    }
}

字节码文件

final class MainThread$1 implements Runnable {
    MainThread$1() {
    }

    public void run() {
        for(int i = 0; i < 40; ++i) {
            System.out.println(Thread.currentThread().getName() + "  接口形式");
        }

    }
}

4.实现Callable接口 

实现callable接口并使用Future类来包装Callable实现类的对象,且以此Future对象作为Thread对象的target来创建线程。

import java.util.concurrent.*;

public class MainThread {
    public static void main(String[] args) throws Exception {
        System.out.println(Thread.currentThread().getName());//main
        //future包装类
        MyThread m = new MyThread();
        FutureTask<Integer> f = new FutureTask<Integer>(m);
        Thread t = new Thread(f,"Window1");
        t.start();
        //调度服务ExecutorService
/*      ExecutorService ser= Executors.newFixedThreadPool(2);
        MyThread m1 = new MyThread();
        Future<Integer> f1=ser.submit(m1);
        int num1 = f1.get();
        ser.shutdownNow();*/
    }
}
import java.util.concurrent.Callable;

public class MyThread implements Callable<Integer> {
    private int n = 10;

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        while (n > 0) {
            sum += sales();
        }
        return sum;
    }

    private Integer sales() {
        if (n > 0) {
            System.out.println(Thread.currentThread().getName() + " sales NO." + n);
            n--;
            return n;
        } else {
            return 0;
        }
    }
}

结果:同上(大同小异)

Callable 和 Future接口 
Callable是类似于Runnable的接口,实现*Callable接口的类和实现Runnable的类都是可被其它线程执行的任务*。 
优点:可以返回值,可以抛异常。 
缺点:实现繁琐。

5.Callable和Runnable的不同点

(1)Callable规定的方法是call(),而Runnable规定的方法是run().

(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。

(3)call()方法可抛出异常,而run()方法是不能抛出异常的。

(4)运行Callable任务可拿到一个Future对象Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。

6.Runnable相比Thread优势

实例:卖票

public class MainThread {
    public static void main(String[] args) {
        MyThread w = new MyThread();//实例一个
        Thread t1=new Thread(w,"Window1");
        t1.start();
        Thread t2=new Thread(w,"Window2");
        t2.start();
    }
}
public class MyThread implements  Runnable {
    private int n = 10;//一共拥有10张票

    public MyThread() {
    }

    @Override
    public void run() {
        while (n > 0) {
            sales();
        }
    }

    private synchronized void sales() {
        if (n > 0) {
            try {
                System.out.println(Thread.currentThread().getName() + " sales NO." + n);
                n--;
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

结果:

Window1 sales NO.10
Window1 sales NO.9
Window2 sales NO.8
Window1 sales NO.7
Window2 sales NO.6
Window1 sales NO.5
Window1 sales NO.4
Window1 sales NO.3
Window1 sales NO.2
Window2 sales NO.1

结果正如前面分析的那样,程序在内存中仅创建了一个资源,而新建的两个线程都是基于访问这同一资源的,并且由于每个线程上所运行的是相同的代码,因此它们执行的功能也是相同的。

  可见,如果现实问题中要求必须创建多个线程来执行同一任务,而且这多个线程之间还将共享同一个资源,那么就可以使用实现Runnable接口的方式来创建多线程程序。而这一功能通过扩展Thread类是无法实现的,为什么?

  实现Runnable接口相对于扩展Thread类来说,具有无可比拟的优势。这种方式不仅有利于程序的健壮性,使代码能够被多个线程共享,而且代码和数据资源相对独立,从而特别适合多个具有相同代码的线程去处理同一资源的情况。这样一来,线程、代码和数据资源三者有效分离,很好地体现了面向对象程序设计的思想。因此,几乎所有的多线程程序都是通过实现Runnable接口的方式来完成的。

原文地址:https://www.cnblogs.com/manusas/p/6386593.html