发布时间:2023-04-11 10:00
前言:
最近在回顾JavaSE部分的知识,对一些薄弱的知识进行记录,学习方式,通过视频和图书的进行学习,视频看B站韩顺平老师的【韩顺平讲Java】一天学会线程 Thread Synchronized 互斥锁 进程 并行 并发 死锁等,图书看Java核心技术 卷I 基础知识(原书第10版)。
【韩顺平讲Java】一天学会线程 Thread Synchronized 互斥锁 进程 并行 并发 死锁等:https://www.bilibili.com/video/BV1zB4y1A7rb?spm_id_from=333.999.0.0
是为完成特定任务、用某种语言编写的一组指令的集合。简单的说就是我们写的代码(数据结构+算法)。
备注:软件不等于程序,软件可以简单理解为由相关开发文档和程序组成
① 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
② 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程。
③进程是系统进⾏资源分配和调度的独⽴单位,每⼀个进程都有它⾃⼰的内存空间和系统资源。
为了提⾼系统的执⾏效率,减少处理机的空转时间和调度切换的时间,以及便于系统管理,所以有了线程,线程取代了进程调度的基本功能(线程由进程创建,是进程的一个实体)。
单线程:
单线程:同一个时刻,只允许执行一个线程
多线程:
多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
并发:
并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说单核cpu实现的多任务就是并发。
并行:
并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。
创建线程的方式:
方式一(通过重写Runnable 接口的run方法)
public class RunnableTest implements Runnable
{
@Override
public 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表达式
运行效果:
方式二(通过继承 Thread 类,重写run方法):
class Thread_NEW extends Thread {
@Override
public 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。
方式三(通过 Callable 和 Future 创建线程):
class Test3{
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask(new RunnableTest(),22);
futureTask.run();
System.out.println(\"返回结果:\"+futureTask.get());// 获取返回结果
FutureTask<Boolean> futureTask1 = new FutureTask<>(new Callable() {
@Override
public Object call() throws Exception {
for (int i = 1; i <= 10; i++) {
System.out.println(\"执行方式四:执行第\" + i + \"次\");
}
return true;
}
});
futureTask1.run();
System.out.println(\"返回结果:\" + futureTask1.get());
}
}
运行效果:
FutureTask 与Future 和Runnable的关系图如下:
进入到FutureTask的源码中,右击鼠标选择Diagrams,选择show Diagram…
从下图可知FutureTask是RunnableFuture接口的实现类,该接口继承 Runnable
和 Future
接口
分析方式三:
FutureTask类通过Runnable接口进行初始化实际上还是在初始化 Callable
通过 Executors.callable(runnable, result);
获取Callable
FutureTask类可以通过get()方法获取线程中方法体的返回值 outcome
,如果初始化FutureTask的时候传递的是Runnable接口,通过get()方法获取的就是传入的result。
FutureTask通过run()方法开启线程
在执行run()方法的时候调用call()执行方法体,并将返回值赋值给result,然后通过set(result)初始化outcome
① 当线程完成任务后,会自动退出。
② 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
需求:主线程休眠1s之后停止子线程运行,实现代码如下:
class RunnableController implements Runnable
{
// 定义一个线程状态标识
private boolean start;
private int i;
public RunnableController() {
this.start = true;
}
@Override
public 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~10
Thread thread2 = new Thread(()-> System.out.println(\"当前线程名称:\"+Thread.currentThread().getName()+\"该线程的优先级:\"+Thread.currentThread().getPriority()));
thread2.setName(\"线程2\");
thread2.setPriority(10);// 1~10
Thread 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
优先级越高并不是一定先执行,只是获得更多的执行机会
@Test
public 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()方法!
@Test
public void test2() {
Thread thread = new Thread(() -> System.out.println(\"-------调用run方法-----\" + Thread.currentThread().getName()));
thread.run();
}
中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
public class Thread_01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+\"吃包子-----------------\"+i);
}
try {
System.out.println(Thread.currentThread().getName()+\"休眠中----------\");
Thread.sleep(20000);
} catch (InterruptedException e) {
// 当该线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码
System.out.println(Thread.currentThread().getName()+\"被interrupt了\");
}
}
@Test
public void test3() throws InterruptedException {
Thread_01 thread_01 = new Thread_01();
thread_01.setPriority(Thread.MIN_PRIORITY);
thread_01.start();
Thread.sleep(3000);
thread_01.interrupt();
}
}
public class Thread_02 extends Thread {
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(\"JoinThread----------------\"+i);
}
}
@Test
public void test4() throws InterruptedException {
Thread_02 thread_02 = new Thread_02();
thread_02.start();
thread_02.join();// 插队
for (int i = 0; i <= 20; i++) {
Thread.sleep(50);
System.out.println(\"张三丰\"+i);
}
}
}
用户线程:
用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
守护线程:
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束,常见的守护线程,垃圾回收机制。
通过 setDaemon(true);
设置该线程为守护线程,实例代码如下:
@Test
public 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 枚举表示了线程的几种状态
具体代码如下:
线程状态转换图
下面的内容来源于:https://www.pdai.tech/md/java/thread/java-thread-x-thread-basic.html
进入方法 | 退出方法 |
---|---|
没有设置 Timeout 参数的 Object.wait() 方法 | Object.notify() / Object.notifyAll() |
没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 |
LockSupport.park() 方法 | - |
进入方法 | 退出方法 |
---|---|
Thread.sleep() 方法 | 时间结束 |
设置了 Timeout 参数的 Object.wait() 方法 | 时间结束 / Object.notify() / Object.notifyAll() |
设置了 Timeout 参数的 Thread.join() 方法 | 时间结束 / 被调用的线程执行完毕 |
LockSupport.parkNanos() 方法 | - |
LockSupport.parkUntil() 方法 | - |
什么是线程同步:
① 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
② 也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作.。
同步具体方法使用Synchronized关键字:
同步原理:
类似于生活中,在寝室上厕所,一次进入一个人(线程),等线程出来之后,再放其他线程进入(这里包括从厕所出来的线程),进入就关门(上锁,互斥锁),出来开门。
需求:网上售票,一段时间同时有多人进行购票,要保证同一张票不能被多人购买,造成票数出现负数情况。
出现票数负数的代码:
package com.zm.synchronization;
public class SynChronZedTest {
public static void main(String[] args) {
RunnableNew runnableNew = new RunnableNew();
Thread thread = new Thread();
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;
@Override
public 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));
}
}
}
改进使用同步代码块:
package com.zm.synchronization;
public class SynChronZedTest {
public static void main(String[] args) {
RunnableNew runnableNew = new RunnableNew();
Thread thread = new Thread();
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;
@Override
public 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));
}
}
}
}
改进使用同步方法:
package com.zm.synchronization;
public class SynChronZedTest {
public static void main(String[] args) {
RunnableNew runnableNew = new RunnableNew();
Thread thread = new Thread();
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;
@Override
public 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进行修饰保证数据共享
详细代码如下:
package com.zm.synchronization;
public class SynChronZedTest {
public static void main(String[] args) {
Thread thread = new Thread();
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;
@Override
public 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换成RunnableNew.class或同步方法加上static关键字(提高锁对象的作用域)
运行效果( 问题解决
):
上面的操作类似与下图:
实际上像上面这种操作,效率是非常低的,运行多次发现大多数情况下只有一个线程在跑
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生.
package com.zm.synchronization;
import org.junit.Test;
public class DeadLockDemo extends Thread {
private static Object playPhone = new Object();
private static Object doHomerWork = new Object();
private boolean tage;
public DeadLockDemo(boolean tage) {
this.tage = tage;
}
@Override
public void run() {
if (tage) {
// 先玩手机再做作业
synchronized (playPhone) {
System.out.println(Thread.currentThread().getName() + \":我在玩手机不要来管喔!\");
synchronized (doHomerWork) {
System.out.println(Thread.currentThread().getName() + \":妈妈,我手机玩好啦,开始做作业啦\");
}
}
} else {
synchronized (doHomerWork) {
System.out.println(Thread.currentThread().getName() +\":儿子,快去做作业,做完再玩!\");
synchronized (playPhone) {
System.out.println(Thread.currentThread().getName() +\":儿子真乖,去玩会手机吧\");
}
}
}
}
public static void main(String[] args) {
DeadLockDemo mom = new DeadLockDemo(false);
mom.setName(\"妈妈\");
DeadLockDemo son = new DeadLockDemo(true);
son.setName(\"儿子\");
mom.start();
son.start();
}
}