Java多线程

实现方式

JAVA中,实现多线程主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。前两种方式线程执行完后都没有返回值,最后一种是带返回值的。

继承Thread类

这种方式是很常见的多线程实现方式。通过Thread子类实例的start()方法来启动线程。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。

其实java.lang.Thread本质上也是实现了Runnable接口的一个实例,打开Thread类的源码如下。

public class java.lang.Thread implements java.lang.Runnable {
继承Thread,覆写run()。

public class SubThread extends Thread {
	private int name;
	
	public SubThread(int name) {
		this.name = name;
	}

	@Override
	public void run() {
		System.out.println("Thread name : " + this.name); 
	}
}
之后我们就可以创建Thread实例,并通过start()启动线程。

public class Test {

	public static void main(String[] args) {
		
		for(int i = 0; i< 10; i++) {
			SubThread subT = new SubThread(i);
			subT.start();
		}
	}
}

实现Runnable接口

在java中,很多情况下,都是多用接口,少用继承。至于为什么这么干,写多了你就明白了。打开Runnable接口发现源码很简单,里面只有一个run()。

public abstract interface java.lang.Runnable {
  
  // Method descriptor #1 ()V
  public abstract void run();
}

public class SecondThread implements Runnable {

	private int name;

	public SecondThread(int name) {
		this.name = name;
	}

	@Override
	public void run() {
		System.out.println("Thread name : " + this.name);

	}
}

而我们只需要覆写改方法即可。只是启动线程时仍然需要一个Thread实例。

public class Test {

	public static void main(String[] args) {
		
		for(int i = 0; i< 10; i++) {
			SecondThread subT = new SecondThread(i);
			Thread thread = new Thread(subT);
			thread.start();
		}
	}
}
所以从本质上讲,两种实现方式区别不大。

但有种情况值得注意,即两个Thread共享同一个target时。此时应考虑使用synchronized。

public class TestV {

	public static void main(String[] args) {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ": " + i);
			SubRunnable threadB = new SubRunnable();
			new Thread(threadB, "threadB-1").start();
			new Thread(threadB, "threadB-2").start();
		}
	}
}
public class SubRunnable implements Runnable {
	private int i;

	@Override
	public void run() {
		for (; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ": " + i);
		}
	}
}
此时对于变量i的访问是线程不安全的。会造成输出跟我们期待的不一致。


Callable

第三种就是使用ExecutorService、Callable、Future了。这种方式最大的区别是它可以从任务中产生返回值。Callable是Java SE5引入的一种具有类型参数的泛型,它的类型参数表示的是从call()中返回的值。此外,它还要由ExecutorService的实例方法submit()来调用。因此它的实现与前两种还是有点区别的。

package com.zw;

import java.util.concurrent.Callable;

public class CallableThread implements Callable<String> {
	private String name;

	public CallableThread(String name) {
		this.name = name;
	}

	@Override
	public String call() throws Exception {
		return "Result : " + this.name + ", id : "+ Thread.currentThread().getId();
	}

}
这里让其返回String。

package com.zw;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Test {

	public static void main(String[] args) throws InterruptedException,
			ExecutionException {

		// 创建一个线程池
		ExecutorService pool = Executors.newCachedThreadPool();
		// 创建多个有返回值的任务
		List<Future> list = new ArrayList<Future>();

		for (int i = 0; i < 10; i++) {
			Callable c = new CallableThread(i);

			Date start = new Date();
			// 执行任务并获取Future对象
			Future f = pool.submit(c);
			Date end = new Date();
			System.out.println(end.getTime() - start.getTime());
			
			list.add(f);
			// 关闭线程池
//			pool.shutdown();
		}
		
		// 获取所有并发任务的运行结果
		for (Future re : list) {
			Date start = new Date();
			// 从Future对象上获取任务的返回值,并输出到控制台
			System.out.println(">>>" + re.get().toString());
			Date end = new Date();
			System.out.println(end.getTime() - start.getTime());
		}
		//for循环中的所有子线程都返回时,才会继续往下执行。
		System.out.println("All Sub Thread has beed done.");
	}
}

这里re.get()还没有返回时,则发生阻塞。

下面进行验证。

在函数call()中让线程sleep一段时间。

@Override  
    public String call() throws Exception {
    	Thread.sleep(this.name * 1000);
        return "Result : " + this.name + ", id : "+ Thread.currentThread().getId();  
    }


在ExecutorService的submit()前后进行时间计算。

Date start = new Date();
			// 执行任务并获取Future对象
			Future f = pool.submit(c);
			Date end = new Date();
			System.out.println(end.getTime() - start.getTime());
在Future的实例方法get()前后计算时间。

// 获取所有并发任务的运行结果
		for (Future re : list) {
			Date start = new Date();
			// 从Future对象上获取任务的返回值,并输出到控制台
			System.out.println(">>>" + re.get().toString());
			Date end = new Date();
			System.out.println(end.getTime() - start.getTime());
		}

结果


第一部分输出,时间都很短,说明submit()只是把线程放到线程池中。

第二部分输出很慢,证明get()方法执行时,会阻塞,直到其返回。







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值