Java多线程学习(吐血超详细总结)

2023-10-26

        写在前面的话:此文只能说是java多线程的一个入门,其实Java里头线程完全可以写一本书了,但是如果最基本的你都学掌握好,又怎么能更上一个台阶呢?如果你觉得此文很简单,那推荐你看看Java并发包的的线程池(Java并发编程与技术内幕:线程池深入理解),或者看这个专栏:Java并发编程与技术内幕。你将会对Java里头的高并发场景下的线程有更加深刻的理解。

目录(?)[-]

  1. 一扩展javalangThread类
  2. 二实现javalangRunnable接口
  3. 三Thread和Runnable的区别
  4. 四线程状态转换
  5. 五线程调度
  6. 六常用函数说明
    1. 使用方式
    2. 为什么要用join方法
  7. 七常见线程名词解释
  8. 八线程同步
  9. 九线程数据传递

        本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。在这之前,首先让我们来了解下在操作系统中进程和线程的区别:

  进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)

  线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)

  线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

  多进程是指操作系统能同时运行多个任务(程序)。

  多线程是指在同一程序中有多个顺序流在执行。

java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口.(其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用,此文这里不讲这个,有兴趣看这里Java并发编程与技术内幕:Callable、Future、FutureTask、CompletionService )

一、扩展java.lang.Thread类

这里继承Thread类的方法是比较常用的一种,如果说你只是想起一条线程。没有什么其它特殊的要求,那么可以使用Thread.(笔者推荐使用Runable,后头会说明为什么)。下面来看一个简单的实例

 

[java]  view plain  copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.multithread.learning;  
  2. /** 
  3.  *@functon 多线程学习 
  4.  *@author 林炳文 
  5.  *@time 2015.3.9 
  6.  */  
  7. class Thread1 extends Thread{  
  8.     private String name;  
  9.     public Thread1(String name) {  
  10.        this.name=name;  
  11.     }  
  12.     public void run() {  
  13.         for (int i = 0; i < 5; i++) {  
  14.             System.out.println(name + "运行  :  " + i);  
  15.             try {  
  16.                 sleep((int) Math.random() * 10);  
  17.             } catch (InterruptedException e) {  
  18.                 e.printStackTrace();  
  19.             }  
  20.         }  
  21.          
  22.     }  
  23. }  
  24. public class Main {  
  25.   
  26.     public static void main(String[] args) {  
  27.         Thread1 mTh1=new Thread1("A");  
  28.         Thread1 mTh2=new Thread1("B");  
  29.         mTh1.start();  
  30.         mTh2.start();  
  31.   
  32.     }  
  33.   
  34. }  

输出:

 

A运行  :  0
B运行  :  0
A运行  :  1
A运行  :  2
A运行  :  3
A运行  :  4
B运行  :  1
B运行  :  2
B运行  :  3
B运行  :  4

再运行一下:

A运行  :  0
B运行  :  0
B运行  :  1
B运行  :  2
B运行  :  3
B运行  :  4
A运行  :  1
A运行  :  2
A运行  :  3
A运行  :  4

说明:
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用MitiSay的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。
 
注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。
Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。

 

但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。

 

[java]  view plain  copy
 
 在CODE上查看代码片派生到我的代码片
  1. Thread1 mTh1=new Thread1("A");  
  2. Thread1 mTh2=mTh1;  
  3. mTh1.start();  
  4. mTh2.start();  


输出:

 

Exception in thread "main" java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Unknown Source)
    at com.multithread.learning.Main.main(Main.java:31)
A运行  :  0
A运行  :  1
A运行  :  2
A运行  :  3
A运行  :  4

二、实现java.lang.Runnable接口

采用Runnable也是非常常见的一种,我们只需要重写run方法即可。下面也来看个实例。

 

[java]  view plain  copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  *@functon 多线程学习 
  3.  *@author 林炳文 
  4.  *@time 2015.3.9 
  5.  */  
  6. package com.multithread.runnable;  
  7. class Thread2 implements Runnable{  
  8.     private String name;  
  9.   
  10.     public Thread2(String name) {  
  11.         this.name=name;  
  12.     }  
  13.   
  14.     @Override  
  15.     public void run() {  
  16.           for (int i = 0; i < 5; i++) {  
  17.                 System.out.println(name + "运行  :  " + i);  
  18.                 try {  
  19.                     Thread.sleep((int) Math.random() * 10);  
  20.                 } catch (InterruptedException e) {  
  21.                     e.printStackTrace();  
  22.                 }  
  23.             }  
  24.           
  25.     }  
  26.       
  27. }  
  28. public class Main {  
  29.   
  30.     public static void main(String[] args) {  
  31.         new Thread(new Thread2("C")).start();  
  32.         new Thread(new Thread2("D")).start();  
  33.     }  
  34.   
  35. }  

