Java学习笔记之多线程

Java学习笔记之多线程

  • 实现多线程方法
    • 继承Thread
    • 通过 Runnable接口
    • 通过Callable接口
    • 继承Thread方法与Runnable接口比较
    • Runnable与Callable

实现多线程方法

正在学习《第一行代码Java》简略写一下学习笔记,记录自己的学习过程。书中指出三种多线程实现方法:继承Thread、使用Runnable接口、使用Callable接口,下面是使用和对比。

继承Thread

class MyThread extends Thread {
	@Override
	public void run(){
	//线程运行部分
	}
}

继承Thread类,通过start()方法调用

public class ThreadTest {
	public static void main(String[] args) throws Exception{
		MyThread mt = new MyThread();
		mt.start();
	}
}

通过 Runnable接口

class MyThread implements Runnable {
	@Override
	public void run(){
	//线程运行部分
	}
}

接口中没有start()方法启动,所以通过Thread的构造方法Thread(Runnable target)启动

public class ThreadTest {
	public static void main(String[] args) throws Exception{
		MyThread mt = new MyThread();
		new Thread(mt).start();
	}
}

通过Callable接口

Callable接口相较于Runnable,使用call()替代run()方法,多加了线程返回值,具体优劣后文会有描述。

public class MyThread implements Callable<Params> {
	@Override
	public Params call() throws Exception {
		//线程运行部分
		return Params;
	}
}

Callable接口需要通过FutureTask使用,实现代码如下:

public class ThreadTest {
	public static void main(String[] args) throws Exception{
		MyThread mt = new MyThread();
		FutureTask<Params> task = new FutureTask<Params>(mt);
		new Thread(task).start();
		Params result = task.get();
		//Params result = task.get(long timeout,TimeUnit timeunit);
	}
}

通过get()方法可以获得Callable的返回值,也可以调用get(long timeout,TimeUnit timeunit)方法来进行超时设置。

继承Thread方法与Runnable接口比较

    直接继承Thread方法比较直接且简单,但是由于Java的多继承限制,有一定局限性,使用Runnable接口可以解决多继承的问题,并且,使用Runnable可以较为简单的解决数据共享问题,举个例子:

class MyThread implements Runnable {
	private int chips = 5;
	@Override
	public void run(){
		for(int i = 0;i < 20;i++){
			if(this.chips>0){
				System.out.println("还剩"+this.chips--+"片没吃");
			}
		}
	}
}

实现多线程数据共享

public class ThreadTest {
	public static void main(String[] args) throws Exception{
		MyThread mt = new MyThread();
		new Thread(mt).start();
		new Thread(mt).start();
		new Thread(mt).start();
	}
}

运行结果:

还剩5片没吃
还剩2片没吃
还剩1片没吃
还剩3片没吃
还剩4片没吃

    可以看到一共吃了5片,三个线程的数据是共享的,但是顺序不对,出现了数据错乱问题,这个问题可以通过同步解决可以参考Synchronized原理详解。
经过同步(synchronized)调整如下:

class MyThread implements Runnable {
	private int chips = 5;
	@Override
	public void run(){
		for(int i = 0;i < 20;i++){
			synchronized(this){
				if(this.chips>0){
					System.out.println("还剩"+this.chips--+"片没吃");
				}
			}
		}
	}
}

实例化运行部分不用更改,结果如下:

还剩5片没吃
还剩4片没吃
还剩3片没吃
还剩2片没吃
还剩1片没吃

可见数据错乱已解决,关于synchronized的简要用法和理解在后续会提到。
    由于继承了Thread的线程无法多次调用start(),虽然也可以使用上述同样的方法使用new Thread(mt).start启动线程,但是由于本身就具有start()方法而显得并不合理,就如书上所描述的那样:

两个人在沙漠里走,都只剩下最后一口水,结果A对B说,把你的水给我喝,我的不喝了。

可见相较于继承Thread方法,Runnable接口可以更好地实现数据共享,但不是唯一。

Runnable与Callable

    Callable相比于Runnable多了线程返回值,但是这个返回值的获取是通过阻塞来实现的。通过调用FutureTask的get方法,阻塞获取线程的返回值。个人认为这个方法只适用用于较少的特定需求,并不是一个可以常用的好方法。

你可能感兴趣的:(Java学习)