线程基础

很不严谨的说,线程是什么?线程就是为了让很多个东西并发执行,大大的提高程序执行的效率啊

三个非常重要的概念:

  • 程序:一组写好了的静态代码块(就我们写的那些代码玩意)
  • 进程:正在进行着的程序,即静态的代码执行起来了
  • 线程:是进程的小单元,或者说多个线程构成一个进程(线程的出现是因为进程满足不了人们的需求,于是进程被细化了)

线程的转换(五个较为简单的转换)可以通过这个图了解一下:

在Java中,

线程有三类:

  1. 主线程:系统线程,如Java中的Java虚拟机(主线程是最先执行的)
  2. 用户线程:Java中main函数执行的那些玩意
  3. 守护线程(精灵):比如Java中的GC(垃圾回收器,他是为主线程服务的,当系统启动后,GC就随之产生了;Java虚拟机断掉了,GC也就不干活了)

线程是操作系统层次的(又或者说是cpu层次的,进程怎么执行的,顺序是啥。。。这些都是靠cpu分配的)
不过线程怎么去用,Java的JDK已经给我们写好了

实现线程的过程(有两个方法):

  1. 自己描述一个类
  2. 实现线程的两个前提条件(继承Thread是最方便的,但是Java是单继承的,很可能会和其他的继承冲突;所以还可以通过实现Runnable接口,不过它要额外的通过写一个Thread类来执行start()方法):
    • 方法一:继承一个父类Thread
    • 方法二:实现一个接口Runnable
  3. 我们都必须重写run()方法,
    因为这个方法来源于cpu,是操作系统给JDK提供的接口,JDK将它包装成run(),其实我们执行的线程就是写在run()方法中的代码执行起来了
  4. new一个线程对象(我们是无法调用run()的,它只有cpu才可以调用,cpu分配时间碎片才能开始执行)
    我们只能调用start()方法(这个方法继承自Thread),它会使这个线程除了cpu资源的其它资源都得到满足,让它进入就绪状态,在就绪队列中等待着cpu执行它

(补充:为什么要这么麻烦呢?为什么不直接使用Thread类或者Runnable接口,直接在里面写线程的代码,但是run()方法是不允许有参数的,我们一般要执行的线程都要传参数,所以只能通过继承和实现来解决参数传递的问题)

一个例子】实现线程的两种方式

  • 继承一个父类 Thread,可以看到调用线程时写法较为简单
public class Running extends Thread {String name;public Running(String name){this.name = name;}@Overridepublic void run() {for(int i = 1; i <= 10; i++){System.out.println(name + "跑到了" + i + "米");}}public static void main(String[] args) {Running Lux = new Running("Lux");Running Ahri = new Running("Ahri");Running Annie = new Running("Annie");//从Thread类中继承过来的方法Lux.start();Ahri.start();Annie.start();}
}
  • 实现一个接口 Runnable,可以看到调用线程时写法复杂一些
public class Running implements Runnable {String name;public Running(String name){this.name = name;}@Overridepublic void run() {for(int i = 1; i <= 10; i++){System.out.println(name + "跑到了" + i + "米");}}public static void main(String[] args) {Running Lux = new Running("Lux");Running Ahri = new Running("Ahri");Running Annie = new Running("Annie");//Thread类中才有start()方法Thread thread1 = new Thread(Lux);thread1.start();Thread thread2 = new Thread(Ahri);thread2.start();Thread thread3 = new Thread(Annie);thread3.start();}
}

多次运行线程代码,发现线程无论执行顺序,还是开始执行时间都是随机的(实际上是操作系统通过算法调度和分配资源,JDK是不能影响它的)

我们只需要类中继承或实现线程,重写run()方法,最后调用start()方法就可以了

我们不要直接调用run()方法,这样就不是多线程了,只是按顺序执行的单线程!!!

模拟一个火车站售票小例子

  • 使用控制台输入输出,仅仅是为了简单玩一下线程,例子很简陋
  • 三个类
    • 车票
    • 售票系统12306
    • 售票窗口
  • 代码:github
  • 效果展示:
从广州北站售出:[ 北京11 --> 深圳11 : 150.0 ]
从广州西站售出:[ 北京10 --> 深圳10 : 125.0 ]
从广州南站售出:[ 北京12 --> 深圳12 : 175.0 ]
从广州西站售出:[ 北京14 --> 深圳14 : 225.0 ]
从广州南站售出:[ 北京15 --> 深圳15 : 125.0 ]
...
...
对不起广州南站窗口车票已售完
对不起广州西站窗口车票已售完
对不起广州北站窗口车票已售完

多线程同时读取一个文件