输出:

 

C运行  :  0
D运行  :  0
D运行  :  1
C运行  :  1
D运行  :  2
C运行  :  2
D运行  :  3
C运行  :  3
D运行  :  4
C运行  :  4

 

说明:
Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

三、Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

 

 

总结:

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

 

 

提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。

 

在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。

 

四、线程状态转换

下面的这个图非常重要!你如果看懂了这个图,那么对于多线程的理解将会更加深刻!

 

1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

五、线程调度

线程的调度

1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
 
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
static int MAX_PRIORITY
          线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
          线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
          分配给线程的默认优先级,取值为5。
 
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
 
每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
 
2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
 
3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
 
4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
 
5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
 
6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
 注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。

六、常用函数说明

①sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

②join():指等待t线程终止。

使用方式。

join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

 

[java]  view plain  copy
 
 在CODE上查看代码片派生到我的代码片
  1. Thread t = new AThread(); t.start(); t.join();  

 

为什么要用join()方法

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

不加join。
[java]  view plain  copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  *@functon 多线程学习,join 
  3.  *@author 林炳文 
  4.  *@time 2015.3.9 
  5.  */  
  6. package com.multithread.join;  
  7. class Thread1 extends Thread{  
  8.     private String name;  
  9.     public Thread1(String name) {  
  10.         super(name);  
  11.        this.name=name;  
  12.     }  
  13.     public void run() {  
  14.         System.out.println(Thread.currentThread().getName() + " 线程运行开始!");  
  15.         for (int i = 0; i < 5; i++) {  
  16.             System.out.println("子线程"+name + "运行 : " + i);  
  17.             try {  
  18.                 sleep((int) Math.random() * 10);  
  19.             } catch (InterruptedException e) {  
  20.                 e.printStackTrace();  
  21.             }  
  22.         }  
  23.         System.out.println(Thread.currentThread().getName() + " 线程运行结束!");  
  24.     }  
  25. }  
  26.   
  27. public class Main {  
  28.   
  29.     public static void main(String[] args) {  
  30.         System.out.println(Thread.currentThread().getName()+"主线程运行开始!");  
  31.         Thread1 mTh1=new Thread1("A");  
  32.         Thread1 mTh2=new Thread1("B");  
  33.         mTh1.start();  
  34.         mTh2.start();  
  35.         System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");  
  36.   
  37.     }  
  38.   
  39. }  
输出结果:
main主线程运行开始!
main主线程运行结束!
B 线程运行开始!
子线程B运行 : 0
A 线程运行开始!
子线程A运行 : 0
子线程B运行 : 1
子线程A运行 : 1
子线程A运行 : 2
子线程A运行 : 3
子线程A运行 : 4
A 线程运行结束!
子线程B运行 : 2
子线程B运行 : 3
子线程B运行 : 4
B 线程运行结束!
发现主线程比子线程早结束

