1.进程

1.进程是正在运行的程序,也就是执行程序的一次执行过程,是系统进行资源分配的基本单位
2.目前操作系统都是支持多进程,可以进行执行多个进程,通过进程ID区分
3.单核cpu在同一时刻,只能有一个进程,宏观并行,微观串行。

2. 线程

线程,又称轻量级进程,进程中的一条执行路径,也是cpu的基本调度单位。一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程。
例如:迅雷是一个进程,当中的多个下载任务即为线程。
Java虚拟机是一个继承,当中默认包含主线程(main),可通过代码创建多个独立线程,与main并发执行

3.进程和线程的区别

1.进程是操作系统资源分配的基本单位,而线程是cpu的基本调度单位。
2.一个程序运行后至少有一个进程。
3.一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义。
4.进程间不能共享数据段地址,但是同进程的线程之间可以。

4.线程的组成

任何一个线程都具有基本的组成部分

      CPU时间片: 操作系统(OS)会为每个线程分配执行时间  运行数据:堆空间: 存储线程需要的对象,多个线程可以共享堆中的数据。栈空间:  存储线程需使用的局部变量,每个线程都拥有独立的栈。线程的逻辑代码.

5.线程的特点

1. 线程抢占式执行效率高 可防止单一线程长时间独占CPU.
2. 在单核CPU中,宏观上同时执行,微观上顺序执行

6.线程的创建方式(三种)

6.1 第一种方式: 继承Thread类 重写run方法

    //1.继承线程
public class MyThread extends Thread{//2。重写run方法@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+"==="+i);}}
}public class Test {public static void main(String[] args) {//3.创建线程MyThread myThread = new MyThread();myThread.setName("aaa");//4.开启线程myThread.start();}
}

相关方法:

一、获取线程ID和线程名称1.在Thread的子类中调用this.getId()或this.getName()2.使用Thread.currentThread().getId()和Thread.currentThread().getName()
二、修改线程名称1. 调用线程对象的setName()方法2. 使用线程子类的构造方法赋值

举例:使用线程Thread类实现4个窗口各卖100张票

public class MyThread extends Thread{int ticket=100;@Overridepublic void run() {for(;ticket>0;ticket--){System.out.println(Thread.currentThread().getName()+"剩余:"+ticket+"张票");}}
}
public class Test {public static void main(String[] args) {MyThread m1 = new MyThread();m1.setName("A窗口");m1.start();MyThread m2 = new MyThread();m2.setName("B窗口");m2.start();MyThread m3 = new MyThread();m3.setName("C窗口");m3.start();MyThread m4 = new MyThread();m4.setName("D窗口");m4.start();}
}

若想让四个窗口共卖这100张票可在ticket前加 static 即static int ticket=100;

6.2 第二种方式: 实现Runnable接口


实例:实现四个窗口共卖100张票

public class MyRunnable implements Runnable{int ticket=100;@Overridepublic void run() {for(;ticket>0;ticket--){System.out.println(Thread.currentThread().getName()+"剩余:"+ticket+"张票");}}
}
public class TestDemo {public static void main(String[] args) {//可以将MyRunnable理解为一个任务由四个窗口执行,则共享票数 从而实现四个窗口//共卖100张票MyRunnable r1 = new MyRunnable();Thread t1 = new Thread(r1,"A窗口");Thread t2 = new Thread(r1,"B窗口");Thread t3 = new Thread(r1,"C窗口");Thread t4 = new Thread(r1,"D窗口");t1.start();t2.start();t3.start();t4.start();}
}

6.3 第二种方式: 实现Callable接口

实现Callable接口,它和实现Runnable接口差不多,只是该接口种的方法有返回值和异常抛出。

public class Test {public static void main(String[] args) throws Exception {My1 task1=new My1();My2 task2=new My2();//第一种方式建创建线程对象并提交Callable类型的任务//但是这种方式是比较麻烦的,需要封装到FutureTask类种,//因此建议使用线程池来提交任务FutureTask futureTask=new FutureTask(task1);Thread t1=new Thread(futureTask);t1.start();System.out.println(futureTask.get());//第二种方式 使用线程池来提交任务     应用场景: 适合大文件上传。ExecutorService executorService = Executors.newFixedThreadPool(5);Future<Integer> future = executorService.submit(task1);Integer sum1 = future.get();//需要等线程执行完毕后,才会把结果返回给该变量//实现1-100的和Future<Integer> future1 = executorService.submit(task2);Integer sum2 = future1.get();System.out.println(sum1+sum2);}
}
class My1 implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int sum=0;for (int i=1;i<=50;i++){sum+=i;}return sum;}
}class My2 implements Callable<Integer>{@Overridepublic Integer call() throws Exception {int sum=0;for (int i=51;i<=100;i++){sum+=i;}return sum;}
}

7.线程的生命周期以及状态转换


       1.新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new Thread();

2.就绪状态(Runnable):当调用线程对象的start()方法,线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

3.运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

4.阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。

根据阻塞产生的原因不同,阻塞状态又可以分为三种:1.等待阻塞--运行状态中的线程执行 wait() 方法,使本线程进入到等待阻塞状态;
2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 -- 通过调用线程的 sleep() 或 join() 或发出了I/O请求时,线程会进入到阻塞状态。
当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5.死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期;

8.线程的常用方法

  1. 休眠:

     public static void sleep(long millis)当前线程主动休眠millis毫秒。
    
public class ThreadSleep {public static void main(String[] args) {ThreadSleepDemo t = new ThreadSleepDemo();t.start();for(int i=1;i<10;i++){System.out.println("main线程====循环"+i+"次");}}
}class ThreadSleepDemo extends Thread{@Overridepublic void run() {for(int i=1;i<10;i++){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"线程===循环"+i+"次");}}
}
  1. 放弃:

     public static void yield()当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片 yield()只让有相同执行权的线程获得cup时间片,但是yield()不能控制cup交出的时间,yeild()只是让线程恢复到就绪状态,那么可能在执行yeild()后进入就绪状态,然后马上又进入运行状态。
    
