实现多线程的三个方式
1、继承Thread类
2、实现Runnable接口
3、实现Callable接口
![在这里插入图片描述](https://img-blog.csdnimg.cn/88246ca563ce4419982c2fd780bf6749.png)
Thread
Thread
其实是实现了Runnable
接口,而线程类,直接继承Thread
,重写run
编写线程体,创建线程对象,调用start()
输出信息测试
/**
* 创建多线程方式1,继承Thread类,重新run方法,调用start开启线程
* 线程不一定立即执行,CPU调度
*/
public class TestThread1 extends Thread {
@Override
public void run() {
//run方法线程体
for(int i=0;i<20;i++){
System.out.println("run方法:"+i);
}
}
public static void main(String[] args) {
//创建线程对象
TestThread1 thread1=new TestThread1();
//调用start开启线程
thread1.start();
//main主线程体
for (int i = 0; i < 20; i++) {
System.out.println("main方法:"+i);
}
}
}
可以看到结果,交替执行的,而不是按照顺序执行
下载网络图片测试
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
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 thread1=new TestThread2("https://img-blog.csdnimg.cn/20200430094021144.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EzNTYyMzIz,size_16,color_FFFFFF,t_70","C:\\Users\\w4523\\Desktop\\PigchaClient_green\\1.jpg");
TestThread2 thread2=new TestThread2("https://img-blog.csdnimg.cn/208f9b580ef948b69b512083345e4600.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAV3VXdUlJ,size_20,color_FFFFFF,t_70,g_se,x_16","C:\\Users\\w4523\\Desktop\\PigchaClient_green\\2.jpg");
TestThread2 thread3=new TestThread2("https://img-blog.csdnimg.cn/f9c8745d07db43f286da8b7ea7562ea0.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAV3VXdUlJ,size_20,color_FFFFFF,t_70,g_se,x_16","C:\\Users\\w4523\\Desktop\\PigchaClient_green\\3.jpg");
//先执行thread1
thread1.start();
//再执行thread2
thread2.start();
//最后执行thread3
thread3.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异常");
}
}
}
可以看到,方法的先后顺序是thread1、thread2、thread3。实际执行顺序是1、3、2,其实是多线程同步执行的,具体顺序是cpu执行
Runnable
/**
* 创建多线程方式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("run方法:"+i);
}
}
public static void main(String[] args) {
//创建runnable接口的实现类对象
TestThread3 thread3=new TestThread3();
//创建线程对象,通过线程对象开启线程,
new Thread(thread3).start();
//main主线程体
for (int i = 0; i < 20; i++) {
System.out.println("main方法:"+i);
}
}
}
Thread和Runnable
-
实现多线程:
- Thread是通过继承类的方式来做多线程,只能实现一个类,
- Runnable是通过实现接口的方式做多线程,可以继承多个接口,
-
启动线程
- 继承Thread的类,通过类对象.start()启动
- 实现Runnable的接口,还是需要new一个Thread,通过new Thread(实现接口的类).start()启动;
推荐使用Runnable,避免了单继承的局限性,可以被多个线程使用
多线程并发
/**
* 多个线程操作同一个对象
* 买火车票例子
* 问题:多线程情况下,操作同一个资源,线程不安全,数据紊乱
*/
public class TestThread4 implements Runnable {
//票数
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 thread4=new TestThread4();
new Thread(thread4,"张三").start();
new Thread(thread4,"李四").start();
new Thread(thread4,"王五").start();
}
}
通过runnable实现多线程,发现变量值在多线程的情况下,被多个线程共用了,数据不一致,肯定是不允许的
Callable
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
/**
* 线程创建方式3,实现callable接口
* 好处:可以获取返回值
* 可以抛出异常
*/
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 thread1=new TestCallable("https://img-blog.csdnimg.cn/20200430094021144.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EzNTYyMzIz,size_16,color_FFFFFF,t_70","C:\\Users\\w4523\\Desktop\\PigchaClient_green\\1.jpg");
TestCallable thread2=new TestCallable("https://img-blog.csdnimg.cn/208f9b580ef948b69b512083345e4600.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAV3VXdUlJ,size_20,color_FFFFFF,t_70,g_se,x_16","C:\\Users\\w4523\\Desktop\\PigchaClient_green\\2.jpg");
TestCallable thread3=new TestCallable("https://img-blog.csdnimg.cn/f9c8745d07db43f286da8b7ea7562ea0.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAV3VXdUlJ,size_20,color_FFFFFF,t_70,g_se,x_16","C:\\Users\\w4523\\Desktop\\PigchaClient_green\\3.jpg");
//创建执行服务
ExecutorService ser= Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> result1=ser.submit(thread1);
Future<Boolean> result2=ser.submit(thread2);
Future<Boolean> result3=ser.submit(thread3);
//获取结果
boolean sr1=result1.get();
boolean sr2=result2.get();
boolean sr3=result3.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异常");
}
}
}
}
多线程执行成功
lamda表达式
函数式接口
任何接口,只包含一个抽象方法,他就是函数式接口,可以通过lambda表达式来创建接口对象
- 接口实现的几种方式
/**
* lambda表示
*/
public class TestLambda {
//3、静态内部类
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda2");
}
}
public static void main(String[] args) {
ILike like=new Like();
like.lambda();
like=new Like2();
like.lambda();
//4、局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda3");
}
}
like=new Like3();
like.lambda();
//5、匿名内部类
like=new ILike() {
@Override
public void lambda() {
System.out.println("i like lambda4");
}
};
like.lambda();
//6、lambada简化
like=()-> {
System.out.println("i like lambda5");
};
like.lambda();
}
}
//1、定义一个函数式接口
interface ILike{
void lambda();
}
//2、实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda");
}
}
- 简化lambda
/**
* 总结:前提必须是函数式接口
* lambda如果只有一行代码,才能简化花括号,如果有多行,就得用代码块
* 多个参数也可以去掉参数类型
*/
public class TestLambda2 {
public static void main(String[] args) {
Ilove love=null;
//1、简化参数类型
love=(a)->{
System.out.println(a);
};
love.love(11111);
//2、简化括号
love=a->{
System.out.println(a);
};
love.love(222222);
//3、简化花括号
love=a-> System.out.println(a);
love.love(333333333);
}
}
interface Ilove{
void love(int a);
}
线程的五大状态
线程停止
stop或destroy已经建议不再使用,通过自己设置标志位让线程停止
/**
* 测试停止线程
* 1.建议线程正常停止,利用次数,不建议死循环
* 2、建议使用标志位,设置一个标志位
* 3、不要使用stop或destroy等过时的或JDK不建议使用的方法
*/
public class TestStop implements Runnable {
//1、设置一个标志位
private boolean flag=true;
@Override
public void run() {
int i=0;
while (flag){
System.out.println("线程正在运行。。。。。。。。。Thread"+i++);
}
}
//2、设置一个公开的方法转换停止线程,转换标志位
public void stop(){
this.flag=false;
}
public static void main(String[] args) {
TestStop stop=new TestStop();
new Thread(stop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main"+i);
if(i==900){
//调用stop方法切换标志位,让线程停止
stop.stop();
System.out.println("线程该停止了");
}
}
}
}
线程休眠
sleep时间单位是毫秒
线程礼让
yield
让当前正在执行的线程暂停,但不阻塞,从运行状态变为就绪状态
两个就绪状态的线程,线程A和线程B,假如A已经进入cpu运行了,B还是就绪状态,通过yield
让线程A变成就绪状态 ,重新等待进入cpu,有可能还是A进入,进入了就继续执行,也有可能B进入,结果不一定
/**
* 测试礼让,
* 礼让不一定成功
*/
public class TestYield {
public static void main(String[] args) {
MyYield myYield=new MyYield();
new Thread(myYield,"A").start();
new Thread(myYield,"B").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}
A还没执行完,礼让以后,B线程就开始执行了,B执行完,A继续执行
join
类似于插队,先执行这个线程,其它线程都阻塞,等这个线程执行完了,其它 线程才能继续执行
/**
* 测试join
* 类似于插队
*/
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程"+Thread.currentThread().getName()+"-------->"+i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动线程
TestJoin testJoin=new TestJoin();
Thread thread = new Thread(testJoin,"A");
thread.start();
//主线程
for (int i = 0; i < 1000; i++) {
//主线程和线程A同时进行,当主线程进行到200时线程A就插队,线程A执行完再执行主线程
if(i==200){
thread.join();//插队
}
System.out.println("main->"+i);
}
}
}
观测线程状态
NEW 尚未启动
RUNNABLE 正在执行中
BLOCKED 阻塞的(被同步锁或者IO锁阻塞)
WAITING 永久等待状态
TIMED_WAITING 等待指定的时间重新被唤醒的状态
TERMINATED 执行完成
/**
* 观察测试线程状态
*/
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程执行完成");
});
//观察没有调用的Thread状态
System.out.println("观察没有调用的Thread状态-->"+thread.getState());
thread.start();
//观察调用start的Thread状态
System.out.println("观察调用start的Thread状态-->"+thread.getState());
while (thread.getState()!=Thread.State.TERMINATED){
Thread.sleep(100);
//线程不停止就一直输出状态
System.out.println("线程不停止就一直输出状态-->"+thread.getState());
}
}
}
线程优先级
/**
* 测试线程优先级
*/
public class TestPriority {
public static void main(String[] args) {
//查看主线程默认优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority=new MyPriority();
Thread thread1=new Thread(myPriority,"thread1");
Thread thread2=new Thread(myPriority,"thread2");
Thread thread3=new Thread(myPriority,"thread3");
Thread thread4=new Thread(myPriority,"thread4");
Thread thread5=new Thread(myPriority,"thread5");
Thread thread6=new Thread(myPriority,"thread6");
//先设置优先级再启动
thread1.start();
thread2.setPriority(1);
thread2.start();
thread3.setPriority(4);
thread3.start();
thread4.setPriority(Thread.MAX_PRIORITY);
thread4.start();
thread5.setPriority(7);
thread5.start();
thread6.setPriority(8);
thread6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
守护线程
线程分为用户线程和守护线程,
虚拟机确保用户线程执行完毕。
虚拟机不用等待守护线程执行完毕。
/**
* 测试守护线程
*/
public class TestDaemon {
public static void main(String[] args) {
God god=new God();
You you=new You();
Thread thread=new Thread(god);
thread.setDaemon(true); //默认false,表示用户线程,一般的线程都是用户线程
thread.start(); //守护线程启动
new Thread(you).start();
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("守护线程运行中");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("用户线程运行中");
}
System.out.println("用户线程结束");
}
}
线程同步
synchronized
锁的对象就是变化的量,需要增删改的对象
可以给代码块或方法加锁
死锁
尽量避免锁的嵌套使用,避免一个进程获取多个锁
Lock锁
//定义lock锁
private ReentrantLock lock=new ReentrantLock();
try {
lock.lock(); //加锁
//方法。。。。。。.。。
}catch (Exception e){
}finally {
lock.unlock(); //解锁
}
synchronized和Lock对比
Lock是显式锁,需要手动开启手动关闭,synchronized是隐式锁,出了作用域自动释放
Lock只能锁代码块,synchronized可以锁代码块和方法
Lock锁,JVM花费较少的时间来调度线程,性能更好,并且更好扩展性(提供更多子类)
优先使用顺序:Lock>同步代码块>同步方法
线程协作
线程池
/**
* 测试线程池
*
*/
public class Pool {
public static void main(String[] args) {
/**
* 1、创建服务,创建线程池
* newFixedThreadPool参数是线程池大小
*/
ExecutorService service= Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//2、关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
回顾
实现线程的三种方式
/**
* 回顾总结线程的创建
*/
public class ThreadNew {
public static void main(String[] args) throws ExecutionException, InterruptedException {
new MyThread1().start();
new Thread(new MyThread2()).start();
FutureTask<Integer> futureTask=new FutureTask(new MyThread3());
new Thread(futureTask).start();
Integer integer = futureTask.get();
System.out.println(integer);
}
}
//1、继承Thread类
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("MyThread1");
}
}
//2、实现Runnable接口
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("MyThread2");
}
}
//3、实现Callable接口
class MyThread3 implements Callable{
@Override
public Integer call() throws Exception {
System.out.println("MyThread3");
return 100;
}
}
线程池按照顺序执行
把线程池数量设为1,就会按照获取线程的顺序执行了
@Test
public void test1(){
ExecutorService service = Executors.newFixedThreadPool(1);
service.submit(new Runnable() {
@Override
public void run() {
System.out.println("thread1");
}
});
service.submit(new Runnable() {
@Override
public void run() {
System.out.println("thread2");
}
});
service.submit(new Runnable() {
@Override
public void run() {
System.out.println("thread4");
}
});
service.submit(new Runnable() {
@Override
public void run() {
System.out.println("thread5");
}
});
service.submit(new Runnable() {
@Override
public void run() {
System.out.println("thread6");
}
});
}
CountDownLatch
countDownLatch.await()会阻塞调用线程直到 state==0。因此,可以利用这个特性使得子线程按序执行
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1 do something");
countDownLatch.countDown();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t2 do something");
countDownLatch.countDown();
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t3 do something");
countDownLatch.countDown();
}
});
t1.start();
// 主线程阻塞等待子线程执行完毕
countDownLatch.await();
t2.start();
// 主线程阻塞等待子线程
countDownLatch.await();
t3.start();
// 主线程阻塞等待子线程
countDownLatch.await();
}
Future
Future模式是多线程开发中常用的一种设计模式,它的核心思想是异步调用。有时候不需要立刻返回执行结果,先让它执行,我们做其它的工作。等要用的时候,再尝试获取它的执行结果。类似于ajax的同步异步。
可以配合Callable使用,获取返回结果。以及对线程的一些操作,取消线程操作或者判断是否完成等。
Future的5个主要方法
get()
主要用来获取Callable的执行结果。
Callable执行完成:立刻返回执行结果
Callable未开始或者进行中:阻塞并等待线程执行完成
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(20);
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 111111;
}
});
try {
//获取结果
Integer integer = future.get();
System.out.println("返回结果:"+integer);
} catch (Exception e) {
e.printStackTrace();
}finally {
service.shutdown();
}
}
返回结果接收成功
get(long timeout,TimeUnit unit)
超时取消任务
传入一个时间,如果时间到了,还没执行完成,就会抛出TimeoutExceptionget,然后取消任务
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(20);
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
//先睡眠5s
Thread.sleep(5000);
return 111111;
}
});
try {
//获取结果
Integer integer = future.get(3,TimeUnit.SECONDS);
System.out.println("返回结果:"+integer);
} catch (Exception e) {
e.printStackTrace();
}finally {
service.shutdown();
}
}
线程执行时间为5s,get的时间设置为3s,因为超出设置的时间,抛出java.util.concurrent.TimeoutException
异常
cancel()
boolean cancel(boolean mayInterruptIfRunning);
取消执行相关线程,参数为boolean值,
为 true ,则线程将被中断,任务将被取消
为 false ,则仅当任务尚未开始执行时才会取消任务
任务取消后,其状态设置为 CANCELLED ,并调用 done 方法来指示任务已完成
//创建线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
//执行线程
Future future = executor.submit(new MyThread3());
future.cancel(boolean)
isDone()
检查任务是否已完成或已取消。如果任务已完成或已取消,则该方法返回 true ;否则,返回 false 。
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(20);
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
//先睡眠5s
Thread.sleep(5000);
return 111111;
}
});
try {
//查看线程状态,如果未执行完,等待1s后再查看线程状态
while (!future.isDone()){
System.out.println("未完成,等待1s");
Thread.sleep(1000);
}
//线程状态
System.out.println(future.isDone());
//获取结果
Integer integer = future.get();
System.out.println("返回结果:"+integer);
} catch (Exception e) {
e.printStackTrace();
}finally {
service.shutdown();
}
}
5s后执行线程结束
isCanaelled()
检查任务是否已取消。如果任务已取消,则该方法返回 true ;否则,返回 false 。
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(20);
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
//先睡眠5s
Thread.sleep(5000);
return 111111;
}
});
try {
int num=0;
//查看线程状态,如果未执行完,等待1s后再查看线程状态
while (!future.isDone()){
num++;
System.out.println("未完成,等待1s");
if(num==3){
//超过3s,取消线程
future.cancel(true);
}
System.out.println("线程是否取消:"+future.isCancelled());
Thread.sleep(1000);
}
//获取结果
Integer integer = future.get();
System.out.println("返回结果:"+integer);
} catch (Exception e) {
e.printStackTrace();
}finally {
service.shutdown();
}
}
获取结果的时候抛出异常java.util.concurrent.CancellationException
,因为线程已经被取消了
CountDownLatch
CountDownLatch是一个非常有用的工具,它可以用来确保在所有线程都执行完毕后才执行后续操作。它在许多并发编程场景中都有应用,比如等待所有子线程执行完毕后再关闭主线程,等待所有数据库操作执行完毕后再提交事务等等。
public static void main(String[] args) {
// 创建一个CountDownLatch,初始值为3
CountDownLatch latch = new CountDownLatch(3);
// 创建三个线程,每个线程都将调用countDown()方法
Thread t1 = new Thread(() -> {
try {
// 模拟耗时操作
Thread.sleep(1000);
// 调用countDown()方法
latch.countDown();
System.out.println(Thread.currentThread()+"执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
// 模拟耗时操作
Thread.sleep(2000);
// 调用countDown()方法
latch.countDown();
System.out.println(Thread.currentThread()+"执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t3 = new Thread(() -> {
try {
// 模拟耗时操作
Thread.sleep(3000);
// 调用countDown()方法
latch.countDown();
System.out.println(Thread.currentThread()+"执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 启动三个线程
t1.start();
t2.start();
t3.start();
// 等待三个线程都执行完毕
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 三个线程都执行完毕后,执行后续操作
System.out.println("所有线程都执行完毕");
}