加join
[java]  view plain  copy
 
 在CODE上查看代码片派生到我的代码片
  1. public class Main {  
  2.   
  3.     public static void main(String[] args) {  
  4.         System.out.println(Thread.currentThread().getName()+"主线程运行开始!");  
  5.         Thread1 mTh1=new Thread1("A");  
  6.         Thread1 mTh2=new Thread1("B");  
  7.         mTh1.start();  
  8.         mTh2.start();  
  9.         try {  
  10.             mTh1.join();  
  11.         } catch (InterruptedException e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.         try {  
  15.             mTh2.join();  
  16.         } catch (InterruptedException e) {  
  17.             e.printStackTrace();  
  18.         }  
  19.         System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");  
  20.   
  21.     }  
  22.   
  23. }  

运行结果:
main主线程运行开始!
A 线程运行开始!
子线程A运行 : 0
B 线程运行开始!
子线程B运行 : 0
子线程A运行 : 1
子线程B运行 : 1
子线程A运行 : 2
子线程B运行 : 2
子线程A运行 : 3
子线程B运行 : 3
子线程A运行 : 4
子线程B运行 : 4
A 线程运行结束!
主线程一定会等子线程都结束了才结束

③yield():暂停当前正在执行的线程对象,并执行其他线程。
        Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
          yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
 
结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。可看上面的图。
[cpp]  view plain  copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  *@functon 多线程学习 yield 
  3.  *@author 林炳文 
  4.  *@time 2015.3.9 
  5.  */  
  6. package com.multithread.yield;  
  7. class ThreadYield extends Thread{  
  8.     public ThreadYield(String name) {  
  9.         super(name);  
  10.     }  
  11.    
  12.     @Override  
  13.     public void run() {  
  14.         for (int i = 1; i <= 50; i++) {  
  15.             System.out.println("" + this.getName() + "-----" + i);  
  16.             // 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)  
  17.             if (i ==30) {  
  18.                 this.yield();  
  19.             }  
  20.         }  
  21.       
  22. }  
  23. }  
  24.   
  25. public class Main {  
  26.   
  27.     public static void main(String[] args) {  
  28.           
  29.         ThreadYield yt1 = new ThreadYield("张三");  
  30.         ThreadYield yt2 = new ThreadYield("李四");  
  31.         yt1.start();  
  32.         yt2.start();  
  33.     }  
  34.   
  35. }  

运行结果:

第一种情况:李四(线程)当执行到30时会CPU时间让掉,这时张三(线程)抢到CPU时间并执行。

第二种情况:李四(线程)当执行到30时会CPU时间让掉,这时李四(线程)抢到CPU时间并执行。

sleep()和yield()的区别
        sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
        sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU  的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
       另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield()  方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。 

④setPriority(): 更改线程的优先级。

    MIN_PRIORITY = 1
       NORM_PRIORITY = 5
           MAX_PRIORITY = 10

用法:
Thread4 t1 = new Thread4("t1");
Thread4 t2 = new Thread4("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);

⑤interrupt():不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!

⑥wait()

Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

    单单在概念上理解清楚了还不够,需要在实际的例子中进行测试才能更好的理解。对Object.wait(),Object.notify()的应用最经典的例子,应该是三线程打印ABC的问题了吧,这是一道比较经典的面试题,题目要求如下:

    建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:

 

[java]  view plain  copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * wait用法 
  3.  * @author DreamSea  
  4.  * @time 2015.3.9  
  5.  */  
  6. package com.multithread.wait;  
  7. public class MyThreadPrinter2 implements Runnable {     
  8.         
  9.     private String name;     
  10.     private Object prev;     
  11.     private Object self;     
  12.     
  13.     private MyThreadPrinter2(String name, Object prev, Object self) {     
  14.         this.name = name;     
  15.         this.prev = prev;     
  16.         this.self = self;     
  17.     }     
  18.     
  19.     @Override    
  20.     public void run() {     
  21.         int count = 10;     
  22.         while (count > 0) {     
  23.             synchronized (prev) {     
  24.                 synchronized (self) {     
  25.                     System.out.print(name);     
  26.                     count--;    
  27.                       
  28.                     self.notify();     
  29.                 }     
  30.                 try {     
  31.                     prev.wait();     
  32.                 } catch (InterruptedException e) {     
  33.                     e.printStackTrace();     
  34.                 }     
  35.             }     
  36.     
  37.         }     
  38.     }     
  39.     
  40.     public static void main(String[] args) throws Exception {     
  41.         Object a = new Object();     
  42.         Object b = new Object();     
  43.         Object c = new Object();     
  44.         MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);     
  45.         MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);     
  46.         MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);     
  47.              
  48.              
  49.         new Thread(pa).start();  
  50.         Thread.sleep(100);  //确保按顺序A、B、C执行  
  51.         new Thread(pb).start();  
  52.         Thread.sleep(100);    
  53.         new Thread(pc).start();     
  54.         Thread.sleep(100);    
  55.         }     
  56. }    

输出结果:

 

ABCABCABCABCABCABCABCABCABCABC

     先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序。
    wait和sleep区别
共同点: 

1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。 
2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。 
   如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。 
   需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。 
不同点: 
1. Thread类的方法:sleep(),yield()等 
   Object的方法:wait()和notify()等 
2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。 
   sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 
3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 
4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
所以sleep()和wait()方法的最大区别是:
    sleep()睡眠时,保持对象锁,仍然占有该锁;
    而wait()睡眠时,释放对象锁。
  但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。
