多线程(一)
1.线程简介
任务、进程、线程、多线程
多任务,例如:边吃饭边玩手机、边开车边打电话…
进程:在操作系统中运行的程序就是进程,如QQ、播放器、游戏…
线程:例如,播放器可以听见声音,播放的声音就是一个线程
多线程:一个进程可以有多个线程,如视频中同时听声音、看图像、看弹幕…
进程与线程的关系
举个例子:
A:进程 B:进程
a1:A的一个线程 b1:B的一个线程
a2:A的一个线程 b2:B的一个线程
注意1:
- 进程A和进程B的内存独立不共享
- java语言中,A的线程和B的线程在堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈
如:XX游戏是一个进程,XX音乐是一个进程,这两个进程是独立的,不共享资源
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。
注意2:
-
很多的多线程是模拟出来的,真正的多线程是指多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
例如:T1线程执行T1的,T2线程执行T2的,T1不会影响T2,T2也不会影响T1,这是多线程并发。对于多核的CPU电脑来说,真正的多线程并发是没有问题的,例如4核CPU,表示同一时间点上,可以真正的有4个进程并发执行。对于单核的CUP来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换,给人的感觉是同时在做。
-
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程。
package se5.thread;
/*
分析以下程序有几个线程(除垃圾回收线程)
1个线程,因为程序只有1个栈
(注意:一个方法并不是一个线程,如果是,那不扯么?!)
*/
public class ThreadTest01 {
public static void main(String[] args) {
System.out.println("main begin");
m1();
System.out.println("main over");
}
private static void m1() {
System.out.println("m1 begin");
m2();
System.out.println("m1 over");
}
private static void m2() {
System.out.println("m2 begin");
m3();
System.out.println("m2 over");
}
private static void m3() {
System.out.println("m3 execute!");
}
}
2.线程创建
三种创建方式:
- Thread class ------> 继承Thread类(重点)
- Runnable接口 ------> 实现Runnable接口(重点)
- Callable接口 ------> 实现Callable接口(目前作为了解)
第一种方式
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
//定义线程类
public class MyThread extends Thread{
public void run(){...}
}
//创建线程对象
MyThread t = new MyThread();
//启动线程
t.start();
package se5.thread;
/*
实现线程的第一种方式:编写一个类直接继承java.lang.Thread,重写run方法
怎么创建线程对象? new就行了
怎么启动线程? 调用线程对象的start()方法
*/
public class ThreadTest02 {
public static void main(String[] args) {
//这里是main方法,这里的代码属于主线程,在主栈中运行
//新建一个分支流对象
MyThread t = new MyThread();
//启动线程
//start方法的作用是:启动一个线程分支,在JVM中开辟一个新的栈空间,这段代码任务完成之后瞬间就结束了。
//这段代码的任务只是为了开辟一个新的栈空间,只有新的栈空间开出来,start方法就结束了,线程就启动成功了。
//启动成功的线程会自动调用run方法,并且run方法在分支线的栈底部(压栈)
//run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的
t.start();//start方法不结束,是不会往下执行其他代码的
//t.run();//不会启动线程,不会分配新的分支线(这种方法就是单线程)
//这里的代码还是运行在主线程中
for (int i = 0; i < 1000; i++) {
System.out.println("主线程--->" + i);
}
/**
* 部分输出结果:
* 分支线程--->172
* 分支线程--->173
* 主线程--->19
* 分支线程--->174
* 分支线程--->175
*/
}
}
class MyThread extends Thread{
@Override
public void run(){
//编写程序,这段程序运行在分支线程(分支栈)中
for (int i = 0; i < 1000; i++){
System.out.println("分支线程--->"+ i);
}
}
}
图片下载例子
注意:此例子下载了commons-io-2.6工具包
package com.thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习Thread,实现多线程同步下载图片
public class TestThread2 extends Thread{
private String url;//网络图片地址
private String name;//保存的文件名
//有参构造方法
public TestThread2(String url,String name){
this.url = url;
this.name = name;
}
//下载图片线程的执行体
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载的图片文件名是:" + name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("http://img1.gamersky.com/upimg/pic/2018/01/18/201801181052241016_small.jpg","南天之怒.jpg");
TestThread2 t2 = new TestThread2("http://img1.gamersky.com/upimg/pic/2017/06/06/201706061152547794_small.jpg","神拳.jpg");
TestThread2 t3 = new TestThread2("http://img1.gamersky.com/upimg/pic/2018/01/18/201801181052251036_small.jpg","北辰之威.jpg");
//启动线程
t1.start();
t2.start();
t3.start();
/**
* 本次运行结果:
* 下载的图片文件名是:北辰之威.jpg
* 下载的图片文件名是:神拳.jpg
* 下载的图片文件名是:南天之怒.jpg
*/
}
}
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}
第二种方式
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
public class MyRunnable implements Runnable{
public void run(){...}
}
//创建线程对象
Thread t = new Thread(new MyRunnable());
//启动线程
t.start;
package com.thread;
/*
创建线程方式二:实现Runnable接口,重写run()方法,执行线程需要丢入Runnable接口实现类,调用start方法
*/
public class TestThread3 implements Runnable{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 1000; i++) {
System.out.println("卧尼玛--->" + i);
}
}
public static void main(String[] args) {
//main线程,主线程
//创建一个线程对象
TestThread3 testThread3 = new TestThread3();
//创建线程对象,通过线程对象来开启我们的线程,代理。
Thread thread = new Thread(testThread3);
thread.start();
//以上两句可简写为:new Thread(testThread3).start();
for (int i = 0; i < 1000; i++) {
System.out.println("卧你妈--->" + i);
}
/**
* 本次运行结果:
* 卧尼玛--->768
* 卧尼玛--->769
* 卧你妈--->721
* 卧你妈--->722
*/
}
}
图片下载例子也可改为实现Runnable接口的方式
图片下载例子
package com.thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/*
练习Thread,实现多线程同步下载图片
*/
public class TestThread2 implements Runnable{
private String url;//网络图片地址
private String name;//保存的文件名
//有参构造方法
public TestThread2(String url,String name){
this.url = url;
this.name = name;
}
//下载图片线程的执行体
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载的图片文件名是:" + name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("http://img1.gamersky.com/upimg/pic/2018/01/18/201801181052241016_small.jpg","南天之怒.jpg");
TestThread2 t2 = new TestThread2("http://img1.gamersky.com/upimg/pic/2017/06/06/201706061152547794_small.jpg","神拳.jpg");
TestThread2 t3 = new TestThread2("http://img1.gamersky.com/upimg/pic/2018/01/18/201801181052251036_small.jpg","北辰之威.jpg");
/*
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
Thread thread3 = new Thread(t3);
thread1.start();
thread2.start();
thread3.start();
可简写,如下
*/
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
}
}
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}
还有使用匿名内部类的方式,了解下
package se5.thread;
/*
采用匿名内部类方式
*/
public class ThreadTest04 {
public static void main(String[] args) {
//采用匿名内部类方式创建线程对象
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("t线程--->" + i);
}
}
});
//启动线程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("main线程--->" + i);
}
}
}
小结
继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.strat()
- 不建议使用:避免OOP单继承局限性
实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象 + Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
第三种方式,Callable(JDK8新特性,了解即可)
这种方式实现的线程可以获取线程的返回值。之前的两种方式无法获取线程的返回值,因为run方法返回void。
package se5.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;//属于java的并发包,老JDK中没有这个包
/*
实现Callable接口
*/
public class ThreadTest15 {
public static void main(String[] args) throws Exception {
//第一步:创建一个未来任务类对象,这里采用匿名内部类的方式
//参数非常重要,需要给一个Callable接口实现类对象
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {//call()方法相当于run方法,只不过这个有返回值
//线程执行一个任务,执行之后可能会有一个执行结果
//模拟执行
System.out.println("call method begin");
Thread.sleep(1000 * 10);
System.out.println("call method end!");
int a = 100;
int b = 200;
return a + b;//自动装箱(300结果变成Interger)
}
});
//创建线程对象
Thread t = new Thread(task);
//启动线程
t.start();
//这里是main方法,主线程中
//在主线程中如何获取t线程的返回结果?
//get方法的执行会导致当前线程阻塞
Object obj = task.get();
System.out.println("线程执行结果:" + obj);
//main方法这里的程序要执行必须等待get方法的结束
//而get方法可能需要很久,因为get方法是为了拿另一个线程的执行结果
//另一个线程执行是需要时间的
System.out.println("Hello World!");
/**
* 运行结果:
* call method begin
* call method end!
* 线程执行结果:300
* Hello World!
*/
}
}
优点:可以获取到线程的执行结果
缺点:效率比较低,在获取t线程结果的时候,当前线程受阻塞
例子:
package com.thread2;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
/*
练习Thread,实现多线程同步下载图片
*/
public class TestCallable implements Callable {
private String url;//网络图片地址
private String name;//保存的文件名
//有参构造方法
public TestCallable(String url,String name){
this.url = url;
this.name = name;
}
//下载图片线程的执行体
@Override
public Boolean call() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下载的图片文件名是:" + name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("https://img1.gamersky.com/upimg/pic/2018/10/14/201810141820249816_small.jpg","KDA.jpg");
TestCallable t2 = new TestCallable("https://img1.gamersky.com/upimg/pic/2018/07/02/201807021201141166_small.jpg","图2.jpg");
TestCallable t3 = new TestCallable("https://img1.gamersky.com/upimg/pic/2018/09/27/201809271122535513_small.jpg","图3.jpg");
//创建执行服务:ExecutorService ser = Executor.newFixedThreadPool(int i);
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);
//获取结果:
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
//关闭服务
ser.shutdownNow();
}
}
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}