系列文章目录

Java核心技术卷一 -第一章:java“白皮书”的关键术语
Java核心技术卷一 -第三章:数据类型
Java核心技术卷一 -第三章:变量与常量
Java核心技术卷一 -第三章:运算符
Java核心技术卷一 -第三章:字符串
Java核心技术卷一 -第三章:输入与输出
Java核心技术卷一 -第三章:数组
Java核心技术卷一 -第四章:类之间的关系-依赖
Java核心技术卷一 -第四章:预定义类-LocalDate类小应用
Java核心技术卷一 -第四章:构造器
Java核心技术卷一 -第四章:null引用
Java核心技术卷一 -第四章:方法参数
Java核心技术卷一 -第四章:对象构造
Java核心技术卷一 -第五章:覆盖方法与super
Java核心技术卷一 -第五章:super关键字
Java核心技术卷一 -第五章:类的强制类型转换与instanceof操作符
Java核心技术卷一 -第五章:抽象类与abstract关键字
Java核心技术卷一 -第五章:Object类的toString方法
Java核心技术卷一 -第五章:数组列表初识
Java核心技术卷一 -第五章:装箱和拆箱
Java核心技术卷一 -第五章:枚举类再认识
Java核心技术卷一 -第七章:异常
Java核心技术卷一 -第九章:集合
Java核心技术卷一 -第六章:抽象类和接口
Java核心技术卷一 -第十章:IO流

文章目录

  • 系列文章目录
  • 前言
  • 一、多线程
    • 1.1、什么是进程?什么是线程?
    • 1.2、基本线程数量
    • 1.3、进程和线程是什么关系?
      • 举个例子:
      • 注意:
    • 1.4、思考一个问题:
    • 1.5、分析一个问题:对于单核的CPU来说,真的可以做到真正的多线程并发吗?
    • 1.6、java语言中,实现线程有两种方式,那两种方式呢?
      • 第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。
      • 代码展示:
      • 第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法。
      • 代码展示:
    • 1.7、线程的生命周期
    • 1.8、线程的方法
      • 代码展示:
      • 代码展示(在java中怎么强行终止一个线程的执行):
        • 直接终止:
        • 间接终止:这种方法安全
    • 1.9、线程安全
      • 为什么这个是重点?
      • 什么时候数据在多线程并发的环境下会存在安全问题呢?
      • 怎么解决线程安全问题呢?
      • 说到线程同步这块,涉及到这两个专业术语:
        • 异步编程模型:
        • 同步编程模型:
    • 1.10、模拟俩个线程对同一账号取款
      • Account类:
      • AccountThread类:
      • Test类:
      • 代码执行原理(锁池):
      • 提示:
      • 小总结:
    • 1.11、死锁
      • 图示:T1从上往下,T2从下往上,T1锁住第一个后再锁第二个;T2锁住第二个后再锁第一个,此时双方都锁住了要锁的第一个,而要锁的第二个被对方锁住,从而造成死锁。
      • 代码展示:
      • 我们以后开发中应该怎么解决线程安全问题?
    • 1.12、守护线程:
      • 图示:
      • 代码展示:
    • 1.13、定时器:
      • 了解:
      • 定时器的作用:
      • 代码展示:
    • 1.14、关于Object类中的wait和notify方法。(生产者和消费者模式!)
      • 图示:
      • (生产者和消费者模式!):
      • 代码展示:
  • 总结

前言

本人为java初学者,文章内容仅为个人学习总结分享,其中包含了大量Java核心技术卷一里面的文章内容以及一些网站文章内容,由于参考文章过多,在此并不一一列出,如有侵权,请联系删除。

一、多线程

1.1、什么是进程?什么是线程?

进程是一个应用程序(1个进程是一个软件)
线程是一个进程中的执行场景/执行单元。
一个进程可以启动多个线程。

1.2、基本线程数量

对于java程序来说,当在DOS命令窗口中输入:java HelloWorld回车之后。会先启动JVM,而JVM就是一个进程。
JVM再启动一个主线程调用main方法。
同时再启动一个垃圾回收线程负责看护,回收垃圾。
最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。

1.3、进程和线程是什么关系?

举个例子:

阿里巴巴:进程
马云:阿里巴巴的一个线程
童文红:阿里巴巴的一个线程

京东:进程
强东:京东的一个线程
妹妹:京东的一个线程

