1.线程概念

下面是在一个单独的线程中执行一个任务的简单过程:

(1)将任务代码移到实现了Runnable接口的类的run方法。这个接口非常简单,只有一个方法:

public interface Runnable{void run();
}

可以 如下所示实现一个类:

class MyRunnble implements Runnable{public void run(){task code}
}

(2)创建一个类对象

Runnable  r = new MyRunnble();

(3)由Runnable创建一个Thread对象

Thread t = new Thread(r);

(4)启动线程。

t.start();

注意:可以通过一个Thread类的子类定义一个线程,如下所示:

class MyThread extends Thread{public void run(){task code}
}

然后,构造一个子类的对象,并调用start方法。目前,这种方法已不再推荐。应该从运行机制上减少需要并行运行的任务数量。如果有很多任务,要为每个任务创建一个独立的线程所付出的代价太大了,可以使用线程池来解决问题。

不要调用Thread类或Runnable对象的run方法。直接调用run方法,只会执行同一个线程中的任务,而不会启动新线程。应该调用Thread.start方法。这个方法将创建一个执行run方法的新线程。

2.中断线程

当对一个线程调用interrupt方法时,线程将被设置为中断状态。每个线程都应该不时地检查这个状态,根据线程是否中断进行不同的操作。

要想弄清线程是否为中断状态,首先调用静态Thread.currentThread方法获得到当前线程,再调用isInterrupted方法。

但是,如果一个被阻塞线程(调用sleep或wait)上调用interrupt方法,阻塞调用将会被InterruptedException异常中断,线程被激活。

没有任何语言方面的需求要求一个被中断的线程应该终止。中断一个线程不过是引起它的注意。被中断的线程可以决定如何响应中断。某些线程如此重要以至应该处理完异常后继续执行,而不理会中断。但,更普遍的是,线程将简单地将中断作为一个终止的请求。