sleep()方法
sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
   sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
  在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。 
wait()方法
wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;
  wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
  wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。

七、常见线程名词解释

主线程:JVM调用程序main()所产生的线程。
当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。
后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。 用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束
前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。

线程类的一些常用方法: 

  sleep(): 强迫一个线程睡眠N毫秒。 
  isAlive(): 判断一个线程是否存活。 
  join(): 等待线程终止。 
  activeCount(): 程序中活跃的线程数。 
  enumerate(): 枚举程序中的线程。 
    currentThread(): 得到当前线程。 
  isDaemon(): 一个线程是否为守护线程。 
  setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) 
  setName(): 为线程设置一个名称。 
  wait(): 强迫一个线程等待。 
  notify(): 通知一个线程继续运行。 
  setPriority(): 设置一个线程的优先级。

 

八、线程同步

1、synchronized关键字的作用域有二种: 
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法; 
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。 

2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象; 

3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法; 

 

Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized关键字的作用进行深入了解才可定论。

总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

在进一步阐述之前,我们需要明确几点:

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

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

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

 

接着来讨论synchronized用到不同地方对代码产生的影响:

 

假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。

 

1.  把synchronized当作函数修饰符时,示例代码如下:

Public synchronized void methodAAA()

{

//….

}

这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。

上边的示例代码等同于如下代码:

public void methodAAA()

