黑马程序员——Java基础---多线程
来源:互联网 发布:mac文件储存位置 编辑:程序博客网 时间:2024/05/21 10:45
-----------android培训、java培训、java学习型技术博客、期待与您交流!------------
概述
1、 进程
正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
2、线程
是进程中的单个顺序控制流,是一条执行路径
一个进程如果只有一条执行路径,则称为单线程程序。
一个进程如果有多条执行路径,则称为多线程程序。
3、Java程序运行原理
java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
并且要注意的是,jvm虚拟机的启动是多线程的,因为除了主线程之外还会启动负责垃圾回收机制的线程。
线程的创建
1、 继承Thread类。又称为继承方式。
创建步骤:
a,定义类继承Thread。
b,复写Thread中的run方法。
目的:将自定义代码存储在run方法中,让线程运行。
c,创建定义类的实例对象。相当于创建一个线程。
d,用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。
发现运行结果每次都不同。
因为多个线程都在获取cpu的执行权。cpu执行到谁,谁就运行。
明确一点,在某一时刻,只能有一个程序在运行。(多核除外)
cpu在做着快速的切换,已达到看上去是同时运行的效果。
我们可以形象把多线程的运行行为看做是在互相抢夺cpu的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,由cpu说的算
为什么要覆盖run方法?
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法
也就是说Thread类中的run方法,用于存储线程要运行的代码
start调用底层让控制单元执行
run仅仅是封装线程要运行的代码
程序示例:
class Demo extends Thread{
public void run(){
System.out.println("demo run");
}
}
class ThreadDemo{
public static void main(String[] args){
Demo d = new Demo();//创建好一个线程
d.start();//开启线程并执行该线程的run方法
d.run();//仅仅是对象调用方法。而线程创建了并没有运行
}
测试用例:
/*
创建两个线程,和主线程交替运行。
*/
class ThreadTest{
public static void main(String[] args){
Test tt = new Test("one");
Test ts = new Test("two");
tt.start();
ts.start();
for (int i = 0;i<60 ;i++ ){
System.out.println("main....."+i);
}
}
}
class Test extends Thread{
private String name;
Test(String name){
this.name = name;}
public void run(){
for (int i = 0 ;i<60 ;i++ ){
System.out.println(name+"run....."+i);
}
}
}
如何获取和设置线程名称:
线程都有自己默认的名称。
Thread-编号 该编号从0开始
static Thread currentThread():获取当前线程对象
getName():获取线程名称
public final String getName()
public final void setName(String name)
其实通过构造方法也可以给线程起名字
演示用例:
class Test extends Thread{
//private String name;
Test(String name){
//this.name = name;
super(name);//自定义线程名称
}
public void run(){
for (int i=0;i<60 ;i++ ){
System.out.println((Thread.currentThread()==this)this.getName()+"run....."+i);
}
}
class ThreadTest{
public static void main(String[] args){
Test t1 = new Test("one");
Test t2 = new Test("two");
t1.start();
t2.start();
for (int i=0;i<60 ;i++ ){
System.out.println("main......"+i);
}
}
线程调度:
线程有两种调度模型:
分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
线程的运行状态:
被创建:等待启动,调用start启动。只要有进程,线程就不会结束。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消忙状态:stop()方法,或者run方法结束。
2、 实现Runnable接口,又称为实现方式。
使用继承方式有一个弊端,那就是如果该类本来就继承了其他父类,那么就无法通过Thread类来创建线程了。所以,为了解决这一弊端就引入了创建线程的第二种方式:实现Runnable接口
创建步骤如下:
a,定义类实现Runnable的接口。
b,覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。
c,通过Thread类创建线程对象。
d,将Runnable接口的子类对象作为实参传递给Thread类的构造方法。
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。
e,调用Thread类中start方法启动线程。start方法会自动调用Runnable接口子类的run方法。
实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。
适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
程序示例:
class TicketDemo{
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);//创建线程并指定run方法所属对象
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);//new线程的同时,指定run方法所属对象
t1.start();
t2.statt();
t3.start();
}
}
class Ticket implements Runnable{
private int tick = 100;
public void run(){
while (true){
System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
}
}
}
两种方法的区别:
实现方式:避免了单继承的局限性。
在定义线程时,建议使用实现方式
继承Thread类:线程代码存放在Thread子类run方法中
实现Runnable:线程代码存放在接口子类的run方法中
线程安全问题
1、导致安全问题的出现的原因:
当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。
简单的说就两点:
a、多个线程访问出现延迟。
b、线程随机性 。
注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
2、解决办法
基本思想:让程序没有安全问题的环境。
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
在java中对于多线程的安全问题提供了专业的解决方式——synchronized(同步)
同步代码块
格式:
synchronized(对象){需要同步的代码;}
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
同步的前提:
1、必须要有两个或者以上的线程。
2、必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在进行
好处:解决了多线程的安全问题
弊端:对锁进行判断,较为消耗资源(在允许消耗范围内),
演示用例:
class {
public static void main(String[] args){
}
}
class Ticket implements Runnable{
private int tick = 100;
Object obj = new Object();
public void run(){
while (true){
synchronized (obj){
if (tick>0){
try{
Thread.sleep(10);
}
catch (Exception e){
}
}
}
}
同步函数
格式:
在函数上加上synchronized修饰符即可。
演示用例:
class BankDemo{
public static void main(String[] args){
Cus c = new Cus();
Thread t1= new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
class Bank{
private int sum;
//Object obj = new Object();
public synchronized void add(int n){
//synchronized{//同步在操作共同数据的多态语句
sum = sum+n;
System.out.println("sum="+sum);
// }
}
class Cus implements Runnable{
private Bank b = new Bank();
public void run(){
for (int i = 0;i<3 ;i++ ){
b.add(100);
}
}
那么同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。
验证:
使用两个线程卖票
一个再同步代码块中
一个再同步函数中
都在执行卖票动作,若同步就不会出现错误的票
class {
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{
Thread.sleep(p);
}
catch (Exception e){
}
t.flag = false;
t2.start();
}
}
class Ticket implements Runnable{
private int tick = 1000;
Object obj = new Object();
boolean flag = ture;
public void run(){
if (flag){
while (true){
synchronized (this){//obj
if (tick>0){
try{
Thread.sleep(10);
}
catch (Exception e){
} System.out.println(Thread.currentThread().getName()+"....code...."+tick--);
}
}
}
}else
while (true){
show();
}
}
public synchronized void show(){
if (tick>0){
try{
Thread.sleep(10);
}
catch (Exception e){
} System.out.println(Thread.currentThread().getName()+".....show..."+tick--);
}
}
}
}
如何寻找多线程中的安全问题
a,明确哪些代码是多线程运行代码。
b,明确共享数据。
c,明确多线程运行代码中哪些语句是操作共享数据的。
静态函数的同步方式
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:
类名.class 该对象的类型是Class
这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class(内存中唯一)。
演示用例:
class {
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{
Thread.sleep(p);
}
catch (Exception e){
}
t.flag = false;
t2.start();
}
}
class Ticket implements Runnable{
private static int tick = 1000;
//Object obj = new Object();
boolean flag = ture;
public void run(){
if (flag){
while (true){
synchronized (Ticket.class){//obj
if (tick>0){
try{
Thread.sleep(10);
}
catch (Exception e){
}
System.out.println(Thread.currentThread().getName()+"....code...."+tick--);
}
}
}
}else
while (true){
show();
}
}
public static synchronized void show(){
if (tick>0){
try{
Thread.sleep(10);
}
catch (Exception e){
} System.out.println(Thread.currentThread().getName()+".....show..."+tick--);
}
}
}
}
单例设计模式——懒汉式
class Single{//保证一个类内存的唯一性
private static Single s = null;//s为共享数据,若存在多个线程并发访问getInstance方法,有多条语句在操作s
private Single(){}
public static synchronized Single getInstance{
if (s == null){
s = new Single();
return s;
}
A进B就不能进,A判断满足,创建对象,B进判断不为null,直接使用s
懒汉式加了同步每次都要判断锁,会比较低效
-------------------------------------------------------------------------------
public static Single getInstance{
if (s = =null){
synchronized(Single.class){
if (s == null){
只要有一个初始化,以后的救不用再判断锁,因为不满足null
s = new Single();
return s; }
}
懒汉式的特点在于延迟加载,会存在问题。
多线程访问时会产生安全问题,可以加同步解决,同步函数,同步代码块都可以
但是有点低效,用双重判断的方式可以解决效率的问题,加同步的时候使用的锁
为该类所属的字节码文件对象
死锁
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
当同步中嵌套同步时,就有可能出现死锁现象。
演示用例:
/*同步代码块的锁是obj,同步函数的锁是this
this锁中有obj锁,obj锁中有this锁*/
class Test{
private boolean flag;
Tead (boolean flag){this.flag = flag;}
public void run(){if (flag){
while (true){
synchronized(MyLock.locka){
System.out.println("if locka");
synchronized(MyLock.lockb){
System.out.println("if lockb");}
}
}}else{
while (){
synchronized(MyLock.lockb){
System.out.println("else lockb");
synchronized(MyLock.locka){
System.out.println("else locka");
}
}}
}}
}
class MyLock{
Object locka = new Object();
Object lockb = new Object();
}
class DeadLockTest{
public static void main(String[] args){
Thread t1 = new Thread(new Test);
}
线程间通信
其实就是多个线程在操作同一个资源,但是操作的动作不同。
演示用例:
class {
public static void main(String[] args){
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.satrt();
}
}
class Res{
String name;
String sex;
}
class Input implements Runnable{
private Res r ;
Input (Res r){this.r = r;}
public void run(){
int x = 0;
while (true){
synchronized(r){//保证锁相同
if (x == 0){
r.name = "";
r.sex = "";}
else{
r.name = "";
r.sex = "";}
x = (x+1)%2;
}//只同步一个无法解决问题
}
}
}
class Output implements Runnable{
private Res r ;
Output (Res r){this.r = r;}
public void run(){
while (true){
synchronized(r){
System.out.println(r.name+""+r.sex);
}//都在操作同一个对象
}
}
}
几个小问题:
1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
a,这些方法存在与同步中。
b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
2)wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
3)为甚么要定义notifyAll?
因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。
等待唤醒机制:
class {
public static void main(String[] args){
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}
class Res{
private String name;
private String sex;
private boolean flag = false;
public synchronized void set(){
if (flag){
try{
this.wait();
}
catch (Exception e){
}
this.name = name;
this.sex = sex;}
flag = true;
this.notify();
}//可能存在安全问题,赋值需要被同步,非静态同步函数,锁为this
public synchronized void out(){
if (
1flag){
try{
this.wait();
}
catch (Exception e){
}
System.out.println(name+"..........."+sex);
flag = false;
this.notify();
}//两个线程,也需要同步
}
class Input implements Runnable{
private Res r ;
Input (Res r){this.r = r;}
public void run(){
int x = 0;
while (true){
if (x == 0){
r.set("","");}
else{
r.set("","");}
x = (x+1)%2;
}
}
}
}
class Output implements Runnable{
private Res r ;
Output (Res r){this.r = r;}
public void run(){
while (true){
r.out();
}
}
}
线程池
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
JDK1.5中提供了多线程升级解决方案。
/*notifyAll不只唤醒了对方,连本方也被唤醒,
现在要求只唤醒对方线程*/
/*JDK1.5开始
java.util.concurrent.locks
|--Lock//替代了synchronized方法和语句的使用,接口
|--lock();获取锁
|--unlock();释放锁
|--Condition//替代了Object监视器方法的使用,notify,notifyAll,接口
|--await();
|--singnal();
|--singnalAll();
提供 了多线程的升级解决方案升级解决方案的示例:
class {
public static void main(String[] args){
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
t1.start();
t2.start();
}
}
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();//创建锁
private Condition con_pro = lock.newCondition();//wait应该定义在同步代码块中,同步语句块有锁,每个wait都要标识自己所属的锁
private Condition con_con = lock.newCondition();
public void set(String name)throws InterruptedException{
Lock.lock();//拿到锁,进入,执行被锁代码,过程中抛出异常,程序结束,功能结束,没有读到unlock,说明没有放锁,其他进程就无法进入了,所以一定要放锁
try{
while (flag){//生产者进入,判断为真,执行/再回来判断不为真,等待
con_pro.await();
this.name = name+""+count++;//带着编号设置数据
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
con_con.signal();}//唤醒消费者
finally{
Lock.unlock();}
public void out()throws InterruptedException{
Lock.lock();//消费者进入,拿到锁,判断不为真,执行/再回来判断为真,等待
try{
while (!flag){
con_con.await();
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
con_pro.signal();}//唤醒生产者
finally{
Lock.unlock();}
}
class Producer implements Runnable{
private Resource res;
Producer(Resource res){
this.res = res;
}
public void run(){
while (true){
try{
res.set("+商品+");
}
catch ( InterruptedException e){
}
}
}
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res){
this.res = res;
}
public void run(){
while (true){
try{
res.out();
}
catch (InterruptedException e){
}
}
}
守护线程
setDaemon()
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,java虚拟机退出。
该方法必须在启动线程前调用。
我们所看的都是前台线程,当我们把某些线程标记成后台线程后,它就就被了一些特殊的含义,后台线程的特点就是,开启后和前台线程共同抢夺cpu的执行权,与前台线程的区别在于执行结束:
当所有的前台线程都结束时,后台线程会自动结束。
演示用例:
class StopThreadDemo{
public static void main(String[] args){
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.setDaemon(true);
t2.setDaemon(true);//程序结束
--------------------------------
t1.start();
t2.start();//程序挂起
int num = 0;
while (true){
if (num++ == 60){
//st.changeFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}
class StopThread implements Runnable{
private boolean flag = true;
public synchronized void run(){
while (flag){//线程0进入,拿到锁,冻结,释放资格;线程1进入,释放资格
try{
wait();
}
catch (InterruptedException e){
System.out.println(Thread.currentThread().getName()+".........Exception");
flag = false;
}
System.out.println(Thread.currentThread().getName()+".........run");
}
}
public void changeFlag(){
flag = false;
}
}//主线程开启了两个线程后与两个线程一块抢夺资源,主线程是前台
停止线程
停止线程
1、定义循环结束的标记
因为线程运行代码一般都是循环,只要控制了循环即可
2、使用interrupt(中断)方法
该方法是结束线程的冻结状态,使线程回到运行状态中来
停止线程只有一种方法,run方法结束。
开启多线程运行,运行代码通常都是循环结构,只要控制住循环,就可以让run方法结束,也就是线程结束
演示用例:
class StopThreadDemo{
public static void main(String[] args){
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 0;
while (true){
if (num++ == 60){
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
}
}
class StopThread implements Runnable{
private boolean flag = true;
public void run(){
while (flag){
System.out.println(Thread.currentThread().getName()+".........run");
}
}
public void changeFlag(){
flag = false;
}
}
/*
特殊情况:
当线程处于冻结状态,就不会读取到标记,那么线程就不会结束
当没有指定方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
*/
class StopThreadDemo{
public static void main(String[] args){
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 0;
while (true){
if (num++ == 60){
//st.changeFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}
class StopThread implements Runnable{
private boolean flag = true;
public synchronized void run(){
while (flag){//线程0进入,拿到锁,冻结,释放资格;线程1进入,释放资格
try{
wait();
}
catch (InterruptedException e){
System.out.println(Thread.currentThread().getName()+".........Exception");
flag = false;
}
System.out.println(Thread.currentThread().getName()+".........run");
}
}
public void changeFlag(){
flag = false;
}
}
toString()&Priority
打印线程名称、优先级和线程组
打印结果:
Thread[Thread-1,5,main]
所以线程默认优先级是5
MAX_PRIORITY 线程可以具有的最高优先级,10。
MIN_PRIORITY 线程可以具有的最低优先级,1。
NORM_PRIORITY 分配给线程的默认优先级,5。
setPriority(Thread.MAX_PRIORITY)设置优先级
yield()暂停当前正在执行的线程对象,并执行其他线程。减缓线程执行的频率
而且能达到线程平均运行的效果
演示用例:
class JoinDemo{
public static void main(String[] args)throws Exception{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t1.setPriority(Thread.MAX_PRIORITY);
t2.start();
for (int x = 0;x<80 ;x++ ){
System.out.println("main......"+x);
}
System.out.println("over");
}
}
class Demo implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName()+""+x);
}
}
join方法
主线程碰到谁的join等谁,谁抢到不管
当A线程执行到了B线程的join方法是,A就会等待,等B线程执行完,A才会执行。
join可以用来临时加入线程执行
class JoinDemo{
public static void main(String[] args)throws Exception{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t1.join();//抢夺cpu执行权,主线程放出执行权,main处于冻结状态,t1执行结束,main才能执行。当我们在进行多线程运算时,若条件满足我们可以临时加入一个线程,让该线程运算完,再继续运行
t2.start();
1.join();//main开启了·1,2,碰到1的join,释放执行权,但是1,2 存活,那么cpu就对12交替执行,1什么时候结束main什么时候活
for (int x = 0;x<80 ;x++ ){
System.out.println("main......"+x);
}
System.out.println("over");
}
}
class Demo implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName()+""+x);
}
}
Thread 0执行完,main和t2才开始交替执行
开发中线程的创建:
扩展小知识:
class ThreadTest{
public static void main(String[] args){
new Thread(){
public void run(){
for (int x = 0;x<100 ;x++ ){
System.out.println("Thread.currentThread().getName()+""+x");
}
}
}.start();
Runnable r = new Runnable(){
for (int x = 0;x<100 ;x++ ){
public void run(){
System.out.println("Thread.currentThread().getName()+""+x");
}
}
};
new Thread(r).start();
}
-----------android培训、java培训、java学习型技术博客、期待与您交流!------------
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础---多线程
- 黑马程序员——Java基础多线程
- 黑马程序员——Java基础->多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础--多线程
- 黑马程序员——java基础--多线程
- 黑马程序员——Java基础--- 多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础-多线程
- 黑马程序员——Java基础:多线程
- android 顶部Tab实现及原理
- Django学习(一) 安装配置Django
- nyoj 55 (摘果耗费体力最少)(队列问题)
- Gios WORD .NET Library (using RTF specification)
- js之this,请问你是谁
- 黑马程序员——Java基础---多线程
- HDOJ S-Nim 1536&POJ S-Nim 2960【求SG函数+Nim游戏】
- iphone-only apps icon
- 尊贤、谦虚、谨慎、交友、有恒、微渐、慎始终、因果
- 【iOS学习】七、Foundation学习
- Swift学习笔记系列——(12)继承
- 国外整理的一套在线渗透测试资源合集
- SEO基础代码优化
- hdu 1873 看病要排队