线程的创建与使用

在Java中,创建多线程有多种方式,JDK1.5之前,有两种创建线程的方式:

  • 继承Thread
  • 实现Runnable接口

JDK1.5之后有新增了两种创建多线程的方式:

  • 实现Callable
  • 使用线程池

Thread

Thread类的特性

  • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为 线程体

  • 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()

构造器

  • Thread() :创建新的Thread对象
  • Thread(String threadname): :创建线程并指定线程实例名
  • Thread(Runnable target) :指定创建线程的目标对象,它实现了Runnable接口中的run方法
  • Thread(Runnable target, String name) :创建新的Thread对象

创建线程

通过继承Thread创建线程步骤:

  • 创建Thread的子类
  • 重写run方法
  • 主线程创建Thread子类的实例
  • 执行start方法
class Capture extends Thread
{

    @Override
    public void run()
    {
        System.out.println("我是线程" + Thread.currentThread().getName() + ",我正在执行爬虫任务");
    }
}

public class T1 {
    public static void main(String[] args) {
        Capture capture = new Capture();
        capture.start();

        // 再开线程跑爬虫程序,需要再重新创建capture实例
        // 并执行start方法
        Capture capture1 = new Capture();
        capture1.start();
    }
}

注意:一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。

Thread类常用方法

Modifier and Type Method Description
static Thread currentThread() Returns a reference to the currently executing thread object.
String getName() Returns this thread’s name.
int getPriority() Returns this thread’s priority.
boolean isAlive() Tests if this thread is alive.
static boolean interrupted() Tests whether the current thread has been interrupted.
void join() Waits for this thread to die.
void join(long millis) Waits at most millis milliseconds for this thread to die.
void join(long millis, int nanos) Waits at most millis milliseconds plus nanos nanoseconds for this thread to die.
void setName(String name) Changes the name of this thread to be equal to the argument name.
void setPriority(int newPriority) Changes the priority of this thread.
static void sleep(long millis) Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.
void start() Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.
static void yield() A hint to the scheduler that the current thread is willing to yield its current use of a processor.

线程优先级

Java线程调度有两种方法:时间片、抢占式(高优先级的线程抢占CPU)

Java 的调度方法:

  • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略;
  • 对高优先级,使用优先调度的抢占式策略

线程的优先级等级(默认为5,最小为1,最大为10)

  • MAX_PRIORITY :10
  • MIN _PRIORITY :1
  • NORM_PRIORITY :5

线程创建时继承父线程的优先级;低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用。

Thread1 thread1 = new Thread1();
thread1.setPriority(1);

Thread2 thread2 = new Thread2();
thread2.setPriority(10);

thread1.start();
thread2.start();

Runnable

使用继承Thread的方法来创建线程,但该方法有单继承的局限性。

使用实现Runnable接口的方式要优于使用继承Thread,下面看看如何使用实现Runnable接口的方式创建线程:

  • 定义子类:实现Runnable接口
  • 子类重写run方法
  • 通过Thread类含参构造器创建线程对象。
  • 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
  • 调用Thread类的start方法:开启线程
// 窗口卖票,总有100张
// 共开启三个窗口

class Window implements Runnable
{
    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + ":" + ticket--);
            } else {
                break;
            }
        }
    }
}

public class WindowTest {

    public static void main(String[] args) {
        Window window  = new Window();
        Thread thread1 = new Thread(window);
        Thread thread2 = new Thread(window);
        Thread thread3 = new Thread(window);

        thread1.setName("window1");
        thread2.setName("window2");
        thread3.setName("window3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

Callable

与使用Runnable相比, Callable功能更强大些

  • 相比run()方法,可以有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果
public class FutureTask<V> implements RunnableFuture<V> 
public interface RunnableFuture<V> extends Runnable, Future<V> 

FutureTask是Future接口的唯一实现类。Future接口可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。 FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

使用实现Callable创建线程的步骤:

  1. 创建一个实现Callable接口的类
  2. 创建Callable接口的实现类的对象
  3. 将此Callable接口实现类的对象传递到FutureTask构造器中,创建FutureTask的对象
  4. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
  5. 如果需要获取Callable中call方法的返回值,则调用FutureTask的get方法获取。
class Window3 implements Callable
{
    private static int ticket = 100;

    @Override
    public Object call()  {
        while (true) {
            if (ticket > 0) {
                // 模拟卖票业务,每次卖票耗时0.1s
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + ":票号" + ticket--);
            } else {
                break;
            }
        }

        return null;
    }
}

public class Window3Test {
    public static void main(String[] args) {
        FutureTask[] tasks = new FutureTask[3];

        for (int i = 0; i < 3; i++)
        {
            Callable callable = new Window3();

            tasks[i] = new FutureTask(callable);

            Thread t = new Thread(tasks[i]);
            t.start();
        }
    }
}

线程池

经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。提前 创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

优点:

提高响应速度(减少了创建新线程的时间)

降低资源消耗(重复利用线程池中线程,不需要每次都创建)

便于线程管理

  • corePoolSize:核心池的大小
  • maximumPoolSize:最大线程数
  • keepAliveTime:线程没有任务时最多保持多长时间后会终止

在实际开发中,需要建立多线程的情况下,基本都是使用创建线程池方法

JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

  • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
  • Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
  • void shutdown() :关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

  • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
  • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
  • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
  • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
class ThreadNum implements Runnable
{

    @Override
    public void run() {
        for (int i = 2; i <= 100 ; i ++) {
            int j;
            for ( j = 2; j <=(int) Math.sqrt(i); j++) {
                if (i % j == 0) {
                    break;
                }
            }

            if (j > (int) Math.sqrt(i)) {
                System.out.println(i);
            }
        }
    }
}

public class ThreadPool {

    public static void main(String[] args) {
        //1. 提供指定线程数量的线程池
        ExecutorService    service  = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //设置线程池的属性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();

        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new ThreadNum());//适合适用于Runnable
        service.execute(new ThreadNum());//适合适用于Runnable

//        service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }
}