public void run(){try{while(!Thread.currentThread().isInterrupted() && more work to do){   //执行方法前先检测线程是否中断了do more work}}catch(InterruptedException e){// thread was interrupted during sleep or wait }finalize(){cleanup,if required}//exiting the run method terminates the thread}

如果在每次工作迭代后都调用sleep方法(或者其他的可中断方法),isInterrupted检测既没有必要,也没有用处。如果在中断状态调用sleep方法,它不会休眠,相反,它将清除这一状态并抛出InterruptedException。因此,如果你的循环调用sleep,不用检测中断状态,而是要如下所示捕获InterruptedException异常:

public void run(){try{...while(more work to do){do more workThread.sleep(delay);}}catch(InterruptedException e){//thread was interrupted during sleep}finally{cleanup,if required}//exiting the run method terminates the thread}

注意:有两个类似的方法,interrupted和isInterrupted。interrupted方法是一个静态方法,它检测当前的线程是否被中断。而且,调用interrupted方法会清除该线程的中断状态。另一方面,isInterrupted方法是一个实例方法,可用来检验线程是否为中断状态。调用这个方法不会改变中断状态。

在很多发布的代码中会发现InterruptedException异常被抵制在很低的层次上,像这样:

    void mySubTask(){try{sleep(delay);}catch(InterruptedException e){   }   //不要忽视它}

不要这样做!如果不认为在catch子句中做什么处理,仍然有两种合理的选择。

在catch子句中调用Thread.currentThead().interrupt()方法来设置中断状态。这样,调用者可以对其进行检测。

void mySubTask(){try{sleep(delay);}catch(InterruptedException e){Thread.currentThread().interrupt();}   }

或者,更好的选择,用throws InterruptedException声明你的方法抛出异常。于是,调用者(或最终的run方法)可以捕获这一异常。

 void mySubTask() throws InterruptedException{sleep(delay);}

3.线程状态

线程可以有6种状态:

New(新生)

Runnable(可运行)

Blocked(被阻塞)

Waiting(等待)

Timed waiting(计时等待)

Terminated(被终止)

3.1新生线程

当用new操作符创建一个新线程时,该线程还没有开始运行。在线程运行前还有一些簿记工作要做。

3.2可运行线程

一旦调用start方法,线程处于runnable状态。一个可运行的线程可以正在运行也可没能没有运行。一旦一个线程开始运行,它不必一直保持运行。线程调用的细节依赖于操作系统提供的服务。抢占式调用系统给每一个可运行线程一个时间片一执行任务。当时间片用完,操作系统剥夺该线程的运行权,并给另一个线程一个运行时间片。当选择下一个线程时,操作系统考虑线程的优先级。

3.3被阻塞线程和等待线程

当线程处于被阻塞或等待状态时,它暂时不活动。它不运行任何代码且消耗最少的资源。直到线程调用器重新激活它。

当一个线程试图获取一个内部对象锁(不是java.util.concurrent库中的锁),而该锁被其他线程持有,则该线程进入阻塞状态。当所有其他线程释放该锁,并且线程调度器允许本线程持有它的时候,该线程将变成非阻塞状态。

当线程等待另一个线程通知调度器一个条件是地,它自己进入等待状态。在调用Object.wait方法或Thread.join方法,或者是等待java.util.concurrent库中的Lock或Condition时,就会出现这种情况。实际上,被阻塞状态与等待状态是有很大不同。

有几个方法有一个超时参数,调用它们导致线程进入计时等待状态。这个状态将一直保持到超时期满或者接收到适当的通知。带有超时参数的方法有Thread.sleep和Object.wait,Thread.join,Lock.tryLock和Condition.await的计时版。

3.4 被终止的线路

线程因如下两个原因之一而被终止:

  • 因为run方法正常退出而自然死亡
  • 因为一个没有捕获的异常终止了run方法而意外死亡

4.线程属性

4.1线程属性

在Java程序设计语言中,每一个线程有一个优先级。默认情况下,一个线程继承它的父线程的优先级。可以用setPriority方法提高或降低任何一个线程的优先级。

当线程调用器有机会选择新线程时,它首先选择具有较高优先级的线程。但是,线程优先级是高度依赖于系统的。不同的操作系统优先级的个数也同。如,Window有7个优先级别。在Sum为linux提供的Java虚拟机,所有线程具有相同的优先级。

注意:不要将程序构建为功能的正确性依赖于优先级。

如果确定要使用优先级,应该避免初学者常犯的一个错误。如果有几个高优先级的线程没有进入非活动状态,低优先级的线程可能永远也不能执行。每当调用器决定运行一个新线程时,首先会在具有高优先级的线程中进行选择,这样可能导致低优先级的线程完全饿死。

4.2守护线程

t.setDaemon(true);将线程转换为守护线程。守护线程唯一用途是为其他线程提供服务,如计时线程。当只剩下守护线程,虚拟机就退出了。

守护线程有时会被初学者错误地使用,他们不打算考虑关机动作。但是这是很危险的。守护线程应该永远不去访问固有资源,如文件,数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

4.3未捕获异常处理器

线程的run方法不能抛出任何被检测的异常。但是,不被检测的异常会导致线程终止。在这种情况下,线程就死亡了。最麻烦的是,在线程中抛出的异常即使在主线程中使用try...catch也无法截获,因此可能导致一些问题出现,比如异常的时候无法回收一些系统资源,或者没有关闭当前的连接等等。

主线程之所以不处理子线程抛出的RuntimeException,是因为线程是异步的,子线程没结束,主线程可能已经结束了。

在线程死亡之前,异常被传递一个实现了 Thread.UncaughtExceptionHandler接口的处理器。 UncaughtExceptionHandler名字意味着处理未捕获的异常。更明确的说,它处理未捕获的运行时异常。Java编译器要求处理所有非运行时异常,否则程序不能编译通过。

从JavaSE 5.0起,可以用setUncaughtExceptionHandler方法为任何线程设置一个处理器,也可以用Thread类的静态方法setDefaultUncaughtExceptionHandler为所有线程设置一个默认的处理。如果不设置默认的处理器,默认的处理器为空。但是,如果不为独立的线程设置处理器,此时的处理器就是该线程的ThreadGroup对象。

ThreadGroup类实现Thread.UncaughtExceptionHandler接口,它的UncaughtExceptionHandler方法做如下操作:

如果该线程组有父线程组,那么父线程组的UncaughtExceptionHandler方法被调用。

否则,如果Thread.GetDefaultException方法返回一个非空的处理器,则调用该处理器。

否则,如果Throwable是ThreadDeath的一个实例,什么也不做。

如:实现setUncaughtExceptionHandler

class ExceptionHandle implements UncaughtExceptionHandler{public void uncaughtException(Thread thread, Throwable e) {if(e instanceof NullPointerException)System.out.println("抛出空指针");e.printStackTrace();}
}

在主方法中调用

  public static void main(String[] args) {Thread t = new Thread(new TestRunnable());t.setUncaughtExceptionHandler(new ExceptionHandle());t.start();}

5.同步

首先,我们构造 一个简单的银行家问题,定义一个银行:

public class Bank {private int account;public Bank(int account){this.account = account;}public void transfer(int num){   //对总帐进行增减相同的数目account += num;account -= num;System.out.println("银行总帐:"+account);}public int getAccount(){return this.account;}
}

为银行实现一个线程任务:

public class BankRunnable implements Runnable {Bank bank;int num;public BankRunnable(Bank bank,int num) {this.bank = bank;this.num = num;}public void run() {while(true){this.bank.transfer(this.num);   //对银行总帐进行增减操作System.out.println(Thread.currentThread().getName() +"   Account is " + bank.getAccount());    //输出相关信息try {Thread.sleep(1000);    //线程休眠一秒} catch (InterruptedException e) {e.printStackTrace();}}}
}

在测试方法中定义10个线程同时去增减同一个银行的总帐:

public static void main(String[] args) {Bank bank = new Bank(1000);    //定义一个银行Thread[] threads = new Thread[10]; //定义10个线程,同时操作一个银行for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(new BankRunnable(bank, 100));}for(int i = 0; i < threads.length; i++){threads[i].start();}}

过一个段时间后,可以看到程序出现了问题,银行总帐不再是1000了,变多或变少了。

当两个线程试图同时更新同一个帐户的时候,假定两个线程同时执行:account += num;稍微了解机算机底层的朋友都知道,这不是原子操作,该指令可能被处理为

1).将account 加载到寄存器

2).在寄存器中对account数值 执行加法运算
3).将运算结果写入到内存中account 指向位置
假定account 为1000,而线程1执行了步骤1,2,计算结果为1100(注意,内存中account仍然为1000,计算结果1100还没有写回内存),但是,它被剥夺了运行权(抢占式调用系统),线程2被唤醒并完成了修改account 工作,则内存中account为1100,而线程1又被唤醒并完成其第3步,将之前结果1100写入内存,这一操作擦去了线程2所做的更新。于是总金额不再正确了(内存中account为1100,实际上应该为1200)

注意:删除线程run方法中的打印语句,程序出错的概率将减少。因为每个线程在再次睡眠之前所做的工作减少,那调度器在计算过程中剥夺线程的运行权就减少。

锁对象

可以用ReentrantLock保护代码块:

myLock.lock();try{critical section}finally{myLock.unlock;}

这一结构确保任何时刻只有一个线程进入临界区。一旦一个线程封锁了锁对象,其他对象线程都无法通过lock语句。当其他线程调用lock时,它们被阻塞,直到第一个线程释放锁对象。

注意:把解锁操作放到finally子句是很重要。如果在临界区的代码抛出了异常,锁必须被释放,否则,其他线程将永远阻塞。

将上面例子银行修改为:

public class Bank {private int account;ReentrantLock lock;    //锁对象public Bank(int account){this.account = account;lock = new ReentrantLock();}public void transfer(int num){   //对总帐进行增减相同的数目try{lock.lock();  //加锁account += num;account -= num;}finally{lock.unlock();    //解锁}}public int getAccount(){return this.account;}
}

而不会出现总帐出错的问题了。

注意:如果有多个Bank对象,那每个Bank对象都有一个ReentrantLock对象。如果两个对象试图访问两一个Bank对象,那锁以串行方式提供服务。但如果两个线程访问不同的Bank对象,每一个线程得到不同的锁对象,则两个线程都不会发生阻塞。因为线程在操作不同的Bank实例时,线程之前不会相互影响。

锁是可重入的。因为线程可以重复地获得已经持有的锁。锁保持一个持有计数来跟踪对lock方法的嵌套调用。线程在每一次调用lock都 要调用unlock来释放锁。由于这一特性,被一个锁保护的代码可以调用另一个使用相同的锁的方法。

注意:要留心临界区中的代码,不要因为异常的抛出而跳出了临界区。如果在临界区代码结束前抛出了异常,finally子句将释放锁,但会使对象可能处于一种受损状态。如上例子中,account += num;account -= num;代码被加锁,如果执行完account += num;出现异常了而解锁跳出临界区,则银行总帐也是不正确的。

注意:可以通过ReentrantLock(boolean fair) 构造一个带公平策略的锁,但使用公平锁比使用常规锁要慢很多。只有当你确实了解你自己要做什么并且对于你要解决的问题有一个特定的理由必须使用公平锁的时候,才可以使用公平锁。即使使用公平锁,也无法确保线程调用器是公平的。如果线程调度器选择忽略一个线程,而该线程为了这个锁已经等待了很长时间了,那么就没有机会公平处理这个锁了。

条件对象

我们修改一下银行对象,使之有存款,取款业务:

public class Bank {private int account;private ReentrantLock lock; //锁对象public Bank(int account){lock = new ReentrantLock();this.account = account;}public void deposit(int num){    //存款try{lock.lock();int accoutBefore = account;account += num;System.out.println("银行总帐:" + accoutBefore + " 存入: " + num + "  ,银行总帐:" + this.account);    //输出信息,以便调试}finally{lock.unlock();   //解锁}}public void withdrawal(int num){  //取款try{lock.lock();while(num > account){    //银行没有足够金额,则等待其他线程存款使银行有足够金额再取款try {System.out.println("金额不足,等待其他线程存款");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}int accoutBefore = account;account -= num;System.out.println("银行总帐:" + accoutBefore + " 取出: " + num + "  ,银行总帐:" + this.account);}finally{lock.unlock();  //解锁}}
}

然后我们定义一个线程进行取款工作,一个线程进行存款工作 :

public class DepositRunnable implements Runnable{private Bank bank;private int num;public DepositRunnable(Bank bank,int num){this.bank = bank;this.num = num;}public void run() {while(true){bank.deposit(num);   //存款try {Thread.sleep(1000);    //线程休眠一秒} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class WithdrawalRunnalbe implements Runnable {private Bank bank;private int num;public WithdrawalRunnalbe(Bank bank,int num){this.bank = bank;this.num = num;}public void run() {while(true){bank.withdrawal(num);   //取款try {Thread.sleep(1000);    //线程休眠一秒} catch (InterruptedException e) {e.printStackTrace();}}}
}

主测试方法如下 :

    public static void main(String[] args) {Bank bank = new Bank(1000);    //定义一个银行Thread depositThread = new Thread(new DepositRunnable(bank, 500)); //定义存款线程Thread withdrawalThread = new Thread(new WithdrawalRunnalbe(bank, 1000));  //定义取款线程    depositThread.start();withdrawalThread.start();}

但问题来了,很快程序就进入了等待死循环,如下:

当银行中没有足够金额时,它会等待另一线程向银行注入资金。但这一线程刚刚获得了对lock排它性访问,因此别的线程没有进行存款操作的机会。所以程序进入了等待的死循环。这也就是为什么 我们需要条件对象的原因。

一个锁对象可以有一个可多个相关的条件对象。可以通过newCondition方法获得一个条件对象。习惯上给每一个条件对象命名为可以反映它所表达的条件的名字。如:sufficientFunds = lock.newCondition;当银行发现余额不足时,可以调用sufficientFunds.await()方法,使当前线程被阻塞,并放弃锁。这样另一个线程就可以进行存款操作了。

一旦一个线程调用了await方法,它进入该条件的等待集。当锁可用时,该线程不能马上解除阻塞。相反,它仍处于阻塞状态,直到另一个线程调用同一条件对象上的signalAll方法为止。所以,当另一线程存款时,它应该调用sufficientFund.ssignalAll(),这一调用重新激活因为这一条件而等待的所有线程。它们将试图重新进入该对象。一旦锁成为可用的,它们中的某个将从await调用返回,获得该锁并从被阻塞的地方继续执行。

public class Bank {private int account;private ReentrantLock lock;  //锁对象private Condition sufficientFunds;public Bank(int account){lock = new ReentrantLock();this.account = account;sufficientFunds  = lock.newCondition();}public void deposit(int num){  //存款try{lock.lock();int accoutBefore = account;account += num;sufficientFunds.signalAll();System.out.println("银行总帐:" + accoutBefore + " 存入: " + num + "  ,银行总帐:" + this.account);    //输出信息,以便调试}finally{lock.unlock();   //解锁}}public void withdrawal(int num){  //取款try{lock.lock();while(num > account){    //银行没有足够金额,则等待其他线程存款使银行有足够金额再取款try {System.out.println("金额不足,等待其他线程存款");sufficientFunds.await();} catch (InterruptedException e) {e.printStackTrace();}}int accoutBefore = account;account -= num;System.out.println("银行总帐:" + accoutBefore + " 取出: " + num + "  ,银行总帐:" + this.account);}finally{lock.unlock(); //解锁}}
}

锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。

锁可以管理试图进入被保护代码段的线程。

锁可以拥有一个或多个相关的条件对象。

每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。

synchronized

如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。也就是说,要调用这个方法,线程必须获得内部的对象锁。

   public synchronized void method(){method body}

等价于

     public void method(){this.intrinsicLock.lock();try{method body}finally{this.intrinsicLock.unlock();}}

synchronized内部对象锁只有一个相关条件。wait方法添加一个线程到等待集中,notiryAll/notify方法解除等待线程的阻塞状态。调用wait或notifyAll等价于:

intrinsicCondition.await();

intrinsicCondition.signalAll();

注意:wait,notifyAll或notify方法是Obect类的final方法。Condition方法必须被命名为await,signalAll和signal以便它们不会与那些方法发生冲突。

可将上面例子中银行对象修改为:

public class Bank {private int account;public Bank(int account){this.account = account;}public synchronized void deposit(int num){    //存款int accoutBefore = account;account += num;notifyAll();System.out.println("银行总帐:" + accoutBefore + " 存入: " + num + "  ,银行总帐:" + this.account);    //输出信息,以便调试}public synchronized void withdrawal(int num){    //取款try{while(num > account){    //银行没有足够金额,则等待其他线程存款使银行有足够金额再取款System.out.println("金额不足,等待其他线程存款");wait();}}catch(InterruptedException e){e.printStackTrace();}int accoutBefore = account;account -= num;System.out.println("银行总帐:" + accoutBefore + " 取出: " + num + "  ,银行总帐:" + this.account);}
}

使用synchronized关键字来编写代码更简洁。

将静态方法声明为synchronized也是合法的。如果调用这种方法,该方法获得相关的类对象的内部锁。例如,如果Bank类有一个静态同步的方法,那么当该方法被调用时,Bank.class对象的锁被锁住。因此,没有其他线程可以调用同一个类的这个或任何其他的同步静态方法。

内部锁synchronized和内部条件局限:

不能中断一个正在试图获得锁的线程。

试图获得锁时不能设定超时。

每个锁仅有单一的条件,可能是不够的。

使用Lock和Condition或同步方法的建议:

最好既不使用Lock/Condition也不使用synchronized关键字。在许多情况下可以使用java.util.concurrent包中的一种机制,它会为你处理所有的加锁。

如果synchronized关键字适合你的程序,那么请尽量使用它,这样可以减少编写的代码数量,减少出错的机率。

如果特别需要Lock/Condition结构提供的独有特性时,才使用Lock/Condition。

死锁:见Java核心技术。

可以使用:

synchronized(this){

code

来锁定对象。

注意:

一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其

他线程的对象访问。

B.每个对象只有一个锁(lock)与之相关联。

C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

读/写锁

java.util.concurrent.locks包含定义读/写锁ReentrantReadWriteLock。如果很多线程从一个数据结构读取而很少线程修改其中数据的话,可以使用它。在这种情况下,允许对读者共享访问是合适的。当然,写者线程依然必须是互动访问。

1)构造 一个ReentrantReadWriteLock对象 :

private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

2)抽取读锁和写锁

private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();

3)对所有的访问者加读锁。

public double getTotalBalance(){
readLock.lock();
try{...}
finally{readLock.unlock()};
}

4)对所有的修改者加写锁。

public void transfer(...){
writeLock.lock();
try{ ... }
finally{writeLock.unlock()};
}

为什么弃用stop和suspend方法

stop方法终止所有未结束的方法,包括run方法。当线程被终止,立即释放被它锁住的所有对象的锁。这会导致对象处于不一致的状态。如,假定TransferThread在从一个账户向另一个账户转账的过程中被终止,钱款已经转出,却没有转入目标账户,现在银行对象就被破坏了。因为锁已经被释放,这种破坏会影响其他尚未停止的线程。

suspend不会破坏对象。但是,如果用suspend挂起一个持有一个锁的线程,那么,该锁在恢复之前是不可用的。如果调用suspend方法的线程试图获得同一个锁,那么程序死锁:被挂起的线程等着被恢复,而将其挂起的线程等待获得锁。

阻塞队列

对于实际编程,应该尽可能远离底层结构。使用由并发处理的专业人士实现的较高层次的结构要方便得多,安全得多。

对于许多线程问题,可以通过使用一个或多个队列以优雅且安全的方式将其形式化。生产者线程向队列插入元素,消费者线程则取出它们。使用队列,可以安全地从一个线程向另一个线程传递数据。如,考虑银行转账程序,转账线程将转账指令对象插入一个队列中,而不是直接访问银行对象,另一个线程从队列中取出指令执行转账。只有该线程可以访问该银行对象的内部。因此不需要同步。

当试图向队列添加元素而队列已满,或是想从队列移出元素而队列为空的时候,阻塞队列导致线程阻塞。在协调多个线程之间的合作时,阻塞队列是一个有用的工具。工作者线程可以周期性地将中间结果存储在阻塞队列中。其他的工作者线程移出中间结果并进一步加以修改。队列会自动地平衡负载。如果第一个线程集运行得比第二个慢,第二个线程集在等待结果时会阻塞。

如果将队列当作线程管理工具使用(如上提到的解决银行家问题的方法),要用put和take方法。当试图向满的队列中添加或空的队列中移出元素时,add,remove和element操作抛出异常。当然,在一个多线程程序中,队列会在任何时候空或满,困此,一定要使用offer,poll和peek方法作为替代。这些方法不能完成任务,只是给出一个错误提示而不会抛出异常。

注意:poll和peek方法返回空来指示失败。因此,向这些队列插入null值是非法的。

转载于:https://www.cnblogs.com/bin1991/p/3636630.html

java多线程 Java核心技术 读书笔记相关推荐

  1. 《Java多线程编程核心技术》读书笔记

    为什么80%的码农都做不了架构师?>>>    <Java多线程编程核心技术>读书笔记. ###第一章 Java多线程技能 使用Java多线程两种方式. 继承Thread ...

  2. 《Java编程思想》读书笔记 第十三章 字符串

    <Java编程思想>读书笔记 第十三章 字符串 不可变String String对象是不可变的,每一个看起来会修改String值的方法,实际上都是创建一个全新的String对象,以及包含修 ...

  3. java多线程编程同步方法_实践【Java多线程编程核心技术】系列:同步方法造成的无限等待...

    本文实践来自于[Java多线程编程核心技术]一书! 同步方法容易造成死循环,如-- 类Service.java: package service; public class Service { syn ...

  4. Think in Java第四版 读书笔记10 第16章 数组

    Think in Java第四版 读书笔记10 第16章 数组 数组和容器很像 但他们有一些差别 16.1 数组为什么特殊 数组与容器的区别主要在效率和存储类型 效率:数组是简单的线性序列 使得数组的 ...

  5. Think in Java第四版 读书笔记9第15章 泛型

    Think in Java第四版 读书笔记9第15章 泛型 泛型:适用于很多很多的类型 与其他语言相比 Java的泛型可能有许多局限 但是它还是有很多优点的. 本章介绍java泛型的局限和优势以及ja ...

  6. 《Java多线程编程核心技术》——1.5节sleep()方法

    本节书摘来自华章社区<Java多线程编程核心技术>一书中的第1章,第1.5节sleep()方法,作者高洪岩,更多章节内容可以访问云栖社区"华章社区"公众号查看 1.5 ...

  7. 《Java编程思想》读书笔记

    前言:三年之前就买了<Java编程思想>这本书,但是到现在为止都还没有好好看过这本书,这次希望能够坚持通读完整本书并整理好自己的读书笔记,上一篇文章是记录的第十七章到第十八章的内容,这一次 ...

  8. 深入分析Java Web技术内幕读书笔记(二)浅析DNS域名解析过程

    上一篇文章<浅析Web请求过程>讲述的是如何发起HTTP请求,对于请求发起过程中很重要的一个步骤--DNS解析过程的描述是一带而过,本篇文章将跟着DNS解析过程来分析域名是如何解析的. 一 ...

  9. 《Java多线程编程核心技术》读后感(十一)

    <Java多线程编程核心技术>读后感(十一) 方法join的使用 在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前结束.这时,如果主线 ...

  10. [转载]我的《java与模式》读书笔记

    我的<java与模式>读书笔记 中国是一个含蓄的民族,处对象一般都得通过一个介绍人. 这是一本阐述微观设计的书,而不是阐述宏观设计的书. <Java与模式>首先阐述了代码的设计 ...

最新文章

  1. Python zip() 函数
  2. Python 语法相关知识
  3. 023 判断出栈顺序是否正确
  4. 华为笔记本怎么激活windows_取代Windows?最美国产操作系统诞生,华为笔记本电脑已搭载...
  5. 基础: 一、Android环境搭建
  6. helm search搜索charts命令
  7. ubuntu如何杀死进程
  8. Stanford公开课《编译原理》学习笔记(2)递归下降法
  9. jupyter notebook 使用pip安装库,解决报错:zsh:1: command not found: pip
  10. Windows 会有开源的一天吗?
  11. asp.net c# 常见面试试题总结汇总(含答案)
  12. 【自我救赎--牛客网Top101 4天刷题计划】 第三天 渐入佳境
  13. extjs Ext.XTemplate
  14. 解析少儿编程与创客教育的实战原理
  15. antd 嵌套表格 没有子项隐藏图标
  16. C语言里栈和堆的区别整理
  17. JS生成 UUID的四种方法
  18. Django1.11.4 在前端显示图片
  19. 成年人的100个心酸瞬间:那些看似光鲜亮丽职业的背后......
  20. Hibernate学习—— 一级缓存快照

热门文章

  1. 快速浏览Silverlight3 beta:鸡肋一样的WritableBitmap
  2. 守护进程-----杀死自己的进程再重新启动自己
  3. 服务器端 viewstate
  4. 获取邮箱的DNS和MX 工具类
  5. html php插入百度地图定位
  6. CSS揭秘之多重边框连续的图像边框
  7. js 从一个json拼接成另一个json,并做json数据分页table展示
  8. CSS实现文字竖排效果
  9. QuantLib 金融计算——QauntLib 入门
  10. 欢迎来怼--第二十九次Scrum会议