public class ThreadYield {public static void main(String[] args) {TY t1 = new TY();t1.start();for(int i=1;i<10;i++){Thread.yield();System.out.println("main线程===循环"+i+"次");}}
}
class TY extends Thread{@Overridepublic void run() {for(int i=1;i<10;i++){Thread.yield();System.out.println(Thread.currentThread().getName()+"线程===循环"+i+"次");}}
}

3.加入:

      public final void join()允许其他线程加入到当前线程中在main函数线程中调用线程tj.join()方法,此时main函数线程就进入阻塞状态,直到线程tj完全执行完以后,线程main才结束阻塞状态
public class ThreadJoin {public static void main(String[] args) {TJ tj = new TJ();tj.start();try {//tj执行完后才会执行main函数tj.join();} catch (InterruptedException e) {e.printStackTrace();}for(int i=1;i<10;i++){System.out.println("main线程===循环"+i+"次");}}
}
class TJ extends Thread{@Overridepublic void run() {for(int i=1;i<10;i++){System.out.println(Thread.currentThread().getName()+"线程===循环"+i+"次");}}
}

4.优先级:

  线程对象.setPriority()线程优先级1-10,默认为5,优先级越高,表示获取CPU的概率越高

5.守护线程:

     线程对象.setDaemon(true);设置为守护线程。线程有两类:用户线程(前台线程)和守护线程(后台线程)如果程序中所有前台线程都执行完毕了,后台线程也会自动结束。垃圾回收线程属于守护线程。
public class ThreadDeamon {public static void main(String[] args) {TP tp1 = new TP();tp1.setName("A线程");//当main线程执行完毕后,后台TP线程自动结束tp1.setDaemon(true);tp1.start();for(int i=1;i<20;i++){System.out.println("main线程===循环"+i+"次");}}
}
class TP extends Thread{@Overridepublic void run() {for(int i=1;i<50;i++){System.out.println(Thread.currentThread().getName()+"===循环"+i+"次");}}
}

9.线程的安全问题

public class TestSafe {//静态资源 共享private static String [] arr=new String[5];private static int index=0;public static void main(String[] args) throws Exception {//匿名对象--Runnable hello=new Runnable() {@Overridepublic void run() {if (arr[index] == null) {arr[index] = "hello";index++;}}};Runnable world=new Runnable() {@Overridepublic void run() {if (arr[index] == null) {arr[index] = "world";index++;}}};Thread t1=new Thread(hello);Thread t2=new Thread(world);t1.start();t2.start();t1.join();t2.join();System.out.println(Arrays.asList(arr));//可能出现的情况,所以出现的情况会丢失 即数据不安全 // (1)hello world null null null// (2)world hello null null null// (3)hello null null null null// (4)world null null null null}
}

多线程安全问题:
1.当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
2.临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
3.原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。

使用synchronized可以解决线程不安全问题

synchronized语法:

synchronized(临界资源对象){//对临界资源对象加锁//代码 原子操作
}
public class TestSafe {//静态资源 共享private static String [] arr=new String[5];private static int index=0;public static void main(String[] args) throws Exception {//匿名对象--Runnable hello=new Runnable() {@Overridepublic void run() {//加锁 可以解决安全问题synchronized (arr) {//共享资源 原子操作if (arr[index] == null) {arr[index] = "hello";index++;}}}};Runnable world=new Runnable() {@Overridepublic void run() {synchronized (arr) {if (arr[index] == null) {arr[index] = "world";index++;}}}};Thread t1=new Thread(hello);Thread t2=new Thread(world);t1.start();t2.start();t1.join();t2.join();System.out.println(Arrays.asList(arr));}

运行一直是这个

注意:
1.每个对象都有一个互斥锁标记,用来分配给线程
2.只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步代码块
3.线程退出同步代码块时,会释放相应的互斥锁标记

10.线程死锁

当A线程拥有锁资源a时,这时A线程需要锁资源b, 而B线程拥有锁资源b,这时B线程需要锁资源a, 这样会导致A等待B线程释放资源b, B线程等待A线程释放锁资源a。 从而二个处于永久等待。从而操作死锁。

例子: 两人去餐厅吃饭,A有一根筷子,B有另一个筷子。A要等B的那根筷子,B要等A的筷子,两个人都在循环等待,则会陷入死锁状态。

解决办法:使用synchronized可以解决该问题

public class Boy extends Thread{@Overridepublic void run() {synchronized (LockObject.b){System.out.println(Thread.currentThread().getName()+"获得筷子b");synchronized (LockObject.a){System.out.println(Thread.currentThread().getName()+"获得筷子a");}}}
}
public class Gril extends Thread{@Overridepublic void run() {synchronized (LockObject.a){System.out.println(Thread.currentThread().getName()+"获得筷子a");synchronized (LockObject.b){System.out.println(Thread.currentThread().getName()+"获得筷子b");}}}
}
public class LockObject {/*筷子a*/public static final Object a = new Object();/*筷子b*/public static final Object b = new Object();
}
public class Test {public static void main(String[] args) {Boy boy = new Boy();boy.setName("aaa");Gril gril = new Gril();gril.setName("bbb");boy.start();gril.start();}
}

1.得到死锁的原因:锁与锁之间有嵌套导致。

2.解决死锁的方法:

1. 尽量减少锁得嵌套。
2. 可以使用一些安全类。
3. 可以使用Lock中得枷锁,设置枷锁时间。

11.线程通信

所谓线程通信就是线程间相互发送数据,线程间共享一个资源即可实现线程通信。

因为我们无法对哪个线程先获得cpu,也无法确定哪个线程先执行,但是我们想指定某个线程先执行,哪些线程后执行,这样的话我们就需要线程通信技术。

线程通信中的方法:

1.等待 -- 释放锁,进入等待队列public final void wait()public finnal void wait(long timeout)必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有的锁标记。同时此线程阻塞让其在等待队列中属于Object类中的方法2.通知 -- 唤醒等待队列中的线程,进入就绪队列中,参与cpu的竞争public final void notify()public final void notifyAll()

实例:存钱和取钱

规定-- 先存钱在取钱 且存一次钱 取一次钱

public class BankCard {private double balance;private boolean flag=false;public double getBalance() {return balance;}public void setBalance(double balance) {this.balance = balance;}//存钱public synchronized void save(double money){//若还有取钱则让该线程等待 进入等待队列if(flag==true){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//否则 如下操作this.balance += money;System.out.println(Thread.currentThread().getName()+"存了"+money+", 余额为:"+this.balance);//存钱后将flag置于true 进入取钱操作flag=true;//唤醒当前等待队列中线程this.notify();}//取钱 加入同步代码 使得线程安全public synchronized void withdraw(double money){//若还没有存钱 则让该线程等待if(flag==false){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//否则 如下操作this.balance -= money;System.out.println(Thread.currentThread().getName()+"取了"+money+", 余额为:"+this.balance);//取钱后将flag置于false 进入存钱操作flag=false;//唤醒当前等待队列中线程this.notify();}
}
public class BoyCard implements Runnable{private  BankCard bankCard;public BoyCard(BankCard bankCard) {this.bankCard = bankCard;}@Overridepublic void run() {for (int i=0;i<10;i++){bankCard.save(1000);}}
}
public class GrilCard implements Runnable{private  BankCard bankCard;public GrilCard(BankCard bankCard) {this.bankCard = bankCard;}@Overridepublic void run() {for (int i=0;i<10;i++){bankCard.withdraw(1000);}}
}
public class Test {public static void main(String[] args) {BankCard bankCard = new BankCard();BoyCard boyCard = new BoyCard(bankCard);GrilCard grilCard = new GrilCard(bankCard);Thread t1  = new Thread(boyCard,"AA");Thread t2  = new Thread(grilCard,"BB");t1.start();t2.start();}
}
sleep和wait的区别:1.所在的类不同:sleep属于Thread类,wait属于Object类。2.使用的地方: sleep可以在任何代码块中使用。wait只能在同步代码块中使用。3.锁资源的释放: sleep不释放锁资源,wait会释放锁资源。4.sleep时间片到了自动唤醒,wait必须需要使用notify或notifyAll唤醒notify()和 notifyAll()有什么区别?1.notifyl()会唤醒所有的线程,notify()会唤醒一个线程。2.notifyAll() 会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。

12.线程池

该池子中预先存储若干个线程对象。整个池子就是线程池

存在问题:1.线程是宝贵的内存资源,单个线程约占1MB的空间,过多分配易造成内存溢出。2.频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。线程池的作用:1.线程容器,可设定线程分配的数量上限2.将预先创建的线程对象存入池子中,并重用线程池中的线程对象3.避免频繁的创建和销毁。

线程池的创建方式:

所有的线程池—封装了一个父接口—java.util.concurrent.Executor.

​ 它的实现接口: ExecutorService.

工具类Executors可以创建相应的线程池:

[1] 创建单一线程池 newSingleThreadExecutor()

[2] 创建定长线程池。newFixedThreadPool(n);

[3] 创建可变线程池. newCachedThreadPool()

[4] 创建延迟线程池 .newScheduledThreadPool(n);

方法:Executor:线程池的根类.它中的方法execute()执行线程任务的方法Runnable类型的任务ExecutorService: 线程池的子接口shutdown(); 关闭线程池。需要等待线程池中任务执行完毕后才会关闭。shutdownNow(): 立即关闭线程池。isTerminated():判断线程池是否终止了。submit(): 提交任务给线程池中线程对象、Runnable和Callable类型的任务。
  1. 创建单一线程池 newSingleThreadExecutor()
    适应场景:队列要求线程有序执行。
ExecutorService executorService = Executors.newSingleThreadExecutor();for (int i = 1; i <= 5; i++) {executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "====");}});executorService.shutdown();}