  • 题目:有一个26字节的文件 test.txt,里面按顺序存储着26个小写英文字母,使用3个线程同时读取
  • 使用控制台输入输出,仅仅是为了简单玩一下线程与IO,例子很简陋
  • 分析:
    • 使用字节型输入流,使用skip()方法
    • 1号线程:读取1-10字节
    • 2号线程:读取11-20字节
    • 3号线程:读取21-26字节
  • 代码:github
  • 效果展示:
Thread-0读取:a
Thread-2读取:u
Thread-1读取:k
Thread-2读取:v
Thread-0读取:b
Thread-2读取:w
...
...

生产消费者模型

生产消费者模型

我创建一个生产者向仓库添加物品
两个消费者向仓库中拿走物品

在多线程并发进行中会出现这么一种问题:

如果仓库只剩一个物品时,出现了这么一个顺序

  1. 消费者1进行判断(还剩一个)
  2. 消费者2进行判断(还剩一个)
  3. 消费者1执行get方法
    • get方法作用是:获取物品
  4. 消费者2执行get方法(这时候就出现问题了,虽然判断时仓库还有一个可以拿;但是到执行get方法时,仓库是空的,那么问题出现了:多线程并发抢夺资源
  • 生产者消费者模型

    • 模拟上述描述的问题,并且代码中不涉及其余线程操作,只会出现生产者消费者模型的问题
    • 三个类:Producer、Consumer、WareHouse
    • 分别代表:生产者、消费者、仓库
  • 代码:github
  • 不出所料,在执行的过程中出现问题了
    两个消费者,成功演示出了线程安全问题;两个消费者并发访问,可能产生抢夺资源的问题;所以多个线程并发执行的时候,有安全隐患

synchronized

  • 生产者消费者模型 解决办法

    • 让仓库被线程访问的时候,仓库对象被锁定(即仓库对象只能被一个线程访问,其它的线程处于等待状态)
    • 使用一个特征修饰符即可synchronized,又称为线程安全锁,表示同步的意思,作用:一个时间点只有一个线程访问
    • 给仓库提供的获取物品的方法(get)加上线程安全锁,可以避免多线程并发抢夺资源

synchronized 有两种写法:

  1. synchronized关键字,放在方法的结构上(特征修饰符),其实它锁定的是synchronized修饰方法所在的对象,由调用该方法的线程锁定
public synchronized void test(){代码}
  1. synchronized关键字,放在方法(/构造方法/块)内部
public void test(){很多代码synchronized(对象){   //只有方法执行到这里的时候才被锁定//锁定哪个对象呢?可以由synchronized()的参数决定某些代码}很多代码
}

第二种写法性能更加棒;而且更为灵活,给内部的synchronized(){}的参数传对象,不仅可以由调用方法的线程锁定,还可由其他线程对象来锁定

线程对象改变引起的异常

  • 如果线程调用的方法中有wait()notify()/notifyAll()方法,但是没有加synchronized线程安全锁时会出现一种异常:java.lang.IllegalMonitorStateExceptionr

  • 注意:wait和notify需要成对出现,或者wait和notifyAll成对出现,否则可能出现假死锁

    • wait() 线程等待
    • notify() 唤醒某个线程
    • notifyAll() 唤醒所有线程
    • wait() notify() notifyAll()都是Object的方法,但和synchronized关键字一样,也是哪个线程调用则对哪个线程起作用

在生产者消费者模型代码基础上进行修改,仓库的获取物品与存放物品的方法不加线程安全锁,并且使用线程等待与线程唤醒方法

这就是张冠李戴的字面解释了,当访问仓库的生产者线程等待,告知生产者的这一刹那,对象变成了另一个线程:该等待的没有等待,不该等待的等待了

join

  • join()方法也是Thread类中的方法,挺有用的
  • 可以让两个并行的线程变成单线程

线程创建好之后,我们不能控制线程执行的先后顺序,我们只能让线程进入就绪状态

例子】首先来回顾一下线程并发执行的情况,以线程ThreadOne与ThreadTwo为例:

ThreadOne one = new ThreadOne();
ThreadTwo two = new ThreadTwo();
one.start();
two.start();
  • one执行,one结束,two执行,two结束
  • one执行,two执行,one结束,two结束
  • one执行,two执行,two结束,one结束

例子】如果有两个线程ThreadOne和ThreadTwo,我想让ThreadTwo加入到ThreadOne中,可以这么写:

ThreadOne

public class ThreadOne extends Thread {public void run() {System.out.println("thread-one start");ThreadTwo two = new ThreadTwo();two.start();try {two.join();//线程2加入到线程1中} catch (InterruptedException e) {e.printStackTrace();}System.out.println("thread-one end");}
}

ThreadTwo

public class ThreadTwo extends Thread{public void run() {System.out.println("thread-two start");try {Thread.sleep(5000);//睡眠5秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("thread-two end");}
}

在main中,调用ThreadOne线程的start()方法:

  • 这里用的是join()的无参方法,thread-one启动后,必须要等着thread-two执行完,才能往后面执行

  • join(millis)还有一个有参重载方法

将ThreadOne类中的 two.join() 改为 two.join(4000)

thread-one启动后,不是必须等待着thread-two执行完,而是等待4000毫秒如果thread-two还没执行完,thread-one就继续往后执行

join源码结合例子分析

【join源码】

  • 无参的 join() 方法:
public final void join() throws InterruptedException {join(0);//还是会调用有参的join方法
}
  • 有参的 join() 方法:
public final synchronized void join(long millis) throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) { //这就是调用无参join时的执行代码while (isAlive()) {wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}
}
  • ThreadOne类中调用的无参 two.join()

    • 只要 ThreaTwo 活着,那么 ThreadOne 就一直是等待状态
while (two.isAlive()) {two.wait(0);
}
  • ThreadOne类中调用的有参且参数大于0 two.join(4000)

    • ThreaTwo 活着的期间,ThreadOne 等待millis毫秒后打破循环,即不再等待了
while (two.isAlive()) {long delay = millis - now;if (delay <= 0) {break;}two.wait(delay);now = System.currentTimeMillis() - base;
}

join遇上线程安全锁

  • 感受一下 synchronized 的强大吧

    • 三个线程类:ThreadOne、ThreadTwo、ThreadThree
    • ThreadOne 中调用 ThreadTwo,并 two.join(2000);,不过 ThreadTwo 需要执行5秒
    • ThreadTwo 中调用 ThreadThree,并将自身通过参数传给 ThreadThree
    • ThreadThree 通过 synchronized 将 ThreadTwo 锁起来10秒
  • 代码:github
  • 执行效果
  1. one启动
  2. two启动
  3. three启动
  4. two就join进one中了(two要执行5000毫秒,但是one只给了two2000毫秒)
  5. 2000毫秒之后,one想要把two从自己的线程中剔除掉;但是发现,two已经不在自己的手中,two已经被three锁定(要被锁定10000毫秒)
  6. one只能等待three将two释放后才能剔除掉two

所以,锁是非常强大滴

死锁

  • synchronized非常厉害,一旦线程被锁定,不释放的情况,其它的线程都需要等待
  • 如果锁没用好,有可能产生死锁的问题(互相都想要对方的资源,却都得不到满足)

这么说吧,
Ahri手里有a资源,然后需要b资源
Lux手里有b资源,然后需要a资源
而且,a资源被Ahri独占了,其它进程拿不到;b资源被Lux独占了,其它进程都拿不到
那么:
Ahri得不到Lux的b资源
Lux得不到Ahri的a资源
就会这样:
Ahri和Lux一直等着对方释放资源

这就是死锁

哲学家进餐问题

哲学家进餐问题 是一个著名的死锁问题:

四个人在餐桌上吃饭,但是只有四只筷子,
四个人同时拿筷子,
而且要求,每一个人先拿左手边的筷子,然后拿右手边的筷子

所以嘛,有可能他们四个人拿筷子的速度恰好一样,都拿了左手边的筷子,于是僵持住了

  • 代码:github

避免死锁的方法:

  • 礼让----产生时间差
  • 不要产生共用的问题

有趣的Timer类

  • 计时器/定时器是JDK已经写好了的线程类
  • java.util包,Timer

小例子】用Timer类模拟一个短信轰炸的效果(每隔一段时间发送信息)

Timer有这么一个方法schedule(),有四个重载方法

对上面出现的参数的解释

  1. TimerTask task表示一个任务,以字符串形式表示
    TimerTask是一个抽象类,new不了的,不过可以使用匿名内部类对象
  2. Date timeDate firstTime表示起始时间(到了起始时间,任务开始执行)
  3. long delay表示延迟时间(及延迟给的时间后,任务开始执行)
  4. long period周期,表示多长时间后再干一次,是一个循环

短信轰炸

//导包
//...
public class TestTimer {private int count = 1;//记录轰炸次数ArrayList<String> userBox = new ArrayList<>();//存储众多个人信息{userBox.add("a"); userBox.add("b"); userBox.add("c"); userBox.add("d");}启动一个小线程,记录时间,可以每隔一段时间去做一件事情public void test() throws ParseException {System.out.println("开始啦");Timer timer = new Timer();SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date firstTime = sdf.parse("2020-07-27 16:30:00");timer.schedule(new TimerTask() {public void run() {System.out.println("第" + count++ + "次执行");for(int i = 0; i < userBox.size(); i++){System.out.println("给" + userBox.get(i) + "发送了一条消息:陌生人祝你幸福");}System.out.println("做了点坏事儿 真开心~~~");}},firstTime,3000);}public static void main(String[] args) {TestTimer demo = new TestTimer();try{demo.test();}catch (ParseException e){e.getErrorOffset();}}}

Java 线程 基础知识总结相关推荐

  1. java线程基础知识

    Java 多线程编程 Java 给多线程编程提供了内置的支持. 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务. 多线程是多任务的一种特别的形式,但多 ...

  2. java线程基础知识整理

    线程基本概念 1.什么是进程?什么是线程? 进程是一个应用程序,线程是一个进程中的执行场景/执行单元.一个进程可以启动多个线程.在java语言中对于两个线程A和B,堆内存和方法区内存共享.但是栈内存独 ...

  3. Java中的线程基础知识

    Java中的线程基础知识 1.线程概念 线程是程序运行的基本执行单元.当操作系统(不包括单线程的操作系统,如微软早期的DOS)在执行一个程序时,会在系统中建立一个进程,而在这个进程中,必须至少建立一个 ...

  4. 你觉得什么才是 Java 的基础知识?

    近日里,很多人邀请我回答各种j2ee开发的初级问题,我无一都强调java初学者要先扎实自己的基础知识,那什么才是java的基础知识?又怎么样才算掌握了java的基础知识呢?这个问题还真值得仔细思考. ...

  5. 线程基础知识系列(三)线程的同步

    本文是系列的第三篇,前面2篇,主要是针对单个线程如何管理,启动等,没有过多涉及多个线程是如何协同工作的. 线程基础知识系列(二)线程的管理 :线程的状态,控制,休眠,Interrupt,yield等 ...

  6. hashcode是什么意思_什么才是 Java 的基础知识?

    作者:晓风轻 链接:zhuanlan.zhihu.com/p/28615617 近日里,很多人邀请我回答各种j2ee开发的初级问题,我无一都强调java初学者要先扎实自己的基础知识,那什么才是java ...

  7. JAVA NIO基础知识

    本文来说下JAVA NIO基础知识. 文章目录 NIO概述 NIO简介 NIO的特性/NIO与IO区别 读数据和写数据方式: NIO核心组件简单介绍 Java NIO 之 Buffer(缓冲区) Bu ...

  8. Java面试基础知识III

    Java面试基础知识: 1.C++或Java中的异常处理机制的简单原理和应用. 当JAVA 程序违反了JAVA的语义规则时,JAVA虚拟机就会将发生的错误表示为一个异常.违反语义规则包括2种情况.一种 ...

  9. Java SE 基础知识

    Java SE 基础知识 1 2 @(Notes)[J2SE, Notes] VICTORY LOVES PREPARATION. 特别说明: 该文档在马克飞象查阅最佳: 本部分知识还在迭代中,欢迎补 ...

最新文章

  1. 46. AngularJS所有版本下载
  2. 说说我为什么看好Spring Cloud Alibaba
  3. Nuxt项目中使用axios
  4. web性能测试基础 知识(引用)
  5. 荣耀鸿蒙系统开机动画,荣耀赵明:鸿蒙系统首发设备欲屏蔽开机广告
  6. 【Linux进程、线程、任务调度】一 Linux进程生命周期 僵尸进程的含义 停止状态与作业控制 内存泄漏的真实含义 task_struct以及task_struct之间的关系
  7. php做游戏登录服务器,游戏登陆服务器php简单实现
  8. python opencv 直方图均衡化_Python opencv—直方图/直方图均衡化/直方图比较,pythonopencv...
  9. win10下添加ssh服务
  10. Zabbix如何通过ODBC对接Oracle获取相关数据
  11. 小米华为鸿蒙,华为鸿蒙比小米MIUI快,老外上手出结果
  12. php7.4报错:Trying to access array offset on value of type null
  13. Fisher精确检验与卡方检验
  14. 王利杰:我做天使投资的心路历程
  15. 大数据开发有哪些难点?
  16. HTML学习记录三 :创建电子邮件链接
  17. php使用QQ登录API,QQ的账号登录及api操作
  18. 基于ThinkPHP6+Layui后台开发框架
  19. chatbot聊天机器人环境搭建以及项目运行指南
  20. Android studio 启动模拟器出现 VT-x is disabled in BIOS 以及 /dev/kvm is not found

热门文章

  1. Auto.js修改QQ语音+破解闪照
  2. TCP/IP多路复用
  3. 【LeetCode】回溯 N皇后(DFS、子集、组合问题)
  4. 锐起无盘精华100问!(包括3.1,3.0版本)
  5. acwing349 黑暗城堡 ——最短路径生成树
  6. 获取光标位置及动态设置光标到指定位置
  7. Liunx wget命令
  8. Navicat Premium 15.0.26 MacOS
  9. 考研操作系统【1.1 操作系统的基本概念】
  10. ebay免费模板html,ebay免费模板