{

synchronized (this)      //  (1)

{

       //…..

}

}

 (1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(

2.同步块,示例代码如下:

            public void method3(SomeObject so)

              {

                     synchronized(so)

{

       //…..

}

}

这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable

{

       private byte[] lock = new byte[0];  // 特殊的instance变量

    Public void methodA()

{

       synchronized(lock) { //… }

}

//…..

}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

3.将synchronized作用于static 函数,示例代码如下:

      Class Foo

{

public synchronized static void methodAAA()   // 同步的static 函数

{

//….

}

public void methodBBB()

{

       synchronized(Foo.class)   //  class literal(类名称字面常量)

}

       }

   代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。

可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

 

 

1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。

 

 

九、线程数据传递

在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。

9.1、通过构造方法传递数据 
在创建线程时,必须要建立一个Thread类的或其子类的实例。因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用类变量保存起来,以便线程使用(其实就是在run方法中使用)。下面的代码演示了如何通过构造方法来传递数据: 

 

[java]  view plain  copy
 
 在CODE上查看代码片派生到我的代码片
  1.    
  2. package mythread;   
  3. public class MyThread1 extends Thread   
  4. {   
  5. private String name;   
  6. public MyThread1(String name)   
  7. {   
  8. this.name = name;   
  9. }   
  10. public void run()   
  11. {   
  12. System.out.println("hello " + name);   
  13. }   
  14. public static void main(String[] args)   
  15. {   
  16. Thread thread = new MyThread1("world");   
  17. thread.start();   
  18. }   
  19. }   

由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。由于Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。 

 

9.2、通过变量和方法传递数据 
向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,另外一次机会就是在类中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋值。下面的代码是对MyThread1类的改版,使用了一个setName方法来设置 name变量: 

 

[java]  view plain  copy
 
 在CODE上查看代码片派生到我的代码片
  1.    
  2. package mythread;   
  3. public class MyThread2 implements Runnable   
  4. {   
  5. private String name;   
  6. public void setName(String name)   
  7. {   
  8. this.name = name;   
  9. }   
  10. public void run()   
  11. {   
  12. System.out.println("hello " + name);   
  13. }   
  14. public static void main(String[] args)   
  15. {   
  16. MyThread2 myThread = new MyThread2();   
  17. myThread.setName("world");   
  18. Thread thread = new Thread(myThread);   
  19. thread.start();   
  20. }   
  21. }   

9.3、通过回调函数传递数据 

 

上面讨论的两种向线程中传递数据的方法是最常用的。但这两种方法都是main方法中主动将数据传入线程类的。这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据,如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个 value是无法事先就传入线程类的。 

 

[java]  view plain  copy
 
 在CODE上查看代码片派生到我的代码片
  1.    
  2. package mythread;   
  3. class Data   
  4. {   
  5. public int value = 0;   
  6. }   
  7. class Work   
  8. {   
  9. public void process(Data data, Integer numbers)   
  10. {   
  11. for (int n : numbers)   
  12. {   
  13. data.value += n;   
  14. }   
  15. }   
  16. }   
  17. public class MyThread3 extends Thread   
  18. {   
  19. private Work work;   
  20. public MyThread3(Work work)   
  21. {   
  22. this.work = work;   
  23. }   
  24. public void run()   
  25. {   
  26. java.util.Random random = new java.util.Random();   
  27. Data data = new Data();   
  28. int n1 = random.nextInt(1000);   
  29. int n2 = random.nextInt(2000);   
  30. int n3 = random.nextInt(3000);   
  31. work.process(data, n1, n2, n3); // 使用回调函数   
  32. System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+"   
  33. + String.valueOf(n3) + "=" + data.value);   
  34. }   
  35. public static void main(String[] args)   
  36. {   
  37. Thread thread = new MyThread3(new Work());   
  38. thread.start();   
  39. }   
  40. }   

 

  好了,Java多线程的基础知识就讲到这里了,有兴趣研究多线程的推荐直接看java的源码,你将会得到很大的提升!

林炳文Evankaka原创作品。转载请注明出处http://blog.csdn.net/evankaka

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java多线程学习(吐血超详细总结) 的相关文章

  • Spring应用中Eureka健康检查的问题

    我正在开发一个基于 Spring 的应用程序 其中包含多个微服务 我的一个微服务充当尤里卡服务器 到目前为止一切正常 在我所有其他微服务中 用 EnableEurekaClient 我想启用这样的健康检查 应用程序 yml eureka c
  • 使用 LinkedList 实现下一个和上一个按钮

    这可能是一个愚蠢的问题 但我很难思考清楚 我编写了一个使用 LinkedList 来移动加载的 MIDI 乐器的方法 我想制作一个下一个和一个上一个按钮 以便每次单击该按钮时都会遍历 LinkedList 如果我硬编码itr next or
  • 如何使用assertEquals 和 Epsilon 在 JUnit 中断言两个双精度数?

    不推荐使用双打的assertEquals 我发现应该使用带有Epsilon的形式 这是因为双打不可能100 严格 但无论如何我需要比较两个双打 预期结果和实际结果 但我不知道该怎么做 目前我的测试如下 Test public void te
  • 在 Jar 文件中运行 ANT build.xml 文件

    我需要使用存储在 jar 文件中的 build xml 文件运行 ANT 构建 该 jar 文件在类路径中可用 是否可以在不分解 jar 文件并将 build xml 保存到本地目录的情况下做到这一点 如果是的话我该怎么办呢 Update
  • 如何更改javaFX中按钮的图像?

    我正在使用javaFX 我制作了一个按钮并为此设置了图像 代码是 Image playI new Image file c Users Farhad Desktop icons play2 jpg ImageView iv1 new Ima
  • 没有 Spring 的自定义 Prometheus 指标

    我需要为 Web 应用程序提供自定义指标 问题是我不能使用 Spring 但我必须使用 jax rs 端点 要求非常简单 想象一下 您有一个包含键值对的映射 其中键是指标名称 值是一个简单的整数 它是一个计数器 代码会是这样的 public
  • 将流转换为 IntStream

    我有一种感觉 我在这里错过了一些东西 我发现自己做了以下事情 private static int getHighestValue Map
  • 检测并缩短字符串中的所有网址

    假设我有一条字符串消息 您应该将 file zip 上传到http google com extremelylonglink zip http google com extremelylonglink zip not https stack
  • java.lang.IllegalStateException:提交响应后无法调用 sendRedirect()

    这两天我一直在尝试找出问题所在 我在这里读到我应该在代码中添加一个返回 我做到了 但我仍然得到 java lang IllegalStateException Cannot call sendRedirect after the respo
  • 在 junit 测试中获取 javax.lang.model.element.Element 类

    我想测试我的实用程序类 ElementUtils 但我不知道如何将类作为元素获取 在 AnnotationProcessors 中 我使用以下代码获取元素 Set
  • Java ResultSet 如何检查是否有结果

    结果集 http java sun com j2se 1 4 2 docs api java sql ResultSet html没有 hasNext 方法 我想检查 resultSet 是否有任何值 这是正确的方法吗 if resultS
  • 在我的 Spring Boot 示例中无法打开版本 3 中的 Swagger UI

    我在 Spring Boot 示例中打开 swagger ui 时遇到问题 当我访问 localhost 8080 swagger ui 或 localhost 8080 root api name swagger ui 时出现这种错误 S
  • Java 和 Python 可以在同一个应用程序中共存吗?

    我需要一个 Java 实例直接从 Python 实例数据存储中获取数据 我不知道这是否可能 数据存储是否透明 唯一 或者每个实例 如果它们确实可以共存 都有其单独的数据存储 总结一下 Java 应用程序如何从 Python 应用程序的数据存
  • 尝试将 Web 服务部署到 TomEE 时出现“找不到...的 appInfo”

    我有一个非常简单的项目 用于培训目的 它是一个 RESTful Web 服务 我使用 js css 和 html 创建了一个客户端 我正在尝试将该服务部署到 TomEE 这是我尝试部署时遇到的错误 我在这里做错了什么 刚刚遇到这个问题 我曾
  • 获取文件的总大小(以字节为单位)[重复]

    这个问题在这里已经有答案了 可能的重复 java 高效获取文件大小 https stackoverflow com questions 116574 java get file size efficiently 我有一个名为 filenam
  • 专门针对 JSP 的测试驱动开发

    在理解 TDD 到底是什么之前 我就已经开始编写测试驱动的代码了 在没有实现的情况下调用函数和类可以帮助我以更快 更有效的方式理解和构建我的应用程序 所以我非常习惯编写代码 gt 编译它 gt 看到它失败 gt 通过构建其实现来修复它的过程
  • Cucumber 0.4.3 (cuke4duke) 与 java + maven gem 问题

    我最近开始为 Cucumber 安装一个示例项目 并尝试使用 maven java 运行它 我遵循了这个指南 http www goodercode com wp using cucumber tests with maven and ja
  • Opencv Java 灰度

    我编写了以下程序 尝试从彩色转换为灰度 Mat newImage Imgcodecs imread q1 jpg Mat image new Mat new Size newImage cols newImage rows CvType C
  • 如何将双精度/浮点四舍五入为二进制精度?

    我正在编写对浮点数执行计算的代码的测试 不出所料 结果很少是准确的 我想在计算结果和预期结果之间设置一个容差 我已经证实 在实践中 使用双精度 在对最后两位有效小数进行四舍五入后 结果始终是正确的 但是usually四舍五入最后一位小数后
  • Spring Boot 无法更新 azure cosmos db(MongoDb) 上的分片集合

    我的数据库中存在一个集合 documentDev 其分片键为 dNumber 样本文件 id 12831221wadaee23 dNumber 115 processed false 如果我尝试使用以下命令通过任何查询工具更新此文档 db

随机推荐

  • 创建steam账户反复人机验证_您必须先通过人机验证才能创建steam帐户怎么办

    展开全部 在注册steam帐户遇到提示必须通过人机验证才能创建62616964757a686964616fe4b893e5b19e31333433643062提示时 勾选注册页面中的进行人机验证 在人机身份验证界面中点击需要的图片并按照步骤
  • socket接收报错

    首先是没得到正确错误号 是因为windows平台WSAGetLastError得过之后就没了 所以需要int变量保存一下 发现错误号后知道是最后一个参数没有初始化复制 bzero buf sizeof buf bzero cli adr s
  • PostgreSQL 多表关联删除

    用PostgreSQL数据库删除某个表数据 student 需要关联多个表 如classroom 作为条件 以下语句走不通 delete s from student s classroom c where s cid c id and s
  • JPA基本数据类型映射

    Employ author Administrator Entity Table name T EMPLOY SequenceGenerator name SEQ sequenceName SEQ SYS FUNC MENU initial
  • 利用MATLAB编写一段表格数据处理并作图

    使用MATLAB处理表格数据并作图可以使用以下步骤 读入表格数据 使用readtable或者xlsread函数读入Excel或者其他格式的表格数据 数据预处理 使用MATLAB的数组运算和统计函数对读入的数据进行预处理 包括清洗缺失值 去除
  • hadoopRPC的使用

    1模拟namenode的查询元数据 public interface ClientNamenodeProtocol public static final long versionID 1L 会读取这个版本号 但可以和客户端的不一样 没有校
  • 【数据结构】树(五)—— 二叉排序树(C语言版)

    数据结构 二叉排序树 C语言版 前言 一 二叉排序树的定义 二 二叉排序树的性质 三 二叉排序树的操作 1 二叉排序树常用存储结构 2 二叉排序树的查找 递归实现 查找 二叉树T 中键值为 key 的节点 非递归实现 查找 二叉树T 中键值
  • java中sum是什么_Java中的IntStream sum()方法

    sum Java中使用IntStream类的方法返回此流中元素的总和 语法如下 int sum 要使用Java中的IntStream类 请导入以下包 import java util stream IntStream 创建IntStream
  • 不知道华为手机识别图片文字怎么弄?2个识别方法收好了

    我们有时候会将书籍上的内容拍照下来进行抄写 但是面对大段的文字 手动抄写会比较花费时间 其实我们可以进行手机识别图片文字 一键进行文字内容提取 这样就方便多了 那你们知道华为手机识别图片文字怎么弄吗 下面我就来分享两个手机识别的方法给大家
  • python安装pip

    pip python2 安装 wget https bootstrap pypa io 2 6 get pip py python2 get pip py pip V pip3 python3 安装 wget https bootstrap
  • 攻防世界PWN新手练习区——cgpwn2

    攻防世界PWN新手练习区 cgpwn2 首先检查文件的有哪些保护 checksec cgpwn2 32位程序 用IDA反编译文件 main函数中只有hello函数 点击查看 整个函数看起来是在进行某种算法 但关键点在于return gets
  • 计算机的起源与发展(概述+习题)

    概述 计算机 computer 也称为 电脑 是一种具有计算功能 记忆功能和逻辑判断功能的机器设备 它能接收数据 保存数据 按照预定的程序对数据进行处理 并提供和保存处理结果 计算机的起源 图灵机 图灵机是一种抽象的计算模型 并没有真正地生
  • H3CNE综合实验

    H3CNE综合实验 实验拓扑 实验需求 按照图示配置IP地址 SW1和SW2之间的直连链路配置链路聚合 公司内部业务网段为Vlan10和Vlan20 Vlan10是市场部 Vlan20是技术部 要求对Vlan 进行命名以便识别 PC1属于V
  • python实现星空效果

    以前用DEV C 这个编译器时 看过easyx 这个C 图形库 文档中有范例程序 是模拟星空的 觉得的不错 今天有空 把Crossin用python写的一个雪花模拟程序 修改了一下 效果和easyx的基本一模一样 使用easyx的 代码 编
  • GMT、UTC与24时区 等时间概念

    许多人都知道两地时间表简称为GMT或UTC 而世界时区表则通称为World Time 那么GMT与UTC的实质原意又是为何 世界时区又是怎么区分的 面盘上密密麻麻的英文单字代表着什么意义与作用呢 这些都是新手在接触两地时间表或世界时区表时
  • java 实现删除单链表中所有指定的结点以及如何清空单链表

    文章目录 1 删除单链表中的所有的指定结点 1 1 删除思路 1 2 删除步骤 1 2 1 删除结点不是头结点 1 2 2 删除的结点是头结点的情况 1 3 部分代码思路分析 1 4 整体代码演示 2 清空单链表 1 删除单链表中的所有的指
  • 第九章 接口(上)

    第九章 接口 上 接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法 9 1 抽象类和抽象方法 包含抽象方法的类叫做抽象类 如果一个类包含一个或多个抽象方法 该类必须被限定为抽象的 否则 编译器就会报错 抽象类和抽象方法都用ab
  • 计算机组成原理:奇偶校验和汉明码校验

    目录 一 奇偶校验 奇偶校验的规律及原理 二 汉明校验码 1 校验位位置 2 汉明码的位号实质上是参与校验的各校验位位号之和 3 计算校验位的值 4 校验 设置指错字 一 奇偶校验 了解汉明码校验之前需要知道奇偶校验 奇偶校验码是一种最简单
  • JS翻转数组

    js翻转数组 reverse 方法翻转 反向添加数组 数组首尾交换 unshift 向数组头部添加 考点 在 数组首尾交换 reverse 方法肯定不是 reverse 是js方法 反向添加数组 和 unshift 向数组头部添加元素 创建
  • Java多线程学习(吐血超详细总结)

    写在前面的话 此文只能说是java多线程的一个入门 其实Java里头线程完全可以写一本书了 但是如果最基本的你都学掌握好 又怎么能更上一个台阶呢 如果你觉得此文很简单 那推荐你看看Java并发包的的线程池 Java并发编程与技术内幕 线程池