不管存在几个线程 永远都是一个线程在执行五次

  1. 创建定长线程池。newFixedThreadPool(n);
ExecutorService executorService = Executors.newFixedThreadPool(2);for(int i=1;i<=5;i++) {executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "====" );}});executorService.shutdown();}
  1. 创建可变线程池. newCachedThreadPool()
ExecutorService executorService = Executors.newCachedThreadPool();for(int i=1;i<=5;i++) {executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "====" );}});executorService.shutdown();}
  1. 创建延迟线程池 .newScheduledThreadPool(n);
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);scheduledExecutorService.schedule(new Runnable() {@Overridepublic void run() {for(int i=1;i<=5;i++){System.out.println(Thread.currentThread().getName()+"===="+i);}}},10, TimeUnit.SECONDS);scheduledExecutorService.shutdown();}
 //long delay:延迟时间 TimeUnit unit:时间单位,Runnable command:runnable的创建线程public ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);

推荐使用原始的创建线程池方式
上面使用Executors创建线程池的四种放方式,都是基于底层ThreadPoolExecutor实现,而阿里开发手册,建议使用最原始的方式。
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式更加明确线程池的运行规则,规避资源耗尽的风险

        /*原始*//*int corePoolSize, 核心线程数
*         int maximumPoolSize, 最大线程数
*         long keepAliveTime, 空闲时间
*         TimeUnit unit, 时间单位
*         BlockingQueue<Runnable> workQueue: 堵塞队列,
*         LinkedBlockingDeque:可以设置等待的个数,如果不设置默认为Integer的最大值。*/LinkedBlockingDeque blockingDeque = new LinkedBlockingDeque(3);ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 10, TimeUnit.SECONDS, blockingDeque);/*这里注意 要保证循环的次数-阻塞队列线程数<= 最大线程数 否则存在异常*/for (int i = 0; i < 8; i++) {executor.submit(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"===");}});}//关闭线程executor.shutdown();}

