面试八--多线程(一)线程创建的四种方法

1 进程和线程的概念

进程是程序的运行实例,线程是进程中独立执行的最小单位

2 线程的创建、启动与应用

在Java平台中创建一个线程就是创建一个Thread类的实例。线程的任务处理可以在Thread类中的run方法直接实现或者通过该方法进行调用。

Thread类有两种常用的构造器:Thread()和Thread(Runnable target)。相应的java 语言创建线程有两种方式。创建新执行线程有两种方法。一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。创建对象,开启线程。run方法相当于其他线程的main方法(继承)。另一种方法是声明一个实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程(实现接口)。

2.1 创建线程方式一继承Thread类

1 定义一个类继承Thread。

2 重写run方法。

3 创建子类对象,就是创建线程对象。

4 调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法。

public class Demo01 {
	public static void main(String[] args) {
		//创建自定义线程对象
		MyThread mt = new MyThread("新的线程!");
		//开启新线程
		mt.start();
		//在主方法中执行for循环
		for (int i = 0; i < 10; i++) {
			System.out.println("main线程!"+i);
		}
	}
}
public class MyThread extends Thread {
	//定义指定线程名称的构造方法
	public MyThread(String name) {
		//调用父类的String参数的构造方法,指定线程的名称
		super(name);
	}
	/**
	 * 重写run方法,完成该线程执行的逻辑
	 */
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(getName()+":正在执行!"+i);
		}
	}
}

2.2 创建线程方式二实现Runnable接口

1、定义类实现Runnable接口。

2、覆盖接口中的run方法。。

3、创建Thread类的对象

4、将Runnable接口的子类对象作为参数传递给Thread类的构造函数。

5、调用Thread类的start方法开启线程。

public class Demo02 {
	public static void main(String[] args) {
		//创建线程执行目标类对象
		Runnable runn = new MyRunnable();
		//将Runnable接口的子类对象作为参数传递给Thread类的构造函数
		Thread thread = new Thread(runn);
		Thread thread2 = new Thread(runn);
		//开启线程
		thread.start();
		thread2.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("main线程:正在执行!"+i);
		}
	}
}
public class MyRunnable implements Runnable{

	//定义线程要执行的run方法逻辑
	@Override
	public void run() {
		
		for (int i = 0; i < 10; i++) {
			System.out.println("我的线程:正在执行!"+i);
		}
	}
}

2.3 两种创建方式的区别,哪种方式更好一些?

Runnalble方式更好一些,因为

(1)实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。继承Thread类,线程对象和线程任务耦合在一起。实现Runnable接口的方式是低耦合的。(2)java不支持多继承,但是支持多实现,因此要想有多个父类,Runnalbe更合适

2.4 创建线程方式三  通过Callable和FutureTask创建线程

      Callable接口:与Runnable接口功能相似,唯一的区别是它能够返回值,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。
     a:创建Callable接口的实现类 ,并实现Call方法
     b:创建Callable实现类的实现,使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的Call方法的返回值
     c:使用FutureTask对象作为Thread对象的target创建并启动线程
     d:调用FutureTask对象的get()来获取子线程执行结束的返回值
通过线程池中的线程对象,使用Callable接口完成两个数求和操作:

class MyCallable implements Callable<Integer> {
	//成员变量
	int x = 5;
	int y = 3;
	//构造方法
	public MyCallable(){
	}
	public MyCallable(int x, int y){
		this.x = x;
		this.y = y;
	}

	@Override
	public Integer call() throws Exception {
		return x+y;
	}
}

public class ThreadPoolDemo {
	public static void main(String[] args) {
		MyCallable c = new MyCallable(100, 200);
		MyCallable c2 = new MyCallable(10, 20);
		FutureTask<Integer> f1 = new FutureTask<Integer>(c);
		FutureTask<Integer> f2 = new FutureTask<Integer>(c2);
		Thread t=new Thread(f1);
		Thread t2=new Thread(f2);
		t.start();
		t2.start();
		Integer sum,sum2;
		try {
			sum = f1.get();
			System.out.println("sum=" + sum);
			sum2= f2.get();
			System.out.println("sum2=" + sum2);
		} catch (InterruptedException | ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
			
	}

}

    2.5 创建线程方式三  通过线程池创建线程

通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。        

面试八--多线程(一)线程创建的四种方法

我们使用线程池创建2个线程:

  1. Executors:线程池创建工厂类  
  2. public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象面试八--多线程(一)线程创建的四种方法
  3. ExecutorService:线程池类


public class ThreadPoolTest {
    public static void main(String[] args) {
        //创建线程池对象
        ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
        //创建Runnable实例对象
        MyRunnable r = new MyRunnable();
        
        service.execute(r);
        service.execute(r);
        
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我要一个教练");
        
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("教练来了: " +Thread.currentThread().getName());
        System.out.println("教我游泳,交完后,教练回到了游泳池");
    }
}

 面试八--多线程(一)线程创建的四种方法

求两个数的之和:

public class ThreadPoolDemo {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//创建线程池对象
		ExecutorService threadPool = Executors.newFixedThreadPool(2);
		
