核心概念
- 路径就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,比如主线程,gc线程等
- main()称之为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器与操作系统紧密相关的,先后顺序是不能人为干预的
- 线程会带来额外的开销,如CPU调度时间,并发控制开销
- 每个县城在自己的工作内存交互,内存控制不当会导致数据不一致
每个线程都有优先权,具有较高优先权的线程优先于优先权较低的线程执行
线程创建的三种方式
继承Thread类
自定义线程类继承Thread类
重写run方法,编写线程执行体
创建线程对象,调用start()方法启动线程
// 创建线程方式一:继承thread类,重写run方法,调用start开启线程
public class TestThread1 extends Thread{
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("thread run:"+i);
}
}
public static void main(String[] args) {
// main线程,主线程
// 创建一个线程对象
TestThread1 tt1 = new TestThread1();
// 调用start方法,开启线程
tt1.start();
for (int i = 0; i < 20; i++) {
System.out.println("main run:"+i);
}
}
}
普通方法调用和多线程
如果直接调用run方法,则相当于调用普通方法,只有主线程一条执行路径
如果调用start方法,则相当于执行多条路径,主线程和子线程并行执行
实例:用多线程方式从网络中下载三张图片
前提工作:
1、创建一个lib文件夹,右键→add as Library
2、下载commons-io.jar包,并放在lib文件夹下
3、在Java代码中可以调用commons-io包的相关方法,在网上下载图片到本地文件中,如FileUtils.copyURLToFile()方法
package 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.name = name;
this.url = url;
}
// 下载图片线程的执行体
@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://kuangstudy.oss-cn-beijing.aliyuncs.com/bbs/2022/10/10/kuangstudydde2601e-eb8f-4601-b097-c07a6adb7485.png","1.jpg");
TestThread2 t2 = new TestThread2("https://kuangstudy.oss-cn-beijing.aliyuncs.com/bbs/2022/10/10/kuangstudyfabac30a-9e31-4ea0-bca8-739ca0f01892.png","2.jpg");
TestThread2 t3 = new TestThread2("https://kuangstudy.oss-cn-beijing.aliyuncs.com/bbs/2022/10/10/kuangstudybcc252b8-d410-4397-bf94-d5849e143b73.png","3.jpg");
t1.start();
t2.start();
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方法出现问题");
}
}
}
实现runnable接口
定义MyRunnable类实现Runnable接口
实现run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
推荐使用Runnable对象,因为Java单继承的局限性
package demo01;
// 创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口的实现类,调用start方法
public class TestThread3 implements Runnable{
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("Runnable run"+i);
}
}
public static void main(String[] args) {
// 创建runnable接口的实现类对象
TestThread3 tt3 = new TestThread3();
// 创建一个线程对象,通过线程对象来开启线程,代理
Thread thread = new Thread(tt3);
// 开启线程对象
thread.start();
// 上述代码简写:new Thread(tt3).start();
for (int i = 0; i < 20; i++) {
System.out.println("main run"+i);
}
}
}
小结:
- 继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免00P单继承局限性
- 实现Runnable接口
- 实现接口Runnable具备多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
实现callable接口
实现Callable接口,需要返回值类型
重写call方法,需要抛出异常
创建目标对象
创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
提交执行:Future result1 = ser.submit(t1);
获取结果:boolean r1 = result1.get();
关闭服务:ser.shutdownNow();
package demo02;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
// 线程创建方式三,实现callable接口
/**
* callable的好处
* 1、可以定义返回值
* 2、可以抛出异常
*/
public class TestCallable implements Callable<Boolean> {
private String url; // 网络图片地址
private String name;// 保存的文件名
public TestCallable(String url,String name){
this.name = name;
this.url = url;
}
// 下载图片线程的执行体
@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://kuangstudy.oss-cn-beijing.aliyuncs.com/bbs/2022/10/10/kuangstudydde2601e-eb8f-4601-b097-c07a6adb7485.png","1.jpg");
TestCallable t2 = new TestCallable("https://kuangstudy.oss-cn-beijing.aliyuncs.com/bbs/2022/10/10/kuangstudyfabac30a-9e31-4ea0-bca8-739ca0f01892.png","2.jpg");
TestCallable t3 = new TestCallable("https://kuangstudy.oss-cn-beijing.aliyuncs.com/bbs/2022/10/10/kuangstudybcc252b8-d410-4397-bf94-d5849e143b73.png","3.jpg");
//创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(1);
//提交执行:
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方法出现问题");
}
}
}
线程不安全问题
当多个线程操作一个资源的情况下,数据可能会产生紊乱的现象,比如某一数据被多次获取,尤其在经济领域,这样的紊乱可能会导致经济损失
以下示例,是指资源仅有10张票,有多个线程并行启动,调用run方法
package demo01;
// 多个线程同时操作同一个对象
// 多个线程操作同一个资源的情况下,线程不安全,数据可能会产生紊乱
public class TestThread4 implements Runnable{
private int ticketNums = 10;
@Override
public void run() {
while(true){
if (ticketNums<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"票");
}
}
public static void main(String[] args){
TestThread4 tt4 = new TestThread4();
new Thread(tt4,"小明").start();
new Thread(tt4,"老师").start();
new Thread(tt4,"黄牛党").start();
new Thread(tt4,"小红").start();
new Thread(tt4,"小李").start();
new Thread(tt4,"小王").start();
new Thread(tt4,"小刘").start();
}
}
sleep应用
sleep函数可以让线程退出就绪状态,即线程在休眠时间内不会去抢占线程
package demo01;
public class Race implements Runnable {
// 胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
// 模拟兔子休息
if(Thread.currentThread().getName().equals("兔子")&& i == 80){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 判断比赛是否结束
boolean flag = gameOver(i);
// 如果比赛结束了,就停止程序
if (flag){break;}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
}
}
// 判断是否完成比赛
private boolean gameOver(int steps){
// 判断是否有胜利者
if(winner != null){
// 已经存在胜利者了
return true;
}else{
if (steps == 100){
winner = Thread.currentThread().getName();
System.out.println("winner is "+ winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}