线程
多任务
- 多任务能够充分的利用CPU的资源,提高程序的运行效率
- 多任务的本质是同一时间执行多个任务
多任务的执行方式:并发,并行
- 并发:指多个任务的在一段时间内交替的执行,但是交替时间很短,形成一种同时执行的错觉。
- 并行:真正的同时运行,只能发生在多核多CPU的情况下,一个CPU执行一个任务,多个任务同一时间,同时执行。
多线程
- 指多个线程并发执行的过程,线程根据不同的顺序进行同时的执行的过程。
- 充分利用资源,提高运行效率
普通方法调用和多线程方法调用
- 普通方法调用:
- 多线程方法调用:
**3和4两个线程并行的执行
**
线程(thread),进程(process),程序
- 程序:程序是指令和数据的有序集合,是一个静态的概念。
- 进程:进程是程序的一次执行过程,是一个动态的,是一个资源分配单位
- 一个进程至少包含一个或多个线程。
- 线程:线程是CPU调度和执行的单位。
核心概念关于线程
- 线程就是独立执行的路径。
- 程序运行时,就算没有创建线程,程序后台本身也会有线程,如主线程(main),回收线程(gc)。
- main是主线程,被用于程序的执行
- 一个进程中如果有多个线程,那么线程的运行由调度器决定,先后顺序不能改变
- 对同一份资源进行操作时,多线程可能会导致资源的抢夺问题,这时候需要考虑程度的并发
- 线程的使用会带来额外的开销,如调度时间,并发控制的开销
- 每个线程都会在自己的内存内交互,内存控制不当会导致数据的不统一。
线程的创建
继承thread类
- 自定义的线程类继承thread类
- 重写run()方法
- 创建线程对象
- 在主线程中执行start()方法
在主线程中执行start()方法的话自定义线程和主线程就是交替的执行,若执行run方法就是在主线程中按顺序执行
- 代码示例:
package com.XianCheng.Thread;
/**
* 自定义线程类的实现
* 方法一:继承Thread类实现
* 1.继承Thread类
* 2.重写run()方法
* 3.创建线程对象
* 4.执行start()方法
*/
public class ThreadTest extends Thread{
//创建自定义线程
//重写run()方法
@Override
public void run() {
for (int i = 0; i < 23; i++) {
System.out.println("我是被重写的run方法");
}
}
//创建主线程
public static void main(String[] args) {
//创建线程对象
ThreadTest td1 = new ThreadTest();
//调用start方法
td1.start();
for (int i = 0; i < 2000; i++) {
System.out.println("我是主线程");
}
}
}
线程开启后不一定马上运行,线程的运行由CPU决定
实现Runnable接口创建线程
- 方法二:通过继承Runnable接口实现线程的创建
1. 实现Runnable接口
2. 重写run方法
3. 创建实现的Runnable接口的对象
4. 创建线程对象:new thread()
5. 将实现类的对象交由创建的线程对象 - 代码案例:
package com.XianCheng.Runnable;
/**
* 方法二:通过继承Runnable接口实现线程的创建
* 1.实现Runnable接口
* 2.重写run方法
* 3.创建实现的Runnable接口的对象
* 4.创建线程对象:new thread()
* 5.将实现类的对象交由创建的线程对象
*/
public class RunnalbleTest implements Runnable{
//重写run方法
@Override
public void run() {
//创建方法证明线程创建成功
for (int i = 0; i < 50; i++) {
System.out.println("Runnable接口实现创建线程成功"+i);
}
}
public static void main(String[] args) {
//创建Runnable接口实现类对象
RunnalbleTest runnalbleTest = new RunnalbleTest();
//将实现类的对象传递给thread的对象实现
//创建线程对象
Thread thread = new Thread(runnalbleTest);
//调用start方法运行线程
thread.start();
/**
* Thread thread = new Thread(runnalbleTest);
* //调用start方法运行线程
* thread.start();
* 也可以直接写成
* new Thread(runnalbleTest).start();
*/
//主线程对比
for (int i = 0; i < 1000; i++) {
System.out.println("w我是主线程"+i);
}
}
}
注意:Thread类的继承由于java的单继承性,具有一定的局限性,Runnable接口的实现可以避免单继承的局限性,更加灵活方便,可以使得同一个对象被多个线程使用
实现Callable接口创建线程
- 继承Callable接口
- 重写call方法
- 创建服务对象
- 创建服务,创建线程池:ExecutorService ser= Executors.newFixedThreadPool(3);
- 提交执行: Future r1=ser.submit(testCallable);
- 获取结果:boolean s1=r1.get();
- 关闭服务:ser.shutdown();
package com.XianCheng.Callable;
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、 实现callable接口==>Callable的返回值和重写的call方法的返回值相同
2、 重写call方法
3、 创建服务目标对象
4、 创建执行服务: ExecutorService ser= Executors.newFixedThreadPool(3);
5、 提交执行: Future <Boolean> r1=ser.submit(testCallable);
6、 获取结果:boolean s1=r1.get();
7、 关闭服务:ser.shutdown();
*/
public class TestCallable implements Callable<Boolean> {
//创建对应属性
private String url;
private String name;
public TestCallable() {
}
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call() throws Exception {
//调用下载器
down down = new down();
down.downway(url,name);
System.out.println(Thread.currentThread().getName()+"下载完成");
return true;
}
//主线程中调用
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建服务对象
TestCallable testCallable = new TestCallable("https://c-ssl.duitang.com/uploads/blog/202302/18/20230218223919_23cf8.jpeg","4.jpg");
TestCallable testCallable1 = new TestCallable("https://c-ssl.duitang.com/uploads/blog/202302/18/20230218223919_23cf8.jpeg","3.jpg");
TestCallable testCallable2 = new TestCallable("https://c-ssl.duitang.com/uploads/blog/202302/18/20230218223919_23cf8.jpeg","2.jpg");
//创建执行服务,创建线程池并规定大小
ExecutorService ser= Executors.newFixedThreadPool(3);
//提交执行
Future <Boolean> r1=ser.submit(testCallable);
Future <Boolean> r2=ser.submit(testCallable1);
Future <Boolean> r3=ser.submit(testCallable2);
//获取结果
boolean s1=r1.get();
boolean s2=r2.get();
boolean s3=r3.get();
//输出结果
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
//关闭服务
ser.shutdown();
}
}
//下载器
class down{
//下载方法
public void downway(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
多线程的图片下载案例
- 使用多线程对网络图片进行下载,可用于证明多线程中线程是并行执行的。
- 使用commons-io-jar包中的方法将网络url转为图片保存到本地
package com.XianCheng.Thread;
//使用多线程对网络图片进行下载
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* 1.导入commons-io-jar包
* 2.使用commons-io-jar包中的FileUtils下的copyFileToFile()方法
* 3.给该方法传递一个url和保存的名字name
* 4.构造器创建属性
* 5.创建线程对象
*/
public class ThreadPhoto extends Thread{
//创建属性
private String url;
private String name;
//创建无参构造
public ThreadPhoto() {
}
//创建有参构造
public ThreadPhoto(String url, String name) {
this.url = url;
this.name = name;
}
//重写run方法
@Override
public void run() {
//编写线程体
//创建下载方法的对象
WebDown webDown = new WebDown();
webDown.WebDownLoad(url,name);
System.out.println("下载"+name+"成功");
}
//主线程中调用对应的线程体
public static void main(String[] args) {
ThreadPhoto threadPhoto = new ThreadPhoto("https://c-ssl.duitang.com/uploads/item/202002/18/20200218155725_arms8.jpeg","1.jpg");
ThreadPhoto threadPhoto1 = new ThreadPhoto("https://c-ssl.duitang.com/uploads/item/201801/13/20180113162938_AUPm2.jpeg","2.jpg");
ThreadPhoto threadPhoto2 = new ThreadPhoto("https://c-ssl.duitang.com/uploads/blog/202102/10/20210210111704_cd68b.jpg","3.jpg");
threadPhoto.start();
threadPhoto1.start();
threadPhoto2.start();
}
}
//下载器
class WebDown{
//下载方法
public void WebDownLoad(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载器出现异常");
}
}
}
多线程同时访问一个对象
-
多线程同时访问一个对象时,没有对对象继续并发操作时,可能会导致数据紊乱,导致数据的不安全。
-
使用龟兔赛跑模拟多线程同时访问一个对象
- 将龟和兔分别当做一个线程,将比赛的总距离当做对象
- 两个线程同时访问该对象,并判断胜者
- 通过Thread.sleep()对兔这个线程进行休眠操作,达到龟兔赛跑中兔子偷懒的效果。
-
代码展示:
package com.XianCheng.Test;
//模拟多个线程同时访问一个对象
//模拟学生,老师,黄牛三个线程,访问票这一个对象
public class Threadsoop implements Runnable{
private int tick=10;
//重写run方法
@Override
public void run() {
while (true){
if (tick<=0){
break;
}
try {
Thread.sleep(200);//在操作前会休眠多少毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// Thread.currentThread().getName()获取线程名
System.out.println(Thread.currentThread().getName()+"--》拿到了第"+tick--+"张票");
}
}
public static void main(String[] args) {
//创建线程对象
Threadsoop threadsoop = new Threadsoop();
//多线程使用单对象
new Thread(threadsoop,"小明").start();
new Thread(threadsoop,"老师").start();
new Thread(threadsoop,"黄牛").start();
}
}