JAVA多线程实现的几种方式及简单示例

 JAVA多线程的实现方式是JAVA基础的一个重点,看过好多次,但不经常用就容易忘记,今天做一个总结,算是把JAVA基础再夯实一下。

Java多线程实现方式主要有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。

1、继承Thread类创建线程
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:

public class MyThread extends Thread {
	public void run() {
		while(true) {
			System.out.println(this.getName()+" running...");
			try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
		}
	}
	public static void main(String[]args) {
		MyThread thread1 = new MyThread();
		MyThread thread2 = new MyThread();
		thread1.start();
		thread2.start();
	}
}

//执行结果
Thread-1 running...
Thread-0 running...
Thread-1 running...
Thread-0 running...
Thread-1 running...
Thread-0 running...
Thread-1 running...
Thread-0 running...
Thread-1 running...
Thread-0 running...
Thread-1 running...
Thread-0 running...

注意:此时除了创建的两个线程外,还有main线程也就是主线程在执行,另外还有负责垃圾回收的线程同样在执行,这里不予研究。线程的名字在创建线程是默认配置,也可以在创建线程时传入线程名字,例如

 MultiThreadDemo td1 = new MultiThreadDemo("t1"); // 指定线程的名字

2、实现Runnable接口

实现Runnable接口也是一种常见的创建线程的方式。使用接口的方式可以让我们的程序降低耦合度。Runnable接口中仅仅定义了一个方法,就是run。

其实Runnable就是一个线程任务,线程任务和线程的控制分离,这也就是上面所说的解耦。我们要实现一个线程,可以借助Thread类,Thread类要执行的任务就可以由实现了Runnable接口的类来处理。 这就是Runnable的精髓之所在!

例如:

//集成Runnable接口定义任务类
public class ThreadTask implements Runnable {

	@Override
	public void run() {
			while(true) {
			System.out.println(Thread.currentThread().getName()+" is running...");
			try {
	            Thread.sleep(1000);
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
		}
	}
}


//在main方法中创建线程去完成该任务
public class Main extends ThreadTask {
	public static void main(String[]args) {
		ThreadTask task = new ThreadTask();//新建任务类
		Thread t1 = new Thread(task);
		t1.start();
		Thread t2 = new Thread(task);
		t2.start();
	}
}

下面通过多个售票员同时执行售票任务的例子来认识多线程

//创建售票任务
public class TicketSailTask implements Runnable{
	public static int number = 100;//总共有100张票
	public Object lock = new Object();//创建锁,防止多个线程同时卖同一张票
	@Override
	public void run() {
		while(true) {
			//进行同步
			synchronized(lock) {
				try{
					Thread.sleep(100);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
			if(number>0) {
				System.out.println(Thread.currentThread().getName()+"正在卖第"+number+"张票");
				number--;
			}else {
				break;
			}
		}
	}
}

//创建多个线程,执行任务
public class Main extends ThreadTask {
	public static void main(String[]args) {
		TicketSailTask task = new TicketSailTask();//新建任务类
		Thread t1 = new Thread(task,"小王");
		t1.start();
		Thread t2 = new Thread(task,"小刘");
		t2.start();
		Thread t3 = new Thread(task,"小李");
	}
}

//执行结果
小王正在卖第100张票
小刘正在卖第99张票
小刘正在卖第98张票
小王正在卖第97张票
小刘正在卖第96张票
小王正在卖第95张票
小刘正在卖第94张票
小刘正在卖第93张票
小王正在卖第92张票
小刘正在卖第91张票
小王正在卖第90张票
小刘正在卖第89张票
小王正在卖第88张票
小刘正在卖第87张票
。。。。

3、实现Callable接口实现多线程

与实现Runnable接口实现多线程类似,也有不同

相同点:
两者都可用来编写多线程程序;
两者都需要调用Thread.start()启动线程;
 

不同点:

两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

package com.callable.runnable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableImpl implements Callable {

    public CallableImpl(String acceptStr) {
        this.acceptStr = acceptStr;
    }

    private String acceptStr;

    @Override
    public String call() throws Exception {
        // 任务阻塞 1 秒
        Thread.sleep(1000);
        return this.acceptStr + " append some chars and return it!";
    }


    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable callable = new CallableImpl("my callable test!");
        FutureTask task = new FutureTask<>(callable);
        long beginTime = System.currentTimeMillis();
        // 创建线程
        new Thread(task).start();
        // 调用get()阻塞主线程,反之,线程不会阻塞
        String result = task.get();
        long endTime = System.currentTimeMillis();
        System.out.println("hello : " + result);
        System.out.println("cast : " + (endTime - beginTime) / 1000 + " second!");
    }
}

//执行结果

hello : my callable test! append some chars and return it!
cast : 1 second!

Process finished with exit code 0

4、通过线程池来实现多线程

public class ThreadDemo05{

    private static int POOL_NUM = 10;     //线程池数量

    /**
     * @param args
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        ExecutorService executorService = Executors.newFixedThreadPool(5);  
        for(int i = 0; i

这里增加一点线程状态的知识

线程总共有5大状态,通过上面第二个知识点的介绍,理解起来就简单了。

新建状态:新建线程对象,并没有调用start()方法之前

就绪状态:调用start()方法之后线程就进入就绪状态,但是并不是说只要调用start()方法线程就马上变为当前线程,在变为当前线程之前都是为就绪状态。值得一提的是,线程在睡眠和挂起中恢复的时候也会进入就绪状态哦。

运行状态:线程被设置为当前线程,开始执行run()方法。就是线程进入运行状态

阻塞状态:线程被暂停,比如说调用sleep()方法后线程就进入阻塞状态

死亡状态:线程执行结束

你可能感兴趣的:(JAVA基础)