进程可以看做是现实生活当中的公司,
线程可以看做是公司当中的某个员工。

注意:

进程A和进程B的内存独立不共享。(阿里巴巴和京东资源不会共享的!)线程A和线程B呢?
在java语言中:

线程A和线程B,堆内存和方法区内存共享。
但是栈内存独立,一个线程一个栈。

假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

火车站,可以看做是一个进程:
火车站中的每一个售票窗口可以看做是一个线程。
我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。
所以多线程并发可以提高效率。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。

1.4、思考一个问题:

使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。
main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。

1.5、分析一个问题:对于单核的CPU来说,真的可以做到真正的多线程并发吗?

对于多核的CPU电脑来说,真正的多线程并发是没问题的。
4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。

什么是真正的多线程并发?
t1线程执行t1的。
t2线程执行t2的。
t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。

单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。
对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于
CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是:多个事情
同时在做!!!!!
线程A:播放音乐
线程B:运行魔兽游戏
线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,
给我们的感觉是同时并发的。

电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度之后,
人类的眼睛产生了错觉,感觉是动画的。这说明人类的反应速度很慢,就像
一根钢针扎到手上,到最终感觉到疼,这个过程是需要“很长的”时间的,在
这个期间计算机可以进行亿万次的循环。所以计算机的执行速度很快。

1.6、java语言中,实现线程有两种方式,那两种方式呢?

java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。

第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。

  // 定义线程类public class MyThread extends Thread{public void run(){}}// 创建线程对象MyThread t = new MyThread();// 启动线程。t.start();

代码展示:

public class Test02 {public static void main(String[] args) {AZX azx=new AZX();//启动线程//start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。//这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动成功了。//启动成功的线程会自动调用run方法,并且run方在分支的栈底部(压栈)。//run方法在分支的底部,main方法在主栈的栈底部。run和main是平级的。azx.start();for (int i=0;i<1000;i++){System.out.println("主线程------>"+i);}}
}class AZX extends Thread{@Overridepublic void run() {for (int i=0;i<1000;i++){System.out.println("分支线程------>"+i);}}
}

第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法。

