线程创建的3种方法
1. 继承Thread类
创建线程的一个方法是:将一个类声明为一个Thread的子类。 这个子类应该重写run类的方法Thread 。 然后可以分配并启动子类的实例。 例如,计算大于规定值的素数的线程可以写成如下:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,以下代码将创建一个线程并启动它运行:
PrimeThread p = new PrimeThread(143);
p.start();
2. 实现Runnable接口
创建线程的另一种方法是声明一个实现Runnable接口的类。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动。 例如,计算大于规定值的素数的线程可以写成如下:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,以下代码将创建一个线程并启动它运行:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
3. 实现Callable接口
Callable接口是java.util.concurrent(简称JUC)包中的内容,属于比较高级的内容,操作比较复杂,不适合初学者使用。实现Callable接口需要重写call()方法,可以抛出异常,有返回值。下面的代码表示使用Callable接口,多线程下载网络图片。
package com.sxt.thread;
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;
/**
* 了解创建线程的方法三:实现Callable,重写call方法
* 实现多线程下载网络图片
* 2019/7/10 15:10
*/
public class CDownloader implements Callable<Boolean>{
private String url;//远程路径
private String name;//存储名字
public CDownloader(String url, String name) {
super();
this.url = url;
this.name = name;
}
@Override
public Boolean call() throws Exception{
WebDownloader wd = new WebDownloader();
wd.download(url, name);
System.out.println(name);
return true;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
//启动三个线程
CDownloader cd1 = new CDownloader("http://p1.so.qhimgs1.com/sdr/400__/t01a9f7f11254e3cffd.jpg","beauty.jpg");
CDownloader cd2 =new CDownloader("http://p2.so.qhimgs1.com/bdr/326__/t01454978771ac6120e.jpg","雷克萨斯.jpg");
CDownloader cd3 =new CDownloader("http://p5.so.qhimgs1.com/bdr/326__/t01d6431991688ea34b.jpg","汪星人.jpg");
//创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行:
Future<Boolean> result1 = ser.submit(cd1);
Future<Boolean> result2 = ser.submit(cd2);
Future<Boolean> result3 = ser.submit(cd3);
//获取结果:
boolean r1 = result1.get();
boolean r2 = result2.get();
boolean r3 = result3.get();
System.out.println(r3);
//关闭服务:
ser.shutdown();
}
}
package com.sxt.thread;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.commons.io.FileUtils;
/**
* 使用commons-io下载图片
* @author asus
* 2019/7/5 14:38
*/
public class WebDownloader{
/**
* 下载
* @param url
* @param name
*/
public void download(String url,String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("不合法的url");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("下载失败");
}
}
}
线程创建总结
少用继承多用实现,JAVA是单继承,假设一个类继承Thread类,后期不得不继承其他类,需要大量重构代码。推荐使用实现Runnable接口创建线程。
线程重要知识点
-
线程一旦启动,人为无法干涉,由操作系统CPU调度器自动调取。
-
线程分为:用户线程和守护线程。用户线程必须执行完才能停下,守护线程是用来保护用户线程的,不必执行完就能停下。
-
线程优先级高并不代表一定会执行,只是执行的概率会大一点。
-
调用start()方法不会立即执行;run()方法是线程入口点。
-
注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即一个CPU的情况下,在同一个时间点,CPU只 能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
-
在程序运行时,即使没有自己创建线程,后台也会存在多个线程,如gc线程、主线程。
-
main()称之为主线程,为系统的入口点,用于执行整个程序。
-
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
-
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
-
线程会带来额外的开销,如CPU调度时间,并发控制开销。
-
每个线程在自己的工作内存交互,加载和存储主内存控制不当会造成数据不一致。