		//创建一个Callable接口子类对象
		//MyCallable c = new MyCallable();
		MyCallable c = new MyCallable(100, 200);
		MyCallable c2 = new MyCallable(10, 20);
		
		//获取线程池中的线程,调用Callable接口子类对象中的call()方法, 完成求和操作
		//<Integer> Future<Integer> submit(Callable<Integer> task)
		// Future 结果对象
		Future<Integer> result = threadPool.submit(c);
		//此 Future 的 get 方法所返回的结果类型
		Integer sum = result.get();
		System.out.println("sum=" + sum);
		
		//再演示
		result = threadPool.submit(c2);
		sum = result.get();
		System.out.println("sum=" + sum);
		//关闭线程池(可以不关闭)
		
	}
}
public class MyCallable implements Callable<Integer> {
	//成员变量
	int x = 5;
	int y = 3;
	//构造方法
	public MyCallable(){
	}
	public MyCallable(int x, int y){
		this.x = x;
		this.y = y;
	}

	@Override
	public Integer call() throws Exception {
		return x+y;
	}
}

3 Thread的常用方法

面试八--多线程(一)线程创建的四种方法

面试八--多线程(一)线程创建的四种方法

1.线程休眠sleep();:线程有优先级,但是我们可以用此方法人为的改变它们的优先级,让线程暂停,它其他线程获得分配空间。
用法:Thread.sleep(2000);//休眠两秒
2.线程让步yield();就是让出自己的分配空间给其他线程,那么问题来了,让步多久呢?如果有两条线程的话,是不是让步到另外一条线程执行完呢?经测试,不是让另外的线程执行,让步时间是不确定的;
注意:当某个线程调用yield()方法之后,只有与当前线程优先级形同或者更高的才能获得执行机会。 (直接进入可运行状态,有可能调用完自己又被CPU调度到,又直接变成运行状态了。)
用法:一般都是指定条件,如if(i==10){Thread.yield();}
3.线程插队join():当某个程序调用其他线程的join()时,调用线程将会阻塞,直到插入的线程运行完毕,才运行该线程,如main线程中for(int i = 0; i < 100; i++){if(i == 2){t.join();}},当i等于2时,线程t将执行完毕再执行main中余下的 i= 3i=4 ……
用法:一般都是指定条件,如if(i==10){t.join();}注意插队肯定是在别的线程中插别人的队,不可能在自己的线程中写join(); 如:t线程中写t.join();,这种方法是不正确的。

4 实践:开启5个线程从网站上下载文件保存到本地

package createThread;

import io.github.viscent.mtia.util.Debug;
import io.github.viscent.mtia.util.Tools;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;

public class FileDownloaderApp {

  public static void main(String[] args) {
    Thread downloaderThread = null;
    for (String url : args) {
      // 创建文件下载器线程
      downloaderThread = new Thread(new FileDownloader(url));
      // 启动文件下载器线程
      downloaderThread.start();
    }
  }

  // 文件下载器
  static class FileDownloader implements Runnable {
    private final String fileURL;

    public FileDownloader(String fileURL) {
      this.fileURL = fileURL;
    }

    @Override
    public void run() {
      Debug.info("Downloading from " + fileURL);
      String fileBaseName = fileURL.substring(fileURL.lastIndexOf('/') + 1);
      try {
        URL url = new URL(fileURL);
        String localFileName = System.getProperty("java.io.tmpdir")
            + "/viscent-"
            + fileBaseName;
        Debug.info("Saving to: " + localFileName);
        downloadFile(url, new FileOutputStream(
            localFileName), 1024);
      } catch (Exception e) {
        e.printStackTrace();
      }
      Debug.info("Done downloading from " + fileURL);
    }

    // 从指定的URL下载文件,并将其保存到指定的输出流中
    private void downloadFile(URL url, OutputStream outputStream, int bufSize)
        throws MalformedURLException, IOException {
      // 建立HTTP连接
      final HttpURLConnection httpConn = (HttpURLConnection) url
          .openConnection();
      httpConn.setRequestMethod("GET");
      ReadableByteChannel inChannel = null;
      WritableByteChannel outChannel = null;
      try {
        // 获取HTTP响应码
        int responseCode = httpConn.getResponseCode();
        // HTTP响应非正常:响应码不为2开头
        if (2 != responseCode / 100) {
          throw new IOException("Error: HTTP " + responseCode);
        }

        if (0 == httpConn.getContentLength()) {
          Debug.info("Nothing to be downloaded " + fileURL);
          return;
        }
        inChannel = Channels
            .newChannel(new BufferedInputStream(httpConn.getInputStream()));
        outChannel = Channels
            .newChannel(new BufferedOutputStream(outputStream));
        ByteBuffer buf = ByteBuffer.allocate(bufSize);
        while (-1 != inChannel.read(buf)) {
          buf.flip();
          outChannel.write(buf);
          buf.clear();
        }
      } finally {
        // 关闭指定的Channel以及HttpURLConnection
        Tools.silentClose(inChannel, outChannel);
        httpConn.disconnect();
      }
    }// downloadFile结束
  }// FileDownloader结束
}

面试八--多线程(一)线程创建的四种方法

 面试八--多线程(一)线程创建的四种方法