2021-07-12~2021-07-16
工作总结
工作内容
本周的工作内容主要是项目开发前的准备工作,这里不做过多的赘述,最近近期同事的工作内容有一个秒杀抽奖的功能,其中有涉及到多线程,这周来好好过一下多线程的知识点。
技术探索
1.浅谈Java进程与线程
1.1进程
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序,数据集合和进程控制块三部分组成。(程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志。)
进程具有的特征:
- 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的
- 并发性:任何进程都可以同其他进行一起并发执行
- 独立性:进程是系统进行资源分配和调度的一个独立单位
- 结构性:进程由程序,数据和进程控制块三部分组成
1.2 线程
早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。
后来,发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成。
1.2.1 线程状态
- 新建(NEW):新建一个新的线程对象
1.1 实现Runnable接口或者继承Thread类可以得到一个线程类,将该类实例化,现在就进入了初始状态。 - 可运行状态(RUNNABLE): 线程对象创建后,其他线程调用该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu的试用权。
2.1 可运行状态说明你有运行的资格,调用程序没有挑选你,你就永远是可运行状态。
2.2 调用该线程的start()方法,此线程进入可运行状态。
2.3 当线程的·sleep() 方法结束,其他线程的jion()方法结束,等待用户操作结束,某个线程拿到对象锁,这些线程也可以进入可运行状态。
2.4 当前线程的时间片用完,调用该线程的yield()方法,当前线程则进入可运行状态。
2.5 锁池中获取的对象锁,则进入可运行状态。 - 运行(RUNNING):可运行状态的线程获取了时间片,代码开始执行。
3.1 线程程序从可运行池中选择一个线程做为当前线程所处的状态。这也是线程进入运行状态的唯一方式。 - 阻塞(BLOCKED):阻塞是指线程因为某种原因放弃了cpu的使用权,暂停停止运行。
(等待阻塞:在执行的线程执行了wait()的方法,JVM将线程方法等待队列(waitting queue)中。
同步阻塞:在执行的线程在获取对象同步锁的时候,若该同步锁被其他线程所占用,则JVM会把该线程放到,则JVM会把该线程放入锁池(lock pool)中。
其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
)
4.1 当前执行的线程调用sleep()方法,当前线程则进入阻塞状态。
4.2 运行在当前线程中的其他线程调用了join()方法,当前线程则进入阻塞状态。
4.3 等待用户操作的时候,当前线程进入阻塞状态。 - 死亡:线程run(),main()方法执行结束,或者因为异常退出了所执行的方法,则该线程结束生命周期,线程死亡则无法复活。
5.1 当一个线程run()方法或者main()执行完毕后,我们会认为该线程结束了生命周期。
5.2 一个结束生命周期,也就是死亡的线程调用start()方法,会抛出IllegalThreadStateException异常。
线程状态图
1.3 进程与线程的区别
- 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位
- 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
- 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
- 线程上下文切换比进程上下文切换要快得多
1.4 结构图
2.Java多线程
指的是这个程序(一个进程)运行时产生了不止一个线程
2.1 并发与并行
- 并发
多个cpu或者多台机器同事执行一断处理逻辑,真正的同时进程 - 并发
通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
2.2 代码校验
2.2.1
实现Runnable接口实现线程
代码1:
package multithread.runnable;
public class RunnableThread implements Runnable {
private String name;
public RunnableThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 1; i < 11; i++) {
System.out.println(name + "执行" + i + "次");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class RunnableDemo1 {
public static void main(String args[]) {
RunnableThread rt1=new RunnableThread("线程1");
RunnableThread rt2=new RunnableThread("线程2");
Thread thread1 = new Thread(rt1);
Thread thread2 = new Thread(rt2);
thread1.start();
thread2.start();
}
}
}
结果1:
线程1执行1次
线程2执行1次
线程1执行2次
线程2执行2次
线程1执行3次
线程2执行3次
线程1执行4次
线程2执行4次
线程1执行5次
线程2执行5次
线程1执行6次
线程2执行6次
线程1执行7次
线程2执行7次
线程1执行8次
线程2执行8次
线程1执行9次
线程2执行9次
线程1执行10次
线程2执行10次
2.2.2
继承Thread类实现线程
代码2:
public class ExtThread extends Thread {
private String name;
public ExtThread(String name){
this.name=name;
}
@Override
public void run() {
for (int i=1;i<11;i++){
System.out.println(name+"执行"+i+"次");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class RunnableDemo2 {
public static void main(String args[]) {
ExtThread thread1 = new ExtThread("线程1");
ExtThread thread2 = new ExtThread("线程2");
thread1.start();
thread2.start();
}
}
}
结果2
线程2执行1次
线程1执行1次
线程2执行2次
线程1执行2次
线程1执行3次
线程2执行3次
线程2执行4次
线程1执行4次
线程2执行5次
线程1执行5次
线程2执行6次
线程1执行6次
线程1执行7次
线程2执行7次
线程1执行8次
线程2执行8次
线程2执行9次
线程1执行9次
线程2执行10次
线程1执行10次
2.2.3
线程抢占
代码3
public class RunnableThread implements Runnable {
private String name;
public RunnableThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 1; i < 6; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行第" + i + "次");
}
}
public static class RunnableDemo1 {
public static void main(String args[]) {
RunnableThread rt1=new RunnableThread("线程1");
Thread thread1 = new Thread(rt1,"线程1");
thread1.start();
for (int i=1;i<11;i++){
if (i>5){
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("抢占"+i);
}
}
}
}
结果3
抢占1
抢占2
抢占3
抢占4
抢占5
线程1执行第1次
线程1执行第2次
线程1执行第3次
线程1执行第4次
线程1执行第5次
抢占6
抢占7
抢占8
抢占9
抢占10
解析3:
在线程操作中,可以使用 join() 方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。在主函数的循环中,在小于6的时候被线程给抢占。
2.2.4
线程礼让
代码4:
public class RunnableThread implements Runnable {
private String name;
public RunnableThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 1; i < 6; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行第" + i + "次");
if (i==2&&"线程1".equals(Thread.currentThread().getName())){
System.out.println(Thread.currentThread().getName()+"线程礼让");
Thread.currentThread().yield();
}
}
}
public static class RunnableDemo1 {
public static void main(String args[]) {
RunnableThread rt1=new RunnableThread("线程1");
RunnableThread rt2=new RunnableThread("线程2");
Thread thread1 = new Thread(rt1,"线程1");
Thread thread2 = new Thread(rt2,"线程2");
thread1.start();
thread2.start();
}
}
}
结果4
线程1执行第1次
线程2执行第1次
线程1执行第2次
线程2执行第2次
线程2执行第3次
线程1线程礼让
线程2执行第4次
线程1执行第3次
线程2执行第5次
线程1执行第4次
线程1执行第5次
解析4
线程1在执行完第二次循环的时候进行线程礼让,让出了cpu,让线程2执行。
2.2.5
同步
通过同步代码块,保证数据同步的按全性。
代码5
public class RunnableThread implements Runnable {
private Integer ticket=5;
@Override
public void run() {
for (int i = 1; i < 100; i++) {
synchronized (this){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"购买了票"+ticket--);
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class RunnableDemo1 {
public static void main(String args[]) {
RunnableThread rt1=new RunnableThread();
Thread thread1 = new Thread(rt1,"用户1");
Thread thread2 = new Thread(rt1,"用户2");
Thread thread3 = new Thread(rt1,"用户3");
thread1.start();
thread2.start();
thread3.start();
}
}
}
结果5
用户2购买了票5
用户3购买了票4
用户1购买了票3
用户1购买了票2
用户3购买了票1
2.2.6
死锁
同步可以保证资源共享操作的正确性,但是过多同步也会产生问题.所谓死锁,就是两个线程都在等待对方先完成,造成程序的停滞,一般程序的死锁都是在程序运行时出现的。
public class Man {
public void say(){
System.out.println("我要美女");
}
public void get(){
System.out.println("我得到了美女");
}
}
public class Woman {
public void say(){
System.out.println("我要帅哥");
}
public void get(){
System.out.println("我得到了帅哥");
}
}
public class RunnableThread implements Runnable {
private static Man man = new Man();
private static Woman woman = new Woman();
private boolean falg=false;
@Override
public void run() {
if (falg){
synchronized (man){
man.say();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (woman){
man.get();
}
}
}else {
synchronized (woman){
woman.say();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (man){
woman.get();
}
}
}
}
public static class RunnableDemo1 {
public static void main(String args[]) {
RunnableThread rt1=new RunnableThread();
RunnableThread rt2=new RunnableThread();
rt1.falg=true;
rt2.falg=false;
Thread thread1 = new Thread(rt1);
Thread thread2 = new Thread(rt2);
thread1.start();
thread2.start();
}
}
}
结果6
我要美女
我要帅哥
2.2.7
多线程经典问题,生产者与消费者问题
代码7
public class Product {
private Integer product=0;
private static final Integer MAX_NUM=5;
public synchronized void produce(){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (this.product>=MAX_NUM){
try {
System.out.println(Thread.currentThread().getName()+"生产数量已满,生产者休息");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
product++;
System.out.println(Thread.currentThread().getName()+"生产者生产了第"+product+"个产品");
notifyAll();
}
}
public synchronized void consume(){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (this.product==0){
try {
System.out.println(Thread.currentThread().getName()+"无商品,请稍等");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+"消费者消费了第"+product--+"个商品");
notifyAll();
}
}
}
public class CThread implements Runnable {
Product p;
public CThread(Product p){
this.p=p;
}
@Override
public void run(){
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.consume();
}
}
}
public class PThread implements Runnable{
Product p;
public PThread(Product p){
this.p=p;
}
@Override
public void run(){
while (true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.produce();
}
}
}
public class RunnableThread implements Runnable {
public static class RunnableDemo1 {
public static void main(String args[]) {
Product product = new Product();
CThread cThread = new CThread(product);
CThread cThread1 = new CThread(product);
PThread pThread = new PThread(product);
PThread pThread1 = new PThread(product);
Thread cc = new Thread(cThread, "消费者1");
Thread cc1 = new Thread(cThread1, "消费者2");
Thread pp = new Thread(pThread, "生产者1");
Thread pp1 = new Thread(pThread1, "生产者2");
pp.start();
pp1.start();
cc.start();
cc1.start();
}
}
}
结果7
生产者2生产者生产了第1个产品
消费者1消费者消费了第1个商品
消费者2无商品,请稍等
生产者1生产者生产了第1个产品
生产者2生产者生产了第2个产品
生产者1生产者生产了第3个产品
消费者1消费者消费了第3个商品
生产者2生产者生产了第3个产品
消费者2消费者消费了第3个商品
生产者1生产者生产了第3个产品
生产者2生产者生产了第4个产品
消费者1消费者消费了第4个商品
生产者1生产者生产了第4个产品
生产者2生产者生产了第5个产品
消费者2消费者消费了第5个商品
生产者1生产者生产了第5个产品
生产者2生产数量已满,生产者休息
........
以上有关异常的内容是阅读了,该文章的学习总结。
总结
1、本周对Java的进程与线程进入深入的学习,但是感觉还是存在着很多的不足之处。
2、通过对多线程案例的编码,使得自己对于多线程有了更加深入的理解,纸上谈兵终觉浅,还是需要通过代码的磨练才能够深入地理解相关知识。
把握当下,展望未来--zwx