13.手动锁

Lock是手动锁的父接口,它下面有很多实现类。
lock() :获取锁,如锁被占用,则等待
unlock()释放锁资源,放在finally中
boolean tryLock():尝试获取锁(成功返回true,失败返回false,不阻塞)

使用重入锁解决卖票

public class demo04 {public static void main(String[] args) {Ticket task=new Ticket();Thread t1=new Thread(task,"窗口A");Thread t2=new Thread(task,"窗口B");Thread t3=new Thread(task,"窗口C");Thread t4=new Thread(task,"窗口D");t1.start();t2.start();t3.start();t4.start();}
}class Ticket implements  Runnable{private int ticket=100;Lock s=new ReentrantLock();@Overridepublic void run() {while(true) {try {s.lock();//查看释放获取锁资源if (ticket > 0) {--ticket;System.out.println(Thread.currentThread().getName() + "卖了一张,剩余:" + ticket + "张");} else {break;}}finally {s.unlock();//释放锁}}}
}
Synchronized和Lock区别:
1.synchronized可以给类,方法,代码块加锁,而lock只能给代码块加锁
2.synchronized不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁,而lock需要自己加锁和释放锁,如果使用不当没有unlock()去释放锁就会造成死锁
3.通过lock可以知道有没有成功的获取锁,而synchronized却无法办法

Java高级--->多线程的学习相关推荐

  1. 用10086客服热线理解Java高级多线程之线程池

    Java高级多线程之线程池 客服热线案例 引入线程池 1.线程的概念 2.线程池的作用: 获取线程池 1.常用的线程池接口和类 2.代码案例 Callable接口 1.概念简述 2.应用场景 3.方法 ...

  2. Java高级框架——Spring学习

    北京尚学堂--基于此教育机构的视频所学习 目录 一.Spring 框架简介及官方压缩包目录介绍 二.IOC 三.Spring环境的搭建 四.Spring的三种创建对象方法 五.如何给bean的属性赋值 ...

  3. Java 高级 --- 多线程快速入门

    这世上有三样东西是别人抢不走的:一是吃进胃里的食物,二是藏在心中的梦想,三是读进大脑的书 多线程快速入门 1.线程与进程区别 每个正在系统上运行的程序都是一个进程.每个进程包含一到多个线程.线程是一组 ...

  4. java高级-多线程编程

    2019独角兽企业重金招聘Python工程师标准>>> 一.进程和线程 在java语言中最大的特点就是支持多线程的开发(也是为数不多支持多线程开发的语言),如果对多线程没有一个全面而 ...

  5. Java8日期时间API,Java高级多线程面试

    plusNanos(int offset):增加指定纳秒 减少相关的方法 minusYears(int offset):减少指定年 minusMonths(int offset):减少指定月 minu ...

  6. Java个人技术知识点总结(业务场景篇,java高级多线程面试

    Kafka宕机引发的高可用问题 问题要从一次Kafka的宕机开始说起. 笔者所在的是一家金融科技公司,但公司内部并没有采用在金融支付领域更为流行的RabbitMQ,而是采用了设计之初就为日志处理而生的 ...

  7. JAVA基础 多线程技术学习笔记(V1.0)

    目录 一.多线程介绍 1.1 多线程中的基本概念 1.1.1多线程与进程 1.1.2 进程.线程的区别和联系 1.1.3 并发和并行的区别 1.1.4   线程的执行特点 1.1.5 主线程与子线程 ...

  8. Java高级----多线程

    一.进程和线程 1.概念 进程,是计算机中正在运行的程序,是系统进行资源分配的基本单位.目前操作系统都是支持多进程,可以同时执行多个进程,通过进程ID区分.以前的单核CPU在同一时刻,只能有一个进程, ...

  9. 带你了解Java高级编程-----多线程

    带你了解Java高级编程-----多线程 对于Java的学习,基本的步骤是Java基础编程,掌握了Java语言的基本语法.数组.面向对象编程.异常处理这四部分之后,就要开始对Java高级编程进一步学习 ...

  10. Java高级特性增强-多线程

    请戳GitHub原文: https://github.com/wangzhiwub... 大数据成神之路系列: 请戳GitHub原文: https://github.com/wangzhiwub... ...

最新文章

  1. python 判断字符串是否包含另一个字符串_强烈推荐:Python字符串(string)方法整理(一)...
  2. 禅道8.2.4 腾讯云迁移至VM
  3. android中实现view的更新有两组方法
  4. java操作storm,Storm集群常用批量操作命令
  5. vpython 贞测碰撞_7、Pygame碰撞检测
  6. 全国计算机等级考试题库二级C操作题100套(第54套)
  7. 大二下学期软件工程概论总结
  8. 牛客14350 苦逼的单身狗
  9. Ubuntu 12.04 eclipse 安装 svn插件
  10. 自创一种新的方法建立 平衡二叉树(AVL)
  11. Geohash距离估算
  12. 增加网站的档次!网页设计师可在网站中加入暗色调
  13. Openwrt Web gui LUCI 流程浅析
  14. Mybatis generator mapper文件重新生成不会覆盖原文件
  15. arm服务器测评_某ARM服务器与X86服务器简单性能对比
  16. cublas_学习笔记2
  17. 【MySQL 12】MySQL 8.0.18 重新初始化
  18. 京东校招java面试题_2018京东校招Java笔试题
  19. 如何破解网络密码?(2种方法)
  20. 第一台生物计算机,世界上第一台DNA计算机问世

热门文章

  1. 【TensorFlow 官网 可以直接访问】让中国开发者更容易地使用TensorFlow打造人工智能应用
  2. 基于形心的目标检测方法
  3. Virtual Judge——C - 月之数
  4. 结对编程分析——中小学数学卷子自动生成程序
  5. 「IOI2018」Highway 高速公路收费
  6. Android 基于GSYVideoPlayer实现短视频软件上下滑自动播放视频
  7. Chirp信号简单介绍
  8. Knights CodeForces - 1221B
  9. 使用动态SQL中的if标签做条件判断的几种用法和注意点
  10. 生产车间管理使用ERP系统提高企业管理水平