个人推荐:
📢📢📢 前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下
"通俗易懂,风趣幽默"
,感觉非常有意思,忍不住分享一下给大家。点击跳转到教程。
前言:
📢📢📢本篇博文主要对B站韩顺平老师的<<一天学会线程 Thread Synchronized 互斥锁 进程 并行 并发 死锁等:>>和廖雪峰博客多线程进行学习。
【韩顺平讲Java】一天学会线程 Thread Synchronized 互斥锁 进程 并行 并发 死锁等:=333.999.0.0
廖雪峰多线程连接:
是为完成特定任务、用某种语言编写的一组指令的集合。简单的说就是我们写的代码(数据结构+算法)。
备注:软件不等于程序,软件可以简单理解为由相关开发文档和程序组成
① 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
② 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程。
③进程是系统进⾏资源分配和调度的独⽴单位,每⼀个进程都有它⾃⼰的内存空间和系统资源。
为了提⾼系统的执⾏效率,减少处理机的空转时间和调度切换的时间,以及便于系统管理,所以有了线程,线程取代了进程调度的基本功能(线程由进程创建,是进程的一个实体)。
单线程:
单线程:同一个时刻,只允许执行一个线程
多线程:
多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
并发:
并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说单核cpu实现的多任务就是并发。
并行:
并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。
创建线程的方式:
public class RunnableTest implements Runnable
{@Overridepublic void run() {for (int i = 1; i <= 10; i++) {System.out.println("执行方式一:执行第" + i + "次");}}
}class Test{public static void main(String[] args) {// 方式一Thread thread = new Thread(new RunnableTest());thread.start();// 方式二Thread thread1 = new Thread(()->{for (int i = 1; i <= 10; i++) {System.out.println("执行方式二:执行第" + i + "次");}});thread1.start();}
}
备注: ()->{}
这种写法是Java8的新特性Lambda表达式
运行效果:
class Thread_NEW extends Thread {@Overridepublic void run() {// 重写run方法for (int i = 1; i <= 10; i++) {System.out.println("执行方式三:执行第" + i + "次");}}
}class Test2{public static void main(String[] args) {Thread_NEW thread_new = new Thread_NEW();thread_new.start();}
}
运行效果:
分析方式一和方式二:
线程通过start()方法启动,实际上该方法调用的是start0(),该方法被native关键字修饰,表示该方法是一个外部方法。
start()方法调用start0()方法后,该线程并不一定会立马执行,只是将线程变成了可运行状态。具体什么时候执行,取决于CPU,由CPU统一调度。
线程的执行方法体就是Runnable接口的run方法(下面的代码在Thread类中)
我们需要执行的内容,只需要重写Runnable接口的run方法,在run方法中编写我们要执行的内容即可
方式一和方式二区别:
① 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口。
② 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable。
class Test3{public static void main(String[] args) throws ExecutionException, InterruptedException {// 方式1Callable callable1 = new CallableTask();// 创建Callable接口FutureTask futureTask1 = new FutureTask(callable1);// 创建FutureTaskThread thread1 = new Thread(futureTask1);// 创建线程thread1.start();// 启动线程System.out.println("方式1:执行情况:"());// 获取返回结果// 方式2FutureTask futureTask2 = new FutureTask(()->{try {System.out.println("执行任务");System.out.println("计算:4/2="+4/2);} catch (Exception e) {return "fail:"Message();}return "Success";});// 创建FutureTaskThread thread2 = new Thread(futureTask2);// 创建线程thread2.start();// 启动线程System.out.println("方式2:执行情况:"());// 获取返回结果}
}// 实现Callable接口,并重写 call()方法
class CallableTask implements Callable<String>{@Overridepublic String call() throws Exception {try {System.out.println("执行任务");System.out.println("计算:4/0="+4/0);} catch (Exception e) {return "fail:"Message();}return "Success";}
}
运行效果:
分析方式三:
FutureTask<V>
类是实现于 RunnableFuture<V>
接口:RunnableFuture<V>
接口继承于 Runnable
和 Future<V>
接口Executors.callable(runnable, result);
获得了一个Callable 对象,并通过该Callable对象进行初始化。① 当线程完成任务后,会自动退出。
② 还可以通过使用变量来控制run方法退出的方式来停止线程,即通知方式
需求:主线程休眠1s之后停止子线程运行,实现代码如下:
class RunnableController implements Runnable
{// 定义一个线程状态标识private boolean start;private int i;public RunnableController() {this.start = true;}@Overridepublic void run() {while (start) {++i;System.out.println("执行方式一:执行第" + i + "次");}}// 通过set进行属性设置public void setStart(boolean start) {this.start = start;}
}class Test4{public static void main(String[] args) throws InterruptedException {RunnableController runnableController = new RunnableController();Thread thread = new Thread(runnableController);thread.start();// 1s钟后停止线程Thread.sleep(1000);runnableController.setStart(false);}
}
运行效果:
public class ThreadMethod {public static void main(String[] args) {Thread thread = new Thread(()-> System.out.println("当前线程名称:"+Thread.currentThread().getName()+"该线程的优先级:"+Thread.currentThread().getPriority()));thread.setName("线程1");thread.setPriority(1);// 1~10Thread thread2 = new Thread(()-> System.out.println("当前线程名称:"+Thread.currentThread().getName()+"该线程的优先级:"+Thread.currentThread().getPriority()));thread2.setName("线程2");thread2.setPriority(10);// 1~10Thread thread3 = new Thread(()-> System.out.println("当前线程名称:"+Thread.currentThread().getName()+"该线程的优先级:"+Thread.currentThread().getPriority()));thread2.setName("线程3");thread.start();thread2.start();thread3.start();}
}
运行效果:
优先级必须设置在1~10闭区间,不然会引发
IllegalArgumentException
异常,数值越大优先级越高,
默认优先级为5,最小优先级为1,最大优先级为10
优先级越高并不是一定先执行,只是获得更多的执行机会
@Testpublic void test() {Thread.currentThread().setPriority(6);for (int j = 0; j < 50; j++) {if (j == 10) {Thread thread1 = new Thread(() -> {for (int i = 0; i < 50; i++) {System.out.println("优先级为---------" + Thread.currentThread().getPriority());}});thread1.setPriority(1);thread1.start();} else if (j == 20) {Thread thread2 = new Thread(() -> {for (int i = 0; i < 50; i++) {System.out.println("优先级为---------" + Thread.currentThread().getPriority());}});thread2.setPriority(10);thread2.start();}}}
运行效果:
注意:执行run()方法并不是开启线程,只是通过对象调用run()方法!
@Testpublic void test2() {Thread thread = new Thread(() -> System.out.println("-------调用run方法-----" + Thread.currentThread().getName()));thread.run();}
运行效果:
调用sleep会让当前线程从Running进入Timed Waiting状态(阻塞),其他线程可以通过interrupt方法打断正在休眠的线程,这时sleep方法会抛出InterruptException异常,睡眠结束后的线程未必会立刻执行,最后推荐使用TimeUnit的sleep代替Thread的sleep来获得更好的可读性。
TimeUnit可以指定休眠时间单位(sleep只有毫秒为单位)
实例代码:
运行效果:
中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程(sleep)和等待(wait)的线程
实例代码:
运行效果:
我们可以通过isInterrupted()判断当前线程是否被中断,返回true表示是,返回false表示否,此方法不会影响中断标志的状态,当调用interrupted()方法可以获取中断情况但是会改变中断的标志状态。:
实例代码:
运行效果:
实例代码:
运行效果:
调用yield会让当前线程从Running进入Runnable就绪状态,然后调度执行其他线程,具体的实现依赖于操作系统的任务调度器,所以礼让的时间不确定,也不一定定礼让成功。
实例代码:
运行效果( 由于礼让不一定成功无法控制,所以不怎么使用,也看不出什么效果
):
线程插队插队成功后,先执行完插入的形成的所有任务,再执行后面的任务,调用join可以表示等待某一个线程结束。
实例代码:
运行效果:
可以通过给join传入一个时间来设置最大等待时间:
运行效果:
用户线程:
用户线程:也叫工作线程,当线程的任务执行完或通知方式结束。
守护线程:
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束,常见的守护线程,垃圾回收机制。
通过 setDaemon(true);
设置该线程为守护线程,实例代码如下:
@Testpublic void test5() throws InterruptedException {Thread thread = new Thread(()->{try {while (true){System.out.println("小偷正在偷钱..");Thread.sleep(50);}} catch (InterruptedException e) {e.printStackTrace();}});// 将thread设置为守护线程thread.setDaemon(true);thread.start();for (int i = 0; i < 50; i++) {System.out.println("警察正在来的路上.....");Thread.sleep(50);}System.out.println("警察赶到,小偷被抓,小偷停止偷钱");}
运行效果:
JDK 中用 Thread.State 枚举表示了线程的几种状态
具体代码如下:
线程状态转换图
下面的内容来源于:/md/java/thread/java-thread-x-thread-basic.html
进入方法 | 退出方法 |
---|---|
没有设置 Timeout 参数的 Object.wait() 方法 | |
没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 |
LockSupport.park() 方法 | - |
进入方法 | 退出方法 |
---|---|
Thread.sleep() 方法 | 时间结束 |
设置了 Timeout 参数的 Object.wait() 方法 | 时间结束 / ify() / ifyAll() |
设置了 Timeout 参数的 Thread.join() 方法 | 时间结束 / 被调用的线程执行完毕 |
LockSupport.parkNanos() 方法 | - |
LockSupport.parkUntil() 方法 | - |
什么是线程同步:
① 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
② 也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作.。
③ 被Synchronized关键字修饰的同步方法的效率会变低。
同步使用Synchronized关键字进行修饰,共二种写法同步代码块和同步方法,如下:
同步原理:
类似于生活中,在寝室上厕所,一次进入一个人(线程),等线程出来之后,再放其他线程进入(这里包括从厕所出来的线程),进入就关门(上锁,互斥锁),出来开门。
需求:网上售票,一段时间同时有多人进行购票,要保证同一张票不能被多人购买,造成票数出现负数情况。
出现票数负数的代码:
synchronization;public class SynChronZedTest {public static void main(String[] args) {RunnableNew runnableNew = new RunnableNew();Thread thread = new Thread(runnableNew);thread.setName("售票窗口1");thread.start();Thread thread2 = new Thread(runnableNew);thread2.setName("售票窗口2");thread2.start();Thread thread3 = new Thread(runnableNew);thread3.setName("售票窗口3");thread3.start();Thread thread4 = new Thread(runnableNew);thread4.setName("售票窗口4");thread4.start();}
}class RunnableNew implements Runnable {private Integer tick = 50;@Overridepublic void run() {while (true) {if (tick <= 0) {System.out.println("售票结束");break;}try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}// 进行出售System.out.println(Thread.currentThread().getName() + "售出,还剩票数" + (--tick));}}
}
改进使用同步代码块:
synchronization;public class SynChronZedTest {public static void main(String[] args) {RunnableNew runnableNew = new RunnableNew();Thread thread = new Thread(runnableNew);thread.setName("售票窗口1");thread.start();Thread thread2 = new Thread(runnableNew);thread2.setName("售票窗口2");thread2.start();Thread thread3 = new Thread(runnableNew);thread3.setName("售票窗口3");thread3.start();Thread thread4 = new Thread(runnableNew);thread4.setName("售票窗口4");thread4.start();}
}class RunnableNew implements Runnable {private Integer tick = 50;@Overridepublic void run() {while (true) {synchronized (RunnableNew.class) {if (tick <= 0) {System.out.println("售票结束");break;}try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}// 进行出售System.out.println(Thread.currentThread().getName() + "售出,还剩票数" + (--tick));}}}
}
运行效果:
改进使用同步方法:
synchronization;public class SynChronZedTest {public static void main(String[] args) {RunnableNew runnableNew = new RunnableNew();Thread thread = new Thread(runnableNew);thread.setName("售票窗口1");thread.start();Thread thread2 = new Thread(runnableNew);thread2.setName("售票窗口2");thread2.start();Thread thread3 = new Thread(runnableNew);thread3.setName("售票窗口3");thread3.start();Thread thread4 = new Thread(runnableNew);thread4.setName("售票窗口4");thread4.start();}
}class RunnableNew implements Runnable {private boolean tage = true;private Integer tick = 50;@Overridepublic void run() {while (tage) {sell();}}private synchronized void sell() {if (tick <= 0) {System.out.println("售票结束");this.tage = false;return;}try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}// 进行出售System.out.println(Thread.currentThread().getName() + "售出,还剩票数" + (--tick));}
}
运行效果:
其实上面二种方式本质是一样的:
使用this进行上锁和未用static修饰的同步方法进行上锁本质上是一致的都是对当前对象进行上锁(上面锁的就是runnableNew对象,四个线程都是使用该对象进行上锁可以保证线程同步)
为了验证上面的说法,这里我们给四个线程分别传递不同的RunnableNew对象,该对象中tick通过static进行修饰保证数据共享
详细代码如下:
synchronization;public class SynChronZedTest {public static void main(String[] args) {Thread thread = new Thread(new RunnableNew());thread.setName("售票窗口1");thread.start();Thread thread2 = new Thread(new RunnableNew());thread2.setName("售票窗口2");thread2.start();Thread thread3 = new Thread(new RunnableNew());thread3.setName("售票窗口3");thread3.start();Thread thread4 = new Thread(new RunnableNew());thread4.setName("售票窗口4");thread4.start();}
}class RunnableNew implements Runnable {private static Integer tick = 50;@Overridepublic void run() {while (true) {synchronized (this) {if (tick <= 0) {System.out.println("售票结束");break;}try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}// 进行出售System.out.println(Thread.currentThread().getName() + "售出,还剩票数" + (--tick));}}}
}
运行效果( 再次运行会发现票数出现负数
):
上面的操作类似于下图:
使用this表示对当前对象进行上锁,但是传入的几个对象都不相同所以当tick=3时,四个线程同时进入,且tick>0满足条件,d都执行tick-1,所以最后的结果为tick=-1。
改进方法,同步代码块中将this换成RunnableNew.class或同步方法加上static关键字(提高锁对象的作用域)
运行效果( 问题解决
):
上面的操作类似与下图:
通过.class进行上锁是对类进行上锁,四个对象都属于同一个类,所以当有一个对象执行时就会上锁,另外三个就无法进入,需要等待进去的线程出来后,再次找机会进入。
实际上像上面这种操作,效率是非常低的,运行多次发现大多数情况下只有一个线程在跑:
本文发布于:2024-02-03 00:36:06,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170689176647502.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |