线程创建
三种创建方式
- Thread class —> 继承Thread类(重点)
- Runnable接口 —> 实现Runnable接口(重点)
- Callable接口 —> 实现Callable接口(现阶段了解)
Thread
创建一个新的执行线程有两种方法,一个是将一个类声明为Thread的子类。这个子类应该重写run类的方法,编写线程执行体。创建线程对象,调用start方法启动线程
交替执行,同时执行
package com.Sucker.Demo01;
//创建线程方法一:继承Thread类,重写run()方法,调用start开启线程
//注意:线程开启不一定立即执行,由cpu调度
public class TestThread1 extends Thread {
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("k---"+i);
}
}
public static void main(String[] args) {
//main线程,主线程
//创建一个线程对象
TestThread1 testThread1 = new TestThread1();
//调用start方法开启线程
testThread1.start();
for (int i = 0; i < 200; i++) {
System.out.println("duo---"+i);
}
}
}
线程不一定立即执行,CPU安排调度
案例:下载图片
首先下载commons-io-2.6 jar 包,在com层级下新建package–lib,将其复制进lib目录中,再右键lib目录选择add as library
package com.Sucker.Demo01;
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("https://img-home.csdnimg.cn/images/20210721094624.jpg","20210721094624.jpg");
TestThread2 t2 = new TestThread2("https://img-home.csdnimg.cn/images/20210721094624.jpg","25.jpg");
TestThread2 t3 = new TestThread2("https://img-home.csdnimg.cn/images/20210721094624.jpg","26.jpg");
t1.start();
t2.start();
t3.start();
}
}
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try {
//调用jar包里面写好的类的方法,把url地址变成一个文件
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}
步骤:
- 首先使用下载器,通过工具类里的方法
- 实现一个线程类,继承Thread类
- 用构造器丢入url和name
- 重写run方法,里面执行
- 主线程main方法里面通过构造器创建多个线程并启动
第二种方法是实现Runnable
- 定义MyRunnable类实现Runnable接口
- 重写run方法,实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
package com.Sucker.Demo01;
//创建线程方式二:实现Runnable接口,执行线程需要丢入runnable接口实现类,调用start方法
public class TestThread3 implements Runnable {
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("k---"+i);
}
}
public static void main(String[] args) {
//创建一个Runnable接口的实现类对象
TestThread3 testThread3 = new TestThread3();
//创建线程对象,通过线程对象来开启线程,代理
// Thread thread = new Thread(testThread1);
// thread.start();
new Thread(testThread3).start();//简写
for (int i = 0; i < 1000; i++) {
System.out.println("duo---"+i);
}
}
}
小结:
- 继承Thread
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性
- 实现Runnable
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
Thread.currentThread().getName()这个方法得到当前执行线程的名字
例子:
package com.Sucker.Demo01;
//多个线程同时操作同一个对象
//买火车票的例子
//发现问题:多个线程操作同一个资源情况下,线程不安全,数据紊乱
public class TestThread4 implements Runnable {
//票数
private int ticketNums = 10;
@Override
public void run() {
while(true){
if(ticketNums<=0) break;
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.currentThread().getName()这个方法得到当前执行线程的名字
System.out.println(Thread.currentThread().getName()+"--->拿到了"+ticketNums--+"票");
}
}
public static void main(String[] args) {
TestThread4 ticket = new TestThread4();
new Thread(ticket,"小明").start();//不同线程,可以起名字
new Thread(ticket,"老师").start();
new Thread(ticket,"黄牛党").start();
}
}
发现问题:多个线程操作同一个资源情况下,线程不安全,数据紊乱
案例:龟兔赛跑
package com.Sucker.Demo01;
//模拟龟兔赛跑
public class Race implements Runnable {
//胜利者
private static String winner; //静态常量static保证只有一个winner
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//模拟兔子睡觉
if(Thread.currentThread().getName().equals("兔子") && i%10==0)
{
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gammeOver(i);
if(flag) break;
System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
}
}
//判断是否完成比赛
private boolean gammeOver(int steps){
//判断是否有胜利者
if(winner!=null) return true;
else if(steps>=100){
winner = Thread.currentThread().getName();
System.out.println("winner is "+winner);
return true;
}
else return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
实现Callable接口
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:Future result1 = ser.submit(t1)
- 获取结果:boolean r1 = result1.get()
- 关闭服务:ser.shutdownNow();
package com.Sucker.Demo02;
import com.Sucker.Demo01.TestThread2;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
//线程创建方式三:实现Callable接口
public class TestCallable implements Callable<Boolean> {
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://img-home.csdnimg.cn/images/20210721094624.jpg","20210721094624.jpg");
TestCallable t2 = new TestCallable("https://cn.bing.com/images/search?view=detailV2&ccid=QnfHdp2q&id=BDCDE7C79D83FB599405AFD2188093140A3011E5&thid=OIP.QnfHdp2qT_0YJXjq1gmglgHaM1&mediaurl=https%3a%2f%2fuploadfile.bizhizu.cn%2fup%2f03%2fef%2f8f%2f03ef8fc438dbe2beb844b305ec87391e.jpg&exph=1774&expw=1024&q=jk%e7%be%8e%e5%a5%b3%e5%9b%be%e7%89%87&simid=608040684881330440&FORM=IRPRST&ck=851CB37FC70B002B589BA118850F85A4&selectedIndex=0","25.jpg");
TestCallable t3 = new TestCallable("https://cn.bing.com/images/search?view=detailV2&ccid=ti3Zfhln&id=5D3EE2D7DE03E7400C8014499C2EE8B94A7F167D&thid=OIP.ti3ZfhlntGiUtwqXhdaLUAHaKL&mediaurl=https%3a%2f%2ftse1-mm.cn.bing.net%2fth%2fid%2fR-C.b62dd97e1967b46894b70a9785d68b50%3frik%3dfRZ%252fSrnoLpxJFA%26riu%3dhttp%253a%252f%252fimg.mm4000.com%252ffile%252f8%252f7b%252f260e19734b.jpg%26ehk%3dsZek3h%252fYAHyVd0KkDUGwyYRYOzMUYLFCkbZ4nbXssj4%253d%26risl%3d%26pid%3dImgRaw&exph=1513&expw=1100&q=jk%e7%be%8e%e5%a5%b3%e5%9b%be%e7%89%87&simid=608040800847024524&FORM=IRPRST&ck=3A1E7FE0E1914FF26937FF1227F44B89&selectedIndex=1","26.jpg");
//创建线程服务:
ExecutorService ser = Executors.newFixedThreadPool(3);//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 {
//调用jar包里面写好的类的方法,把url地址变成一个文件
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}
Callable的好处:
- 可以定义返回值
- 可以抛出异常