  // 定义一个可运行的类public class MyRunnable implements Runnable {public void run(){}}// 创建线程对象Thread t = new Thread(new MyRunnable());// 启动线程t.start();

代码展示:

public class Test02 {public static void main(String[] args) {//创建一个可运行的对象MyRunnable r=new MyRunnable();//将可运行的对象封装成一个线程对象Thread t=new Thread(r);//启动线程t.start();for (int i=0;i<100;i++){System.out.println("主线程------>"+i);}}
}class MyRunnable implements Runnable{@Overridepublic void run() {for (int i=0;i<100;i++){System.out.println("分支线程------>"+i);}}
}

注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承
其它的类,更灵活。

1.7、线程的生命周期

1.8、线程的方法

代码展示:

public class Test02 {public static void main(String[] args) {//让当前线程进行休眠,5秒后唤醒try {//注意:sleep方法是静态方法,无论是谁调用,最后它都只会让当前线程睡眠,即便是用其它线程的名字调用也没用Thread.sleep(1000*5);} catch (InterruptedException e) {e.printStackTrace();}//5秒后执行这里的代码System.out.println("HelloWorld!!!");//获取当前对象线程的名字Thread currentThread =Thread.currentThread();System.out.println(currentThread.getName());//创建一个线程对象AZX r=new AZX();//设置线程的名字r.setName("ttttt");//获取线程的名字String tName= r.getName();System.out.println(tName);//启动线程r.start();for (int i=0;i<10;i++){System.out.println("主线程------>"+i);}}
}class AZX extends Thread{@Overridepublic void run() {for (int i=0;i<10;i++){//currentThread就是当前线程对象。当前线程是谁呢?//当t1我程执行run方法,那么这个当前线程就是t1//当t2线程执行run方法,那么这个当前线程就是t2Thread currentThread =Thread.currentThread();System.out.println(currentThread.getName()+":分支线程------>"+i);}}
}

代码展示(在java中怎么强行终止一个线程的执行):

直接终止:

这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失。

public class Test02 {public static void main(String[] args) {Thread t=new Thread(new AZX());t.setName("t");t.start();//模拟5秒try {//注意:sleep方法是静态方法,无论是谁调用,最后它都只会让当前线程睡眠,即便是用其它线程的名字调用也没用Thread.sleep(1000*5);} catch (InterruptedException e) {e.printStackTrace();}//5秒后强行终止t线程t.stop();//已过时,不建议使用}
}class AZX extends Thread{@Overridepublic void run() {for (int i=0;i<10;i++){//执行十秒Thread currentThread =Thread.currentThread();System.out.println(currentThread.getName()+":分支线程------>"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

结果:

间接终止:这种方法安全

public class Test02 {public static void main(String[] args) {MyRunnable r=new MyRunnable();Thread t=new Thread(r);t.setName("t");t.start();//模拟5秒try {//注意:sleep方法是静态方法,无论是谁调用,最后它都只会让当前线程睡眠,即便是用其它线程的名字调用也没用Thread.sleep(1000*5);} catch (InterruptedException e) {e.printStackTrace();}//5秒后强行终止t线程r.run=false;}
}class MyRunnable implements Runnable{//打一个布尔标记boolean run=true;@Overridepublic void run() {for (int i=0;i<10;i++){if(run){//执行十秒Thread currentThread =Thread.currentThread();System.out.println(currentThread.getName()+":分支线程------>"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}else {//终止当前线程return;}}}
}

结果:

1.9、线程安全

为什么这个是重点?

以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。

最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点:*****)

什么时候数据在多线程并发的环境下会存在安全问题呢?

三个条件:
条件1:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为。

满足以上3个条件之后,就会存在线程安全问题。

怎么解决线程安全问题呢?

当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?

线程排队执行。(不能并发)。

用排队执行解决线程安全问题。这种机制被称为:线程同步机制

专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。

线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。

说到线程同步这块,涉及到这两个专业术语:

异步编程模型:

线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。
其实就是:多线程并发(效率较高。)

异步就是并发。

同步编程模型:

线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。

效率较低:线程排队执行

同步就是排队。

1.10、模拟俩个线程对同一账号取款

Account类:

public class Account {//账户private String actno;//余额private double balance;//构造方法public Account(){}public Account(String actno, double balance) {this.actno = actno;this.balance = balance;}//set与get方法public String getActno() {return actno;}public void setActno(String actno) {this.actno = actno;}public double getBalance() {return balance;}public void setBalance(double balance) {this.balance = balance;}//取款的方法public void withdraw(double money){synchronized (this){//取款之前的余额double before=this.getBalance();//取款之后的余额double after=before-money;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//更新余额this.setBalance(after);}}
}

AccountThread类:

public class AccountThread extends Thread{//俩个线程共享同一个账户对象private Account act;//构造方法public AccountThread(Account act) {this.act = act;}public void run(){//run方法的执行表示取款操作double money=5000;//取款act.withdraw(money);System.out.println(Thread.currentThread().getName()+"对"+act.getActno()+"取款成功,余额:"+act.getBalance());}
}

Test类:

public class AccountTest01 {public static void main(String[] args) {//创建账户对象(只创建1个)Account act=new Account("act-001",10000);//创建俩个线程Thread t1=new AccountThread(act);Thread t2=new AccountThread(act);//设置namet1.setName("t1");t2.setName("t2");//启动线程取款t1.start();t2.start();}
}

结果:

代码执行原理(锁池):


提示:

这里需要注章的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的。

小总结:

1.11、死锁

图示:T1从上往下,T2从下往上,T1锁住第一个后再锁第二个;T2锁住第二个后再锁第一个,此时双方都锁住了要锁的第一个,而要锁的第二个被对方锁住,从而造成死锁。

代码展示:

public class DeadLock {public static void main(String[] args) {Object o1=new Object();Object o2=new Object();//t1和t2俩个线程共享o1、o2Thread t1=new MyThread1(o1,o2);Thread t2=new MyThread22(o1,o2);t1.start();t2.start();}
}class MyThread1 extends Thread{Object o1;Object o2;public MyThread1(Object o1, Object o2) {this.o1 = o1;this.o2 = o2;}public void run(){synchronized (o1){try {System.out.println("MyThead1-begin");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (o2){System.out.println("MyThead1-end");}}}
}class MyThread22 extends Thread{Object o1;Object o2;public MyThread22(Object o1, Object o2) {this.o1 = o1;this.o2 = o2;}public void run(){synchronized (o2){try {System.out.println("MyThead22-begin");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (o1){System.out.println("MyThead22-end");}}}
}

结果:卡住了

我们以后开发中应该怎么解决线程安全问题?

是一上来就选择线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。

第一种方案:尽量使用局部变量代替“实例变量和静态变量”。

第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)

第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

1.12、守护线程:

图示:

代码展示:

package Package03;
public class ThreadTest04 {public static void main(String[] args) {Thread t=new BakDataThread();//启动线程之前,将线程设置为守护线程t.setDaemon(true);//启动线程t.start();//主线程for (int i=0;i<10;i++){System.out.println(Thread.currentThread().getName()+"--->"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
class BakDataThread extends Thread{public void run(){int i=0;while (true){System.out.println(Thread.currentThread().getName()+"--->"+(++i));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

结果:主线程即main线程结束,守护线程也结束(其死循环结束)

1.13、定时器:

了解:

定时器:Timer类

注意schedule类:

定时器的作用:

间隔特定的时间,执行特定的程序。

列如:每周要进行银行账户的总账操作、每天要进行数据的备份操作。

在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:

可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)

在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。

在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

代码展示:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;public class TimerTest {public static void main(String[] args) throws Exception {//创建定时器对象Timer timer=new Timer();//指定定时任务SimpleDateFormat sdf=new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");Date firstTime=sdf.parse("2022-02-14 11:15:00");timer.schedule(new LogTimerTask(), firstTime, 1000*10);}
}//编写一个定时任务类
class LogTimerTask extends TimerTask{public void run(){SimpleDateFormat sdf=new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");String strTime=sdf.format(new Date());System.out.println(strTime+":成功完成了一次数据备份!");}
}

结果:

1.14、关于Object类中的wait和notify方法。(生产者和消费者模式!)

图示:

(生产者和消费者模式!):



代码展示:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;public class ThreadTest01 {public static void main(String[] args) throws Exception{//创建1个仓库对象,共享的List list=new ArrayList();//创建俩个线程对象//生产者线程Thread t1=new Thread(new Producer(list));//消费者线程Thread t2=new Thread(new Consumer(list));t1.setName("生产者线程");t2.setName("消费者线程");//启动线程t1.start();t2.start();}
}//生产线程
class Producer implements Runnable{//仓库private List list;//数组大小private int size=0;//构造方法public Producer(List list) {this.list = list;}@Overridepublic void run() {//一直生产(使用死循环来模拟一直生产)while (true){//给仓库对象List加锁synchronized (list){if (list.size()>0){try {list.wait();} catch (InterruptedException e) {e.printStackTrace();}}//程序能够执行到这里说明仓库是空的,可以生产Object obj=new Object();//获取数组大小for(int i = 0 ; i < list.size() ; i++) {size=i+1;}obj=size;list.add(obj);System.out.println(Thread.currentThread().getName()+"--->"+obj);//唤醒消费者进行消费list.notify();}}}
}//消费线程
class Consumer implements Runnable{//仓库private List list;//数组大小private int size=0;//构造方法public Consumer(List list) {this.list = list;}@Overridepublic void run() {//一直消费while (true){//给仓库对象List加锁synchronized (list){if (list.size()==0){try {//仓库已经空了//消费者线程等待list.wait();} catch (InterruptedException e) {e.printStackTrace();}}//程序能够执行到这里说明仓库是有数的,可以消费//获取数组大小for(int i = 0 ; i < list.size() ; i++) {size=i;}Object obj=list.remove(size);System.out.println(Thread.currentThread().getName()+"--->"+obj);//唤醒生产者进行消费list.notify();}}}
}

结果:(生产和消费交替运行)

总结

以上就是本文的内容,记录了一些关于java“多线程”的内容,本人也是刚开始接触java,不能保证总结内容的正确性,若是有错误的话,欢迎大家指出,谢谢!

Java核心技术卷一 -第十二章:多线程相关推荐

  1. 【JAVA SE】第十二章 流(Stream)、IO流和文件(File)

    第十二章 流(Stream).IO和文件(File) 文章目录 第十二章 流(Stream).IO和文件(File) 一.流(Stream) 1.什么是流 2.流的分类 二.IO流 1.字节输入流(I ...

  2. 第十二章 多线程基础

    文章目录 12.1 进程与线程 12.2 创建线程 12.2.1 继承Thread类 12.2.2 实现Runnable接口 12.2.3 区别和联系 12.3 终止线程 12.4 线程常用方法 12 ...

  3. 《Java核心技术卷一》第3章— — —个人知识点整理

    1 关键字public称为访问修饰符,它用于控制程序的其他部分对这段代码的访问级别 2 关键字class表明Java程序中的全部部分都包含在类中,将类作为一个加载程序逻辑的容器,程序逻辑定义了应用程序 ...

  4. Java课后题第十二章:12.18(添加包语句)

    1.找到文件路径 File file = new File("D:\\PackageP13\\");File[] files = file.listFiles(); 2.复制到St ...

  5. Java核心技术卷一读书笔记

    文章目录 Java核心技术卷一读书笔记 第一章 Java程序设计概述 1.1 关键特性 第二章 Java程序设计环境 2.1 使用命令行工具 第三章 Java的基本查询设计结构 3.1 数据类型 3. ...

  6. 《Kotlin 程序设计》第十二章 Kotlin的多线程:协程(Coroutines)

    第十二章 Kotlin的多线程:协程(Coroutines) Kotlin 1.1 introduced coroutines, a new way of writing asynchronous, ...

  7. Java核心技术卷一 -第九章:集合

    系列文章目录 Java核心技术卷一 -第一章:java"白皮书"的关键术语 Java核心技术卷一 -第三章:数据类型 Java核心技术卷一 -第三章:变量与常量 Java核心技术卷 ...

  8. Java核心技术卷一 -第四章:方法参数

    系列文章目录 Java核心技术卷一 -第一章:java"白皮书"的关键术语 Java核心技术卷一 -第三章:数据类型 Java核心技术卷一 -第三章:变量与常量 Java核心技术卷 ...

  9. Java核心技术卷一、二读书笔记(PDF)分享

    分享一下笔记(书)PDF在下面,懂得都懂 一 Java程序设计概述 1)Java语言的特性 简单性 Java语法是c++的一个纯净版本,这里没有头文件,指针运算(指针语法),结构,联合,操作符重载,虚 ...

最新文章

  1. java+实现集合并运算_JAVA程序设计报告+集合运算
  2. 跟着实例学习设计模式(7)-原型模式prototype(创建型)
  3. Android Svn 中 Bin ,Gen 目录不进行版本控制
  4. 汇编学习笔记(4)-伪指令(MASM)
  5. 【渝粤教育】广东开放大学 会展项目管理 形成性考核 (59)
  6. “Device eth0 does not seem to be present”解决办法
  7. Swagger2 生成API文档时泛型总是显示不出来的问题
  8. 解决2010版办公软件打不开从系统中导出的Excel表格
  9. 完美解决Python与anaconda之间的冲突问题
  10. zynq以太网官网例子调试
  11. 计算机快速看图教程,cad快速看图制图
  12. Ravpower苹果20W充电器,充电快又稳,使用更安全
  13. VMware系统启动假死,一直处于“繁忙”状态
  14. c++ 一个学习小组有5个人,每个人有三门课(高数、英语和C语言)的考试成绩,求每人的平均成绩。按行输出每个学生的各科成绩及平均成绩。
  15. JQ...CSS3爱心飘落特效
  16. 实用经验 92 区分函数模版与模版函数,类模版和模板类
  17. 串行进位加法器和超前进位加法器代码实现及性能对比
  18. 恢复计算机到手机桌面,桌面的图标怎么恢复原来的呀?
  19. NUCLEO-F401RE
  20. 对抗抑郁症带来的悲伤,治愈系AI心理医生会有用吗?

热门文章

  1. 前沿资讯:索尼展现裸眼3D显示技术,阿里推出图计算平台……
  2. 极其简单的响应式的模块化布局、看板布局 js 工具
  3. Thread优先级之优先级别
  4. 如何系统自学软件测试,看这篇软件测试学习方法万字总结就够了
  5. nginx - nginx的安装部署
  6. 异常错误cannot be cast to java.lang.Comparable解决办法
  7. 毕业设计论文选题系统系统用例图_毕业设计管理系统UML
  8. 如何查看电脑CPU的核心个数
  9. 中国程序员最容易读错的70个英文单词,非常值得一看!
  10. 中台战略全解读(三):业务中台建设