测试方向基础-多线程知识和常见示例
文章目录
- 线程和多线程
- 程序、进程、线程的区别:
- 线程的声明周期:
- 用java语言创建多线程:
- 多线程的同步和死锁问题:
- 常见线程同步案例
- 生产者消费者示例1:
- 生产者消费者示例2
- 卖票示例
- 车间玩具制造示例
线程和多线程
个人理解:多线程产生的意义在于提高效率,什么样的程序需要使用多线程?比如一个程序是卖饭的一个过程,多个线程就相当于多个厨师,厨师多了,再需求不变的情况下效率就高了,但是客人给钱是要一个人一个人来的,这样可以避免因为很多人同时交钱而出现账目混乱。所以如果把这个过程比作一个程序,上半段代码写做饭,下半段代码写收钱,上半端代码是提高效率的根本所在,所有线程都可以同步执行,下半段代码需要加锁,所有线程竞争cpu使用权,一次只能执行一个。
程序、进程、线程的区别:
- 程序是一段静态的代码,是应用软件执行的蓝本。
- 进程是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也就是进程本身从产生、发展至消亡的过程。
- 线程是比进程更小的执行单位。进程在执行过程中可以产生多个线程,形成多条执行线索,每条线索,即每个线程也有它自身的产生、存在和消亡的过程,也是一个动态的概念。
- 主线程:每个java程序都有一个默认的主线程,当JVM加载代码发现main方法之后,就会立即启动一个线程,这个线程称为主线程。主线程是产生其他子线程的线程,不一定是最后完成执行的线程。
- 单线程:main方法中没有创建其他线程。
- 多线程:main方法中又创建了其他线程。
线程的声明周期:
线程完整的生命周期包括五个状态:新建、就绪、运行、阻塞和死亡。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QxExd8Y4-1647617363437)(C:\Users\yyc\AppData\Roaming\Typora\typora-user-images\image-20220314161847824.png)]
- 新建:线程对象已经创建,还没在其上调用start()方法。
- 就绪状态:当线程调用start方法,但调度程序还没有把它选定为运行线程时所处的状态。
- 运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时所处的状态。这也是线程进入运行状态的唯一方式。
- 阻塞状态:线程仍旧时活得,但是没有条件运行。它时可运行的当某件事件出现,他可能返回到可运行状态。!!!!!注意!!阻塞状态被唤醒后从阻塞代码段开始向下执行。
- 死亡状态:当线程的run()方法完成时就认为它死去。线程一旦死亡,就不能复生。一个死去的线程上调用start()方法,会抛出异常
用java语言创建多线程:
创建线程有两种方式:
通过继承Thread类实现多线程:
public class MyThread extends Thread{public void run(){……}public static void main(String[] args){MyThread t1=new MyThread().start();MyThread t2=new MyThread().start();} }
- 优点:编写简单,访问当前线程直接使用this即可。
- 缺点:不能再继承其他类
实现Runnable接口实现多线程:
public class MyThread implement Runnable{public void run(){……}public static void main(String[] args){MyThread m=new MyThread();Thread t1=new Thread(m);Thread t2=new Thread(m);} }
优点:可以继承其他类,非常适合多个相同线程来处理同一份资源的情况。
缺点:访问当前线程必须使用Thread.currentThread()方法。
常用的方法和常识
setName():自定义线程名字。
getName():返回此线程的名称。
Thread.currentThread():返回对象当前正在执行的线程对象的引用。
start():启动线程,让线程从新建状态进入就绪队列排队。此线程开始执行,Java虚拟机就会调用run()方法
run():线程对象被调度之后执行的操作。
Thread.sleep():暂停线程的执行,让当前线程休眠若干毫秒。
isAlive():测试线程的状态,新建,死亡状态的线程返回false。
interrupt():”吵醒“休眠的线程,唤醒”自己“。
Thread.yield():暂停正在执行的线程,让同等优先级的线程运行,进入就绪状态。
join():当前线程等待调用该方法的线程结束后,再排队等待CPU资源,进入阻塞状态。
stop():终止线程。
setPriority()设置优先级。
wait():
1.wait()是Object里面的方法,而不是Threadl里面的,所以应该在对象上调用该方法而不是在线程上调用。 2.它的作用是将当前线程置于预执行队列,并在wait()所在的代码处停止,等待唤醒通知,唤醒后从暂停出继续执行。 3.wait()只能在同步代码块或者同步方法中执行,如果调用wait()方法,而没有适当的锁,就会抛出异常。 4.wait()方法调用后会释放出锁,线程与其他线程竞争重新获得锁。
notify():该方法也是要在同步代码块或者同步方法中调用的,作用是使停止的线程继续执行。
一个线程只能被启动一次。
JVM调度线程采用队列的形式
多线程的同步和死锁问题:
多线程程序再设计上最大的困难在于各个线程的控制流彼此独立,使得各个线程之间的代码时乱序执行的,而且各个线程共享资源,所以多线程会带来线程调度,同步,死锁等一系列问题。
线程同步:当两个或两个以上线程访问同一资源时,需要某种方式来确保资源再某一时刻只被一个线程使用。
用对象互斥锁来解决:关键字synchronized与对象互斥锁联合起来使用保证对象再任意时刻只能由一个线程访问,synchronized可修饰方法,表示这个方法在任意时刻只能由一个线程访问,synchronized可以修饰类,表示该类的所有对象共用一把锁。下面会有使用例子
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了 锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。
ReentrantLock() 创建一个ReentrantLock的实例
lick() 获得锁
unlock() 释放锁
买票代码示例:【三个窗口同时卖100张票】
public class SellTicket implements Runnable {private int tickets = 100;//创建对象 private Lock lock = new ReentrantLock(); @Override public void run() {while (true) {try {//上锁 lock.lock(); if (tickets > 0) {try {Thread.sleep(100); } catch (InterruptedException e) {e.printStackTrace(); System.out.println(Thread.currentThread().getName() + "正在出售 第" + tickets + "张票"); tickets--; } } finally {//解锁 lock.unlock(); } } } } public class SellTicketDemo {public static void main(String[] args) {SellTicket st = new SellTicket(); Thread t1 = new Thread(st, "窗口1"); Thread t2 = new Thread(st, "窗口2"); Thread t3 = new Thread(st, "窗口3"); t1.start(); t2.start(); t3.start(); } }
死锁:当多个线程共享一个资源的时候需要进行同步,但是过多的同步可能导致死锁:即当两个或两个以上的线程在执行过程中,因争夺资源造成了互相等待,并且若无外力作用,它们都将无法推进下去的现象称为系统处在死锁状态或系统产生了死锁。
- 出现死锁的情况:
- 相互排斥:一个线程永远占用某一共享资源。
- 循环等待:线程A在等待线程B,线程B在等待线程C,线程C在等待线程A。
- 部分分配:线程A得到了资源一,线程B得到了资源二,两个线程都不能得到全部的资源。
- 缺少优先权:一个线程访问了某资源,但一直不释放该资源,即使该线程处于阻塞状态。
- 出现死锁的情况:
常见线程同步案例
生产者消费者示例1:
【使用多线程实现随机生产和消费标有26个大写字母的球,生产出的球放入容量为5的箱子里并从该箱子中消费,要求生产球的总量为10个】
//盒子
public class Box {private int index = 0;private char[] container = new char[5];public synchronized void push(char c) {//判断盒子是否盛满//之前一直以为,线程如果被唤醒后再次执行时,会从头开始运行这个线程,也就是重新运行Runnable中的run()方法;//而实际情况是,被唤醒并且被执行的线程是从上次阻塞的位置从下开始运行,也就是从wait()方法后开始执行。//所以判断是否进入某一线程的条件 是用while判断,而不是用If判断判断。while (index == container.length) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//把球放入箱子中container[index] = c;index++;//唤醒等待的消费者this.notify();}public synchronized char pop() {//判断盒子是否为空while (index == 0) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//消费球index--;char c = container[index];//唤醒等待的生产者this.notify();//返回消费的球return c;}
}
//生产者
public void run() {for (int i = 0; i < 10; i++) {char c = (char) ((int) (Math.random() * 26) + 'A');box.push(c);System.out.println("生产者" + Thread.currentThread().getName() + "生产了名子为" + c + "的球");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
//消费者
public class Consumer implements Runnable {Box box;public Consumer(Box box) {this.box = box;}@Overridepublic void run() {for (int i = 0; i < 10; i++) {char c = box.pop();System.out.println("消费者" + Thread.currentThread().getName() + "消费了名字为" + c + "的球");try {Thread.sleep((int) (Math.random() * 1000));} catch (InterruptedException e) {e.printStackTrace();}}}
}
//测试
public class Test {public static void main(String[] args) {Box box = new Box();Producer p = new Producer(box);Consumer c = new Consumer(box);Thread p1 = new Thread(p);p1.setName("P1");Thread c1 = new Thread(c);c1.setName("c1");c1.start();p1.start();}
}
生产者消费者示例2
【案例需求:】
生产者消费者案例中包含的类:
奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下
①创建奶箱对象,这是共享数据区域
②创建消费者创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
③对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
④创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
⑤启动线程
public class Box {//定义一个成员变量,表示第x瓶奶
private int milk;
//定义一个成员变量,表示奶箱的状态
private boolean state = false;
//提供存储牛奶和获取牛奶的操作
public synchronized void put(int milk) {//如果有牛奶,等待消费
if(state) {try {wait();
} catch (InterruptedException e) {e.printStackTrace();
}
}
//如果没有牛奶,就生产牛奶
this.milk = milk;
System.out.println("送奶工将第" + this.milk + "瓶奶放入奶箱");
//生产完毕之后,修改奶箱状态
state = true;
//唤醒其他等待的线程
notifyAll();
}
public synchronized void get() {//如果没有牛奶,等待生产
if(!state) {try {wait();
} catch (InterruptedException e) {e.printStackTrace();
}
}
//如果有牛奶,就消费牛奶
System.out.println("用户拿到第" + this.milk + "瓶奶");
//消费完毕之后,修改奶箱状态
state = false;
//唤醒其他等待的线程
notifyAll();
}
}
public class Producer implements Runnable {private Box b;
public Producer(Box b) {this.b = b;
}
@Override
public void run() {for(int i=1; i<=30; i++) {b.put(i);
}
}
}
public class Customer implements Runnable {private Box b;
public Customer(Box b) {this.b = b;
}
@Override
public void run() {while (true) {b.get();
}
}
}
public class BoxDemo {public static void main(String[] args) {//创建奶箱对象,这是共享数据区域
Box b = new Box();
//创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
Producer p = new Producer(b);
//创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
Customer c = new Customer(b);
//创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
//启动线程
t1.start();
t2.start();
}
卖票示例
【某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票】
//卖票
public class SellTicket implements Runnable {private int ticket = 100;@Overridepublic void run() {while (true) {synchronized (this) {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "张票");ticket--;}}//这里加sleep的原因://第一是为了还原现实中卖票的中间空闲时刻//第二是因为如果不设置等待时间,同等优先权的线程中竞争cup执行同一个程序,为了提高cpu的效率就默认不切换线程。几乎不会出现多线程的效果try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class BoxDemo {public static void main(String[] args) {//创建奶箱对象,这是共享数据区域
Box b = new Box();
//创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
Producer p = new Producer(b);
//创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
Customer c = new Customer(b);
//创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
//启动线程
t1.start();
t2.start();
}
}
车间玩具制造示例
【题目描述】
创建工厂类,属性包括:车间类的对象的集合,仓库容量方法包括:1. 加工玩具的方法(product),方法的功能是各个车间同时工作。根据仓库容量平均分配给各个车间需要加工玩具的数量。2. 建造玩具车间的方法(createWorkshop),方法功能是模拟建造玩具生成车间,即向集合属性中添加一个新车间。创建车间类:属性包括:车间名称要求:1. 使用多线程模拟加工玩具的功能,假设每隔2秒可完成一个玩具的加工,每个车间完成一个玩具的加工后,打印当前已加工玩具数量(给每个线程设置名称为车间的名称)2. 创建测试类,模拟工厂加工玩具的功能。提示:车间集合的容量即是线程的数量
//工厂类
public class Factory {private List<Workshop> list = new ArrayList<Workshop>();private int capacity;public Factory() {}public Factory(int capacity) {this.capacity = capacity;}public void product() {int i = 1;int num = capacity / list.size();Workshop.setNum(num);for (Workshop workshop : list) {Thread t = new Thread(workshop);t.setName("车间" + i);i++;t.start();}}public void createWorkshop(String name) {Workshop workshop = new Workshop(name);list.add(workshop);}
}
//车间类
public class Workshop implements Runnable {private static int num;public String name;public Workshop() {}public Workshop(String name) {this.name = name;}public static int getNum() {return num;}public static void setNum(int num) {Workshop.num = num;}public String getName() {return name;}public void setName(String name) {this.name = name;}public void run() {synchronized (Thread.currentThread()) {for (int i = 1; i <= num; i++) {System.out.println(Thread.currentThread().getName() + "生产了第" + i + "个玩具");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}}
}
//测试类
public class Test {public static void main(String[] args) {Factory factory = new Factory(100);for (int i = 0; i < 5; i++) {factory.createWorkshop("车间" + i);}factory.product();}
}
测试方向基础-多线程知识和常见示例相关推荐
- python基础--列表知识和常见操作
列表的格式 变量A的类型为列表 namesList = ['xiaoWang','xiaoZhang','xiaoHua'] 比C语言的数组强大的地方在于列表中的元素可以是不同类型的 testList ...
- 主线程如何等待多线程完成 返回数据_多线程基础体系知识清单
作者:Object 来源:https://juejin.im/user/5d53e1f6f265da03af19cae0/posts 前言 本文会介绍Java中多线程与并发的基础,适合初学者食用. 线 ...
- 零基础转行,入职军工类测试方向,月薪10K | 既然选择了,就要全力以赴
做一个决定,并不难,难的是付诸行动,并且坚持到底. 在实现目标的路途中,我们需要尽可能地排出外在的干扰,用足够的毅力和耐心去坚持,总有一天你会发现:原来我也这么优秀!希望我的经历能给同样在向梦想奔跑的 ...
- 软件测试基础知识 - 测试开发需要哪些知识,需要具备什么能力
分享一个大牛的人工智能教程.零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请点击http://www.captainbed.net 需要的知识 软件测试基础理论知识,如黑盒测试.白盒测 ...
- 多线程基础体系知识清单
前言 本文会介绍Java中多线程与并发的基础,适合初学者食用. 线程与进程的区别 在计算机发展初期,每台计算机是串行地执行任务的,如果碰上需要IO的地方,还需要等待长时间的用户IO,后来经过一段时间有 ...
- 计算机软硬件故障排除知识,计算机软硬件基础知识及常见故障排除方法(精选).doc...
文档介绍: 计算机软硬件基础知识及常见故障排除方法 CPU的主频.外频和倍频3者的关系:主频=外频×倍频 计算机的主板一般有南北桥两片芯片,南桥芯片提供对键盘控制器.实时时钟控制器.USB.高级能源管 ...
- 数据库基础知识和常见术语学习
数据库基础知识和常见术语学习 什么是数据库 数据库系统 什么是数据库系统 数据库系统(DBS)的组成 数据库系统的特点 数据库管理系统(DBMS) 什么是数据库管理系统 数据库管理系统所提供的功能 数 ...
- 计算机基础知识用语,电脑基础知识之常见术语
电脑基础知识之常见术语 No.术语是在特定学科领域用来表示概念的`称谓的集合,在我国又称为名词或科技名词(不同于语法学中的名词).下面,小编为大家分享电脑常见术语,希望对大家有所帮助! No.1 HD ...
- Day74~75_Flink(一)Flink基础核心知识
第一讲 Flink基础核心知识 课程大纲 课程内容 学习效果 掌握目标 Flink概述 Flink概述 了解 Flink开发环境 Flink开发环境 掌握 Flink入门实战 Flink入门实战 掌握 ...
最新文章
- X-Content-Type-Options: nosniff
- js动态生成div和事件
- 如何基于netty实现mq 编程思路
- 超干货 | 一线从业者实践案例大分享:3个tips实现用户增长
- Android实现数据存储技术
- android手机定位
- 华为融合电信云解决方案包括_运营商这“一朵云”怎么建?使能者华为的解决之道...
- VB.NET Visual Basic
- mariadb数据库文件的组成
- QQ正式上线QID功能 用户可自定义专属身份卡
- HTML 标签学习总结第一天
- Java编程风格与命名规范整理(转载)
- Android 显示历史搜索记录
- error:尝试引用已删除的函数或已显式删除函数
- html字两边的横线_css实现中间文字 两边横线(原创)
- php strtotime 2038 时间戳,PHP的strtotime计算2038年以上日期的时间戳错误
- 举个栗子!Tableau 技巧(176):快速添加 Server 受信任的 IP
- 上市首日24小时销量1573辆,这个被马云和雷军同时看上的男人
- 一种基于Harris-Laplace算法的角点检测方法(Matlab代码实现)
- Ubuntu安装NextCloud相关命令
热门文章
- 梳理一个问题(垃圾分类,垃圾回收)
- Splunklive!2018用户大会上海站落幕:探险家精神一览无遗!
- ELK:LogStash写入ES索引建立差8个时区问题
- 野火STM32F103开发板使用串口3接收数据并通过串口2发送
- 2020-10-22.c和.h文件的区别
- java 虚拟机访问对象的两种机制到底有什区别 http://www.cnblogs.com/h2-database/archive/2012/06/27/2572498.html
- 【职场】而立之年,未来5-10年行业预测和职业规划
- 项目测试 | Pycharm+Pyqt5+Qt Designer6+Eric7实现逻辑与界面分离
- 给计算机老师致歉信,学生给老师的道歉信(15篇)
- 列表按钮android,android keycode按键列表