我们举例来理解下多任务;
开车 + 打电话
吃饭 + 玩手机
这些动作都可以抽象为任务,虽然看起来一心二用,但人只有一个大脑,在一个时间片刻只能处理一个任务。
CPU 也是一样,面对多个任务,只能在一个时间片刻处理一个任务。
我们举例来理解下多线程;
多条线路同时跑起来;有多个分支共同去执行;
主线程调用 run 方法和调用 start 方法开启子线程的区别如下图所示。
执行程序的过程,叫进程;一个进程中有多个线程;
线程的三种实现方式:(线程实现在实际工作中优先使用Runnable接口和Callable接口方式)
继承 Thread 类,重写 run 方法。创建这个类的对象,再调用 start() 即可
package com.example.democrud.democurd.test01;
/**
* @author 闫文超
*/
//多线程调用 先继承Thred 方法 重写run方法 最后再用start 开启线程
//总结:线程的开启不一定立即开始执行,由CPU调度执行
public class testThread01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程---" + i);
}
}
public static void main(String[] args) {
//创建一个线程对象
testThread01 thread01 = new testThread01();
//先执行run的方法在执行下面的内容
// thread01.run();
//start的方法和main同步执行;顺序同步
// 调用start 开启线程
thread01.start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习thread--" + i);
}
}
}
下载文件需要在 pom.xml 中 commons io 包。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
package com.example.democrud.democurd.test01;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class testThred02 extends Thread {
public String url;//图片路径
public String name;//图片的名字
public testThred02(String url, String name) {
this.name = name;
this.url = url;
}
@Override //代表重写
public void run() {
downloadpng down = new downloadpng();
down.download(url, name);
System.out.println("下载的文件名为" + name);
}
//主线程 main方法
public static void main(String[] args) {
testThred02 thred01 = new testThred02("https://img1.baidu.com/it/u=413643897,2296924942&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=26be472b80013591100109eb63c7c5ec", "test01.jpg");
testThred02 thred02 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test02.jpg");
testThred02 thred03 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test03.jpg");
//开启线程
thred01.start();
thred02.start();
thred03.start();
}
//先写一个下载器
class downloadpng {
//引用地址和名字进行下载
public void download(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader 方法出现问题");
}
}
}
}
使用该方法下载网络图片。
继承 Runnable 接口,创建 Tread 对象 或者使用Runnable接口通过线程池 下面演示的是继承 Runnable 接口,创建 Tread 对象: 继承 Runnable 接口,创建 Tread 对象,传入实现类,开启 start 方法
package com.example.democrud.democurd.test01;
//1.实现Runnable接口 重写run方法 执行线程需要丢人Runnable接口实线路调用start方法启动线程
public class testThread03 implements Runnable {
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("闫文超在摸鱼啊---" + i);
}
}
public static void main(String[] args) {
//创建runnable接口的实现类对象
testThread03 thread03 = new testThread03();
//创建线程对象,通过线程对象来开启我们的线程;代理
Thread thread = new Thread(thread03);
thread.start();
// 或者
// new Thread(thread03).start();
for (int i = 0; i < 20; i++) {
System.out.println("我在学习当当中" + i);
}
}
}
以上两种方式的比较:
火车抢票实例:
Runnable 实现多线程,创造一个实列 ticketRunnable ,可共享给多个线程。
package com.example.democrud.democurd.test01;
//实现Runnnable的方式调用多线程
//多个线程调用一个对象
// 买火车票的例子
// 发现问题:多个线程操作同一个资源,线程不安全,数据紊乱!
public class testThread04 implements Runnable{
public int ticketNums=30;
@Override
public void run() {
while (true){
if (ticketNums<=10){
break;
}
try {
//线程模拟休眠2s
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");
}
}
public static void main(String[] args) {
testThread04 thread04 = new testThread04();
new Thread(thread04,"小王").start();
new Thread(thread04,"小龙").start();
new Thread(thread04,"小李").start();
new Thread(thread04,"黄牛").start();
}
}
龟兔赛跑案例:
package com.example.democrud.democurd.test01;
//实现runnable的接口
public class testThred05 implements Runnable {
//胜利者
public static String winner;
@Override
public void run() {
for (int i = 0; i < 1001; i++) {
if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {
//每跑10步 兔子休眠0.5毫秒
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Boolean gameover = gameover(i);
if (gameover)
break;
System.out.println(Thread.currentThread().getName() + "-跑了" + i);
}
}
public Boolean gameover(int steps) {
if (winner != null) {
return true;
} else {
if (steps >= 1000) {
winner = Thread.currentThread().getName();
System.out.println("winner is" + winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
testThred05 thred05 = new testThred05();
new Thread(thred05, "兔子").start();
new Thread(thred05, "乌龟").start();
}
}
Callable接口详解_逍遥绝情的博客-CSDN博客_callable接口(不懂看这个博客)
Callable两种执行方式:第一种借助线程池来运行
Future接口 Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、 等待完成和得到计算的结果。 当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。 如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。 一旦计算完成了,那么这个计算就不能被取消。
package com.example.democrud.democurd.test01;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
//Callable<Boolean> 布尔类型和call 同步
//callable的好处:
//1.可以定义返回值
//2.可以抛出异常
public class testThred02 implements Callable<Boolean> {
public String url;//图片路径
public String name;//图片的名字
public testThred02(String url, String name) {
this.name = name;
this.url = url;
}
@Override //代表重写
public Boolean call() {
downloadpng down = new downloadpng();
down.download(url, name);
System.out.println("下载的文件名为" + name);
return true;
}
//主线程 main方法
public static void main(String[] args) throws ExecutionException, InterruptedException {
testThred02 thred01 = new testThred02("https://img1.baidu.com/it/u=413643897,2296924942&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=26be472b80013591100109eb63c7c5ec", "test01.jpg");
testThred02 thred02 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test02.jpg");
testThred02 thred03 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test03.jpg");
// 创建执行服务 线程池中有3个线程
ExecutorService ser = Executors.newFixedThreadPool(3);
// 提交执行 执行每个线程
Future<Boolean> result1 = ser.submit(thred01);
Future<Boolean> result2 = ser.submit(thred02);
Future<Boolean> result3 = ser.submit(thred03);
// 获取结果:
boolean r1 = result1.get();
boolean r2 = result2.get();
boolean r3 = result3.get();
//关闭服务
ser.shutdownNow();
}
//先写一个下载器
class downloadpng {
//引用地址和名字进行下载
public void download(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader 方法出现问题");
}
}
}
}
Callable两种执行方式:第二种借助FutureTask执行
示例代码中,CallableTest 类实现了 Callable 接口的 call() 方法。在 main() 函数内首先创建了一个 FutureTask 对象(构造函数为 CallableTest 实例),然后使用创建的 FutureTask 对象作为任务创建了一个线程并且启动它,最后通过 futureTask.get() 等待任务执行完毕返回结果。
Future接口
Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、
等待完成和得到计算的结果。
当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。
如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。
一旦计算完成了,那么这个计算就不能被取消。
FutureTask类
FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以说FutureTask是一个提供异步计算的结果的任务。
FutureTask有2个构造方法:入参分别是Callable或者Runnbale对象。
FutureTask类同时实现了两个接口,Future和Runnable接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
public class FutureTask<V> implements RunnableFuture<V>{}
public interface RunnableFuture<V> extends Runnable, Future<V>
代码示例:
package com.sjmp.practice;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest implements Callable<String> {
@Override
public String call() throws Exception {
return "Hello CallableThread";
}
public static void main(String[] args) {
CallableTest callableTest = new CallableTest();
FutureTask<String> futureTask = new FutureTask<>(callableTest);
new Thread(futureTask).start();
try {
String s = futureTask.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
Hello CallableThread
Process finished with exit code 0
相同点
1、两者都是接口;(废话)
2、两者都可用来编写多线程程序;
3、两者都需要调用Thread.start()启动线程;
不同点
1、两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;
而实现Runnable接口的任务线程不能返回结果;
2、Callable接口的call()方法允许抛出异常;
而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
注意点
Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!
1、Callable规定的方法是call(),Runnable规定的方法是run().
2、Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
3、call方法可以抛出异常,run方法不可以
4、运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
5、代码示例:
//Callable 接口
public interface Callable<V> {
V call() throws Exception;
}
// Runnable 接口
public interface Runnable {
public abstract void run();
}
1、借助FutureTask执行
FutureTask类同时实现了两个接口,Future和Runnable接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
具体流程:
//定义实现Callable接口的的实现类重写call方法。
public class MyCallableTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
//TODO 线程执行方法
}
}
---------------------------------------------------------
//创建Callable对象
Callable<Integer> mycallabletask = new MyCallableTask();
//开始线程
FutureTask<Integer> futuretask= new FutureTask<Integer>(mycallabletask);
new Thread(futuretask).start();
--------------------------------------------------------
通过futuretask可以得到MyCallableTask的call()的运行结果:
futuretask.get();
2、借助线程池来运行
线程池中执行Callable任务原型:
public interface ExecutorService extends Executor {
//提交一个Callable任务,返回值为一个Future类型
<T> Future<T> submit(Callable<T> task);
//other methods...
}
借助线程池来运行Callable任务的一般流程为:
ExecutorService exec = Executors.newCachedThreadPool();
Future<Integer> future = exec.submit(new MyCallableTask());
通过future可以得到MyCallableTask的call()的运行结果: future.get();
例1:
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException,TimeoutException{
//创建一个线程池
ExecutorService executor = Executors.newCachedThreadPool();
Future<String> future = executor.submit(()-> {
TimeUnit.SECONDS.sleep(5);
return "CallableTest";
});
System.out.println(future.get());
executor.shutdown();
}
}
例2:Callable任务借助FutureTask运行:
public class CallableAndFutureTask {
Random random = new Random();
public static void main(String[] args) {
Callable<Integer> callable = new Callable<Integer>() {
public Integer call() throws Exception {
return random.nextInt(10000);
}
};
FutureTask<Integer> future = new FutureTask<Integer>(callable);
Thread thread = new Thread(future);
thread.start();
try {
Thread.sleep(2000);
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
例3:Callable任务和线程池一起使用,然后返回值是Future:
public class CallableAndFuture {
Random random = new Random();
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
Future<Integer> future = threadPool.submit(new Callable<Integer>() {
public Integer call() throws Exception {
return random.nextInt(10000);
}
});
try {
Thread.sleep(3000);
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
例4:当执行多个Callable任务,有多个返回值时,我们可以创建一个Future的集合:
class MyCallableTask implements Callable<String> {
private int id;
public OneTask(int id){
this.id = id;
}
@Override
public String call() throws Exception {
for(int i = 0;i<5;i++){
System.out.println("Thread"+ id);
Thread.sleep(1000);
}
return "Result of callable: "+id;
}
}
public class Test {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<Future<String>>();
for (int i = 0; i < 5; i++) {
results.add(exec.submit(new MyCallableTask(i)));
}
for (Future<String> fs : results) {
if (fs.isDone()) {
try {
System.out.println(fs.get());
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("MyCallableTask任务未完成!");
}
}
exec.shutdown();
}
}
Spring提供的计时器StopWatch对于秒、毫秒为单位方便计时的程序,尤其是单线程、顺序执行程序的时间特性的统计输出支持比较好。也就是说假如我们手里面有几个在顺序上前后执行的几个任务,而且我们比较关心几个任务分别执行的时间占用状况,希望能够形成一个不太复杂的日志输出,StopWatch提供了这样的功能。而且Spring的StopWatch基本上也就是仅仅为了这样的功能而实现。
public String call() throws Exception {
StopWatch stopWatch = new StopWatch();
stopWatch.start("测试StopWatch");
//TODO 业务逻辑
stopWatch.stop();
return "test";
}
Lamda 表达式属于函数式编程的概念
Lamda 表达式的演进:
package com.example.democrud.democurd.test01;
public class testLambda {
//3.静态内部类
static class like2 implements iLike{
@Override
public void lamdba() {
System.out.println("我在学习lamdba-2");
}
}
public static void main(String[] args) {
iLike like = new like();
like.lamdba();
like = new like2();
like.lamdba();
//4.局部内部类
class like3 implements iLike{
@Override
public void lamdba() {
System.out.println("我在学习lamdba-3");
}
}
like=new like3();
like.lamdba();
//5.匿名内部类 没有类的名称必须借助接口或者父类
like=new iLike() {
@Override
public void lamdba() {
System.out.println("我在学习lamdba-4");
}
};
like.lamdba();
//6.lambda简化
like=()->{
System.out.println("我在学习lamdba-5");
};
like.lamdba();
}
}
//1.定义一个函数式接口
interface iLike{
void lamdba();
}
//2.实现类
class like implements iLike{
@Override
public void lamdba() {
System.out.println("我在学习lamdba-1");
}
}
package com.example.democrud.democurd.test01;
public class testLamdba01 {
//静态内部类
static class Love1 implements iLove {
@Override
public void love(int a) {
System.out.println("打印我输入的数字是" + a);
}
}
public static void main(String[] args) {
iLove love = new Love();
love.love(20);
love = new Love1();
love.love(12);
//匿名类
love = new iLove() {
@Override
public void love(int a) {
System.out.println("打印我输入的数字是" + a);
}
};
love.love(33);
//lambba简化
love = (int a) -> {
System.out.println("打印我输入的数字是" + a);
};
love.love(56);
//简化参数类型
love = (a) -> {
System.out.println("打印我输入的数字是" + a);
};
love.love(57);
//简化中括号
love = a -> {
System.out.println("打印我输入的数字是" + a);
};
love.love(58);
//简化花括号
love = a -> System.out.println("打印我输入的数字是" + a);
love.love(59);
//总结:lamdba表达式只能有代码一行的情况下 才能简化一行,多行用大括号包围
//用户函数是接口
//函数是接口:任何接口如果只包含唯一一个 抽象方法那么他就是一个函数式接口
}
}
interface iLove {
void love(int a);
}
//实现类
class Love implements iLove {
@Override
public void love(int a) {
System.out.println("打印我输入的数字是" + a);
}
}
多线程 Thread 为代理,Runnable 为被代理对象!底层就是动态代理
package com.example.democrud.democurd.test01;
/**
* //静态代理模式
* //真实对象和代理对象都要实现同一接口
* //代理对象 必须要代理真实对象
* //好处:
* //代理对象可以做很多真实对象做不了的事情
* //真实对象专注于做自己的事情
*/
public class testThred06 {
public static void main(String[] args) {
You you = new You();
WeddingCompany company = new WeddingCompany(you);
company.HappyMarry();
new Thread( ()-> {System.out.println("我爱你");} ).start();
new Thread( ()-> System.out.println("我爱你") ).start();
// new Thread(Runnable::run).start();
new WeddingCompany(you).HappyMarry(); // 2个用法一样,都是静态代理
}
}
interface Marry{
void HappyMarry();
}
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("老王要结婚不知道他开心不");
}
}
//代理角色,婚庆公司,帮助你结婚
class WeddingCompany implements Marry{
//代理对象-->真实目标角色
private Marry target;
public WeddingCompany(Marry target){
this.target=target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void after() {
System.out.println("结婚后");
}
private void before() {
System.out.println("结婚前");
}
}
线程有6种状态,下面有错误:
public enum State {
//线程刚创建
NEW,
//在JVM中正在运行的线程
RUNNABLE,
//线程处于阻塞状态,等待监视锁,可以重新进行同步代码块中执行
BLOCKED,
//等待状态 WAITING,
//调用sleep() join() wait()方法可能导致线程处于等待状态
TIMED_WAITING,
//线程执行完毕,已经退出
TERMINATED;
}
package com.example.democrud.democurd.test01;
//观察 线程的状态
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("---等待结束");
}
});
//观察线程的状态 NEW
Thread.State state = thread.getState();
System.out.println(state);
//观察线程的状态 RUNNABLE
thread.start();
state = thread.getState();
System.out.println(state);
//终止线程的线程状态。线程已完成执行。 只要线程不停止运行他就会进入到此处进行运行
while (state!=Thread.State.TERMINATED){
Thread.sleep(100);
state = thread.getState();//TIMED_WAITING
System.out.println(state);
}
}
}
BLOCKED是指线程正在等待获取锁;WAITING是指线程正在等待其他线程发来的通知(notify),收到通知后,可能会顺序向后执行(RUNNABLE),也可能会再次获取锁,进而被阻塞住(BLOCKED)。
(Thread t = new Thread)线程 t 的一些方法如下图所示:
1、public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 2、public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 3、public final void setName(String name) 改变线程名称,使之与参数 name 相同 4、public final void setPriority(int piority) 更改线程的优先级。 5、public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 6、public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。 7、public void interrupt() 中断线程。 8、public final boolean isAlive() 测试线程是否处于活动状态。 9、public static void static yield() 暂停当前正在执行的线程对象,并执行其他线程。 10、public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 11、public static Thread currentThread() 返回对当前正在执行的线程对象的引用。
配一张图:
sleep() 方法的用处
package com.example.democrud.democurd.test01;
import java.text.SimpleDateFormat;
import java.util.Date;
public class testSleep {
public static void main(String[] args) throws InterruptedException {
// tenDown();
testDate();
}
public static void testDate() throws InterruptedException {
Date date = new Date(System.currentTimeMillis());//获取系统当前的时间
while (true){
//休眠1秒
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
//上面就获取了一次 每过1s咱们就需要更新一次
date=new Date(System.currentTimeMillis());//更新当前的时间
}
}
//数字倒计时demo
public static void tenDown() {
int num = 10;
try {
while (true) {
Thread.sleep(1000);
System.out.println(num--);
if (num < 0) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
主线程调用子线程的interrupt()方法,导致子线程抛出InterruptedException, 在子线程中catch这个Exception,不做任何事即可从Sleep状态唤醒线程,继续执行。
//new 一个线程
Thread thread = new Thread(new TestThread(1));
//开启线程
thread .start();
try {
//休眠
Thread.sleep(3000);
//结束休眠开始继续运行
thread .interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
\
package com.example.democrud.democurd.test01;
public class testyield {
public static void main(String[] args) {
yield yield = new yield();
new Thread(yield,"a").start();
new Thread(yield,"B").start();
}
}
class yield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行");
//礼让线程 但是不一定礼让就会成功是一个概率的事件
//礼让线程,并不能一定成功礼让,只能让运行状态线程变为就绪状态,重新竞争,看CPU心情
Thread.yield();
System.out.println(Thread.currentThread().getName()+"结束执行");
}
}
Join 合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。 可以想象成插队。
package com.example.democrud.democurd.test01;
//测试Join方法
//想象为插队
//Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
Join join = new Join();
Thread thread = new Thread(join);
thread.start();
for (int i = 0; i < 50; i++) {
if (i==30){
//join插队优先进行运行
thread.join();
}
System.out.println("main"+i);
}
}
}
class Join implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我是vip的线程");
}
}
}
第一种:标志位
package com.example.democrud.democurd.test01;
//测试stop
//1.建议线程正常停止--->利用次数,不建议死循环。
//2.建议使用标志位--->设置一个标志位.
//3.不要使用stop或destroy等过时或JDK不建议使用的方法
public class testStop implements Runnable {
private Boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("执行线程任务进行中"+i++);
}
}
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 < 100; i++) {
System.out.println("main"+i);
if (i == 90) {
stop.stop();
System.out.println("执行到90停止");
}
}
}
}
第二种: interrupt方法
1、直接调用interrupt()方法
package com.example.democrud.democurd.test01;
public class testStop01 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
//直接调用interrupt
while (!Thread.interrupted()){
System.out.println("你好啊");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//加入break才是终止执行
break;
}
}
System.out.println("停止");
});
t1.start();
Thread.sleep(2000);
System.out.println("终止");
//让t1终止 使用此方法
t1.interrupt();
}
}
2、拿到当前线程在调用interrupt()方法(Thread.currentThread().isInterrupted())
public class ThreadDemo14 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
//拿到当前线程 判断是否终止
while (!Thread.currentThread().isInterrupted()){
System.out.println("别烦我");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//加入break才是终止执行
break;
}
}
System.out.println("转账终止");
});
t1.start();
Thread.sleep(2000);
System.out.println("有内鬼终止交易");
//让t1终止 使用此方法
t1.interrupt();
}
}
注:当调用interrupt方法时,一定要加上break终止代码否则只会出现暂时终止。
\
调用interrupt()方法的两种方法的区别 Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志,也就是复位
public class ThreadDemo16 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
while (!Thread.interrupted()){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//获取当前线程
Thread curr=Thread.currentThread();
System.out.println("打印当前线程状态"+curr.isInterrupted());
System.out.println("打印当前线程状态"+curr.isInterrupted());
System.out.println("---------------------");
Thread.interrupted();
//Thread.currentThread().interrupt();
System.out.println("打印当前线程状态"+curr.isInterrupted());
System.out.println("打印当前线程状态"+curr.isInterrupted());
}
}
});
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志,不会复位
public class ThreadDemo16 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
while (!Thread.interrupted()){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//获取当前线程
Thread curr=Thread.currentThread();
System.out.println("打印当前线程状态"+curr.isInterrupted());
System.out.println("打印当前线程状态"+curr.isInterrupted());
System.out.println("---------------------");
// Thread.interrupted();
Thread.currentThread().interrupt();
System.out.println("打印当前线程状态"+curr.isInterrupted());
System.out.println("打印当前线程状态"+curr.isInterrupted());
}
}
});
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
打印结果
补:线程中的重要属性
public class ThreadDemo11 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
for (int i=0;i<10;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//重要属性
System.out.println("线程的ID:"+t1.getId());
System.out.println("线程的名称:"+t1.getName());
System.out.println("线程的优先级:"+t1.getPriority());
System.out.println("线程的状态:"+t1.getState());
System.out.println("线程的类型:"+t1.isDaemon());
System.out.println("线程是否存活:"+t1.isAlive());
t1.start();
Thread.State 线程状态。线程可以处于以下状态之一:
package com.can.ThreadState;
//观察测试线程的状态
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.State state = thread.getState();
System.out.println(state); //NEW
//观察启动后
thread.start();//启动线程
System.out.println(thread.getState()); //Run
while (state != Thread.State.TERMINATED){ //只要线程不终止,就一直输出状态。
Thread.sleep(100);
state = thread.getState();
System.out.println(state);
}
//死亡之后的线程是不能在启动的Exception in thread "main" java.lang.IllegalThreadStateException
//thread.start();
}
}
优先级的设定建议在 start() 调度前
package com.example.democrud.democurd.test01;
//测试线程的优先级,由高到低,越高越容易优先,但不一定
//优先级低只意味着获取调度的概念低,并不是优先级低就不会被优先调用,这都是看CPU的调度。
public class testPriority {
public static void main(String[] args) {
//主线程 优先级默认 是5 无法修改
System.out.println("线程名字"+Thread.currentThread().getName()+"线程优先级"+Thread.currentThread().getPriority());
Priority priority = new Priority();
Thread t1 = new Thread(priority,"t1");
Thread t2 = new Thread(priority,"t2");
Thread t3 = new Thread(priority,"t3");
Thread t4 = new Thread(priority,"t4");
Thread t5 = new Thread(priority,"t5");
Thread t6 = new Thread(priority,"t6");
//注意需要先设置优先级 然后启动线程
t1.start();
//最小优先级 1
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
//最大线程优先级 10
t4.setPriority(Thread.MAX_PRIORITY);
t4.start();
}
}
class Priority implements Runnable{
@Override
public void run() {
System.out.println("线程名字"+Thread.currentThread().getName()+"线程优先级"+Thread.currentThread().getPriority());
}
}
注意:
优先级低只意味着获取调度的概念低,并不是优先级低就不会被优先调用,这都是看CPU的调度。
默认优先级都是5,优先级不能小于1大于10否则会抛出异常
package com.example.democrud.democurd.test01;
//测试守护线程
//上帝守护你
//所有的用户线程结束了,守护线程才会结束了
//当只剩下守护线程时,JVM就退出了,守护线程不依赖于终端,而依赖于系统,与系统同生共死
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
Yous you = new Yous();
Thread thread = new Thread(god);
//设置守护线程必须要在线程启动前进行
thread.setDaemon(true); //默认是false,表示是用户线程,正常线程都是用户线程,设置成true变为守护线程
thread.start();
new Thread(you).start(); // 你 用户线程启动..
}
}
//上帝
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝保佑着你");
}
}
}
//你
class Yous implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("你一生都开心的活着");
}
System.out.println("-=========goodbye! world!==========");
}
}
同一个对象被多个线程同时操作
现实生活中,我们会遇到”同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队一个个来。
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象.这时候我们就需要线程同步.线程同步其实就是一种等待机制,多个需要同时访问!此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
形成线程安全的条件:
队列和锁: 在多线程场合下,最重要的就是保障数据的一致性问题,而保障数据一致性问题,就需要借助于锁了。 其实我们在多线程的场景下应该搞清楚一个问题,就是到底什么需要保护?并不是所有的的数据都需要加锁保护,只有那些涉及到被多线程访问的共享的数据才需要加锁保护。 锁的本质其实就是确保在同一时刻,只有一个线程在访问共享数据,那么此时该共享数据就能得到有效的保护。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可.存在以下问题:
下面的售票,取钱示例,那么需要多线程模拟客户去抢票或者取钱。
举例:不安全的售票
package com.example.democrud.democurd.test01;
//不安全的买票
//线程不安全
//3人 可能会拿到同一张票,甚至在最后一张票的时候可能都会将最后一张票执行,就会有-1
public class UnsafeBuyTick {
public static void main(String[] args) {
BuyTicket ticket = new BuyTicket();
new Thread(ticket,"小王").start();
new Thread(ticket,"小李").start();
new Thread(ticket,"小赵").start();
}
}
class BuyTicket implements Runnable{
//票
private int tickNums=10;
Boolean flag=true;
@Override
public void run() {
//买票
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//判断是否有票
public void buy() throws InterruptedException {
//无票;
if (tickNums<=0){
flag=false;
return;
}
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"拿到了,第"+tickNums--);
}
}
举例:银行取钱\
package com.example.democrud.democurd.test01;
import com.example.democrud.democurd.Prototype.demo01.Video;
//不安全的取钱
//两人去银行取钱
public class testUnsafeBank {
public static void main(String[] args) {
Account account = new Account(100, "结婚");
Drawing you = new Drawing(account,50,"你");
Drawing girl = new Drawing(account,100,"媳妇");
you.start();
girl.start();
}
}
class Account {
int money; //余额
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行 模拟取钱
class Drawing extends Thread {
Account account;//账号
int drawingMoney;// 取多少钱
int nowMoney; //现在手里有多少钱
String name;
public Drawing(Account account, int drawingMoney, String name) {
this.account = account;
this.drawingMoney = drawingMoney;
this.name = name;
}
//取钱
@Override
public void run(){
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"取了"+drawingMoney+"钱不够了抱歉");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额=余额-取的钱
account.money=account.money-drawingMoney;
//手里的钱+取的钱
nowMoney=nowMoney+drawingMoney;
System.out.println(account.name+"余额:"+account.money);
System.out.println(this.name+"手里的钱"+nowMoney);
}
}
举例:线程不安全的集合 可参考:ArrayList为什么是线程不安全的:https://blog.csdn.net/qq_42183409/article/details/100586255
package com.can.syn;
import java.util.ArrayList;
import java.util.List;
//线程不安全的集合
//不够安全的原因是因为,两个线程同一瞬间,操作了同一个位置上,把两个数组添加到了同一位置,就覆盖掉了,元素就会少,少的元素就这么来的
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
//lambda表达式
new Thread( ()->{
// synchronized (list) { 解决并发问题
list.add(Thread.currentThread().getName());
// }
}).start();
}
Thread.sleep(100);
//输出大小
System.out.println(list.size());
}
}
举例:JUC 线程安全的集合:CopyOnWriteArrayList
package com.example.democrud.democurd.test01;
import java.util.concurrent.CopyOnWriteArrayList;
public class TestJuc {
public static void main(String[] args) {
//list线程安全的
CopyOnWriteArrayList<String> list= new CopyOnWriteArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
这个博客很好,可参考:synchronized的四种用法 修饰方法 Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来
由于我们可以通过关键字 private 关键字来保证数据对象只能被方法访问,所以我们只要针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种方法: synchronized 方法和 synchronized 块.
同步方法:
public synchronized void method(int args){}
Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,
synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,
修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。
例如:
方法一
public synchronized void method()
{
// todo
}
方法二
public void method()
{
synchronized(this) {
// todo
}
}
写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一与写法二是等价的,
锁的对象都是方法的调用者,就是当前类的实例化对象
使用synchronized 同步方法 (同步方法中无需指定同步监视器,因为同步方法的同步监视器默认就是 this ,就是当前类的对象,如果是静态同步方法就是当前类Class)
package com.can.syn;
//不安全的买票
//线程不安全
//"苦逼的我","牛逼的你","可恶黄牛"可能会拿到同一张票,甚至在最后一张票的时候可能都会将最后一张票执行,就会有-1
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"苦逼的我").start();
new Thread(buyTicket,"牛逼的你").start();
new Thread(buyTicket,"可恶黄牛").start();
}
}
class BuyTicket implements Runnable{
private int ticketNums = 10; //票
private boolean flag = true; //外部停止方式
@Override
public void run() {
//买票
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//此处加油synchronized 更好的解决 线程不安全的问题 加入之后正常
private synchronized void buy() throws InterruptedException {
//判断是否有票
if(ticketNums<=0){
flag = false;
return;
}
//模拟延时
Thread.sleep(10);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
同步块:synchronized(Obj){} Obj 称之为同步监视器
当我们需要锁指定的对象(Account)时就需要使用同步块,我们如果使用同步方法锁,锁run()方法,默认会锁住this,即Drawing对象,这不是我们想锁的,所以我们使用同步块。
package com.can.syn;
import lombok.experimental.Accessors;
//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(100,"结婚基金");
Drawing you = new Drawing(account,50,"你");
Drawing girlFriend = new Drawing(account,100,"girlFriend");
you.start();
girlFriend.start();
}
}
//账户
class Account{
public int money; //余额
public String name; //账户
public Account(int money,String name){
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Drawing extends Thread{
Account account; //账户
//取多少钱
int drawingMoney;
//现在手里有多少钱
int nowMoney;
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
//锁的是变化的对象。即增删改的对象
synchronized(Account){
//判断有没有钱
if(account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"余额不足,取不了");
//余额不够取得钱,return
return;
}
//模拟延时,放大问题的发生性
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 取的钱
account.money = account.money-drawingMoney;
//手里的钱 = 手里的钱 + 去的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name+"余额为:"+account.money);
//Thread.currentThread().getName() = this.getName,因为继承了Thread,所以有Thread的全部方法
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
}
下面是两个静态方法同步的例子。这些方法同步在该方法所属的类对象上。
public class MyClass {
public static synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
}
public static void log2(String msg1, String msg2){
synchronized(MyClass.class){
log.writeln(msg1);
log.writeln(msg2);
}
}
}
这两个方法不允许同时被线程访问。
如果第二个同步块不是同步在 MyClass.class 这个对象上。那么这两个方法可以同时被线程访问。
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥“两个以上对象的锁”时,就可能会发生“死锁”的问题。
产生死锁的四个必要条件:
上述四个条件,只要破坏其任意一个就可避免死锁的发生。
死锁演示:
package com.example.democrud.democurd.test01;
//死锁:多个线程互相用者对象需要的资源,互不释放相互僵持
public class DeadLock {
}
//口红
class Lipstick {
}
//镜子
class Mirror {
public static void main(String[] args) {
Makeup makeupA = new Makeup(0,"A女孩");
Makeup makeupB = new Makeup(1,"B女孩");
makeupA.start();
makeupB.start();
}
}
class Makeup extends Thread {
//需要的资源只有一份,用static来保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;//选择
String girlName;//使用化妆品的人
Makeup(int choice,String girlName){
this.choice=choice;
this.girlName=girlName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆 互相持有对方的锁
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
//1s后他下拿到镜子
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
}
}
} else {
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(1000);
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
}
}
}
}
}
此处就是死锁;他们互相拿着对方需要的东西;互补撒手;你等我 我等你;…
避免代码:
//化妆 互相持有对方的锁
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
//1s后他下拿到镜子
}
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
}
} else {
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(1000);
}
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
}
}
}
synchronized 与 Lock 的对比
package com.example.democrud.democurd.test01;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
Testlock2 testlock2 = new Testlock2();
new Thread(testlock2, "A").start();
new Thread(testlock2, "B").start();
new Thread(testlock2, "C").start();
}
}
class Testlock2 implements Runnable {
// 10张票
int tickNums = 10;
//定义lock锁 私有不可变
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
//打开锁
lock.lock();
if (tickNums > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "的票" + tickNums--);
} else {
break;
}
} finally {
lock.unlock(); //关闭锁
}
}
}
}
应用场景:生产者和消费者问题
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
注意:均是 Object 类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常 llegalMonitorStateException
\
并发写作模型“生产者/消费者模式”–>管程法
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
package com.sjmp.advanced;
// 生产者,消费者,产品,缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
// 生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了"+i+"只鸡");
container.push(new Chicken(i));
}
}
}
class SynContainer{
// 需要一个容器的大小
Chicken[] chickens = new Chicken[10];
// 容器计数器
int count = 0;
// 生产者放入产品
public synchronized void push(Chicken chicken){
// 如果容器满了,就需要等待消费者消费
if (count == chickens.length){
// 通知消费者消费,生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
chickens[count] = chicken;
count++;
this.notifyAll();
}
// 消费者消费产品
public synchronized Chicken pop(){
// 判断能否消费
if(count==0){
// 等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果可以消费
count--;
Chicken chicken = chickens[count];
// 可以通知消费了
this.notifyAll();
return chicken;
}
}
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了-->"+container.pop().id+"只鸡");
}
}
}
// 产品
class Chicken{
int id; //产品编号
public Chicken(int id){
this.id = id;
}
}
这段代码是一个简单的生产者-消费者模型,其中演员(Player)作为生产者生产节目(voice),观众(Wathcher)作为消费者观看节目。TV 类中的 flag 变量用于控制生产者和消费者之间的配合,flag 为 true 表示可以生产,观众需要等待;flag 为 false 表示可以消费,演员需要等待。
package com.example.democrud.democurd.test01;
public class TestSync {
public static void main(String[] args) {
TV tv = new TV(); // 创建一个 TV 实例
Player player = new Player(tv); // 创建演员线程
Watcher watcher = new Watcher(tv); // 创建观众线程
player.start(); // 启动演员线程
watcher.start(); // 启动观众线程
}
}
// 生产者 - 演员
class Player extends Thread{
private final TV tv; // TV 对象
public Player(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) { // 生产 20 个节目
if(i % 2 == 0){
this.tv.play("快乐大本营"); // 生产“快乐大本营”节目
} else {
this.tv.play("天天向上"); // 生产“天天向上”节目
}
}
}
}
// 消费者 - 观众
class Watcher extends Thread{
private final TV tv; // TV 对象
public Watcher(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) { // 观看 20 个节目
tv.watch(); // 观众观看 TV 节目
}
}
}
// 产品 - 节目
class TV{
// 标记,用于控制生产者和消费者之间的配合
// flag 为 true 表示可以生产,观众需要等待;flag 为 false 表示可以消费,演员需要等待。
private boolean flag = true;
private String voice; // 节目名称
// 生产者生产节目
public synchronized void play(String voice){
while(!flag){ // 如果 flag 为 false,则说明已经有其他生产者在生产,当前线程需要等待
try {
this.wait(); // 等待其他线程唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了: "+voice);
// 生产完毕,唤醒其他被阻塞的消费者线程
this.notifyAll();
this.voice = voice; // 存储节目名称
this.flag = false; // 修改标记,指示可以消费
}
// 消费者观看节目
public synchronized void watch(){
while(flag){ // 如果 flag 为 true,则说明已经有其他消费者在观看,当前线程需要等待
try {
this.wait(); // 等待其他线程唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了: "+voice);
// 消费完毕,唤醒其他被阻塞的生产者线程
this.notifyAll();
this.flag = true; // 修改标记,指示可以生产
}
}
这段代码实现了生产者-消费者模型,使用线程同步机制 synchronized 和 wait/notifyAll 方法来控制线程安全和数据共享。其中,演员是生产者,观众是消费者,TV 节目是产品。
为了更好地理解和优化该代码,建议从以下几个方面入手:
添加注释:为代码添加必要的注释,解释类、方法和变量的作用和功能。
简化命名:对于类、方法和变量的命名应简洁明了,能快速表达其含义。
优化代码结构:将多余的空格、括号、换行符等去掉,使代码更加紧凑易读。
增加异常处理:在代码中添加必要的异常处理,防止程序因为异常而崩溃或出错。
尝试使用更简洁的方式:可以使用 Java 并发包中的 Lock 和 Condition 类来替代 synchronized 和 wait/notifyAll 方法,实现更加简洁和高效的线程同步和互斥。
增加日志输出:在代码中增加必要的日志输出,记录程序执行过程中的关键信息,方便调试和排查问题。
引入线程池:当需要创建大量线程时,可以考虑使用线程池来避免频繁创建和销毁线程的开销,提高程序的性能和效率。
通过对代码进行优化和升级,可以使其更加易读易懂、性能更加高效。
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。 可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
优点: 提高响应速度(减少了创建新线程的时间) 降低资源消耗(重复利用线程池中线程,不需要每次都创建) 便于线程管理…
package com.example.democrud.democurd.test01;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Testpool {
public static void main(String[] args) {
//创建线程池 大小为10
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.shutdownNow();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
package com.sjmp.Thread01;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadFutureTest {
public static class CallerTask implements Callable<String>{
@Override
public String call() throws Exception {
return "Thread-Callable- hello";
}
}
public static void main(String[] args) {
// 创建异步任务
FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
new Thread(futureTask).start();
try {
String result = futureTask.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
如上代码中的 CallerTask 类实现了 Callable 接口的 call() 方法。在 main 函数内首先创建了一个 FutrueTask 对象(构造函数为 CallerTask 的实例),然后使用创建的 FutrueTask 对象作为任务创建了一个线程并且启动它,最后通过futureTask.get() 等待任务执行完毕并返回结果。
小结:使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过 set 方法设置参数或者通过构造函数进行传递,而如果使用 Runnable 方式,则只能使用主线程里面被声明为 final 的变量。不好的地方是 Java 不支持多继承,如果继承了 Thread 类,那么子类不能再继承其他类,而 Runable 则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是 Futuretask 方式可以。
package com.example.democrud.democurd.test01;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.function.Function;
public class ThreadNew {
public static void main(String[] args) {
//1.
new MyThread1().start();
//2.
new Thread(new MyThread2()).start();
//3.
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
new Thread(futureTask).start();
try {
Integer integer=futureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//1.继承Thread
class MyThread1 extends Thread {
@Override
public void run() {
System.out.println("我是继承Thread的方法");
}
}
//2.实现Runnable的接口
class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("我是实现Runnable的接口");
}
}
//3.实现callable接口
//Callable<Integer> 中的Integer和重写的call返回值同步的
class MyThread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("我是实现callable接口");
return 10000;
}
}
这个文章只是简单的一个文章是狂神的视频 ;是我一边学一边写的demo;