Java多线程面试题归纳

多线程面试一

1、多线程有哪几种实现方法?举个例子说明下线程的同步。

(1)Java多线程有两种实现方式:继承Thread类和实现Runnable接口,Thread就是实现了Runnable接口。

两个最简单的线程例子:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package chc.runnable;  
  2.   
  3. public class ThreadTest2 {  
  4.     public static void main(String[] args) throws InterruptedException {  
  5.         Thread1 t = new Thread1();  
  6.         //t.run(); //这里也不能直接调用方法  
  7.         t.start();  
  8.         for (int i = 0; i < 100; i++) {  
  9.             System.out.println("main:"+i);  
  10.         }  
  11.     }  
  12. }  
  13.   
  14. class Thread1 extends Thread{  
  15.     public void run() {  
  16.         for (int i = 0; i < 100; i++) {  
  17.             System.out.println("Thread-----:"+i);  
  18.         }  
  19.     }  
  20. }  
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package chc.runnable;  
  2.   
  3. public class ThreadTest1 {  
  4.     public static void main(String[] args) {  
  5.         Runnable1 r =new Runnable1();  
  6.         Thread t1=new Thread(r);  
  7.         t1.start();  
  8.     }  
  9. }  
  10.   
  11. class Runnable1  implements Runnable {  
  12.   
  13.     public void run() {  
  14.         // TODO Auto-generated method stub  
  15.         for (int i = 1; i <= 5; i++) {  
  16.             System.out.println("实现Runnable接口的线程----->"+i);  
  17.         }  
  18.     }  
  19.   
  20. }  

通过上两个例子发现,当启动线程的时候并不影响主程序的继续执行。

(2)线程同步问题

使用synchronnized关键字

银行账户存取钱的问题:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class ThreadTest {  
  2.     public static void main(String[] args){  
  3.         ThreadA  t1=new ThreadA();  
  4.         t1.start();  
  5.         ThreadB  t2=new ThreadB();  
  6.         t2.start();  
  7.     }  
  8. }  
  9.   
  10. class ThreadA extends Thread{  
  11.     @Override  
  12.     public void run(){  
  13.         for(int i=0;i<100;i++){  
  14.             account.add();  
  15.             try {  
  16.                 sleep(10);//模拟银行系统处理时间  
  17.             } catch (InterruptedException e) {  
  18.                 // TODO Auto-generated catch block  
  19.                 e.printStackTrace();  
  20.             }  
  21.         }  
  22.     }  
  23. }  
  24.   
  25. class ThreadB extends Thread{  
  26.     @Override  
  27.     public void run() {  
  28.         for(int i=0;i<100;i++){  
  29.             account.remove();  
  30.             try {  
  31.                 sleep(10);<span style="font-family: SimSun;">//模拟银行系统处理时间</span>  
  32.             } catch (InterruptedException e) {  
  33.                 // TODO Auto-generated catch block  
  34.                 e.printStackTrace();  
  35.             }  
  36.         }  
  37.     }  
  38. }  
  39.   
  40. class account {  
  41.     public static   int count=1000;  
  42.       
  43.     //减去100元  
  44.     public static synchronized void remove(){  
  45.         count=count-100;  
  46.         System.out.println("减去100元,卡内余额"+count);  
  47.     }  
  48.       
  49.     //增加100元  
  50.     public static synchronized void add(){  
  51.         count=count+100;  
  52.         System.out.println("加上100元,卡内余额"+count);  
  53.     }  
  54. }  
注意:上例中将account类的add()和remove()方法都加上了static关键字,因为这样在线程中可以直接通过类名调用到这两个方法,而不需要实例化对象。

因为synchronized同步只对同一个对象中的方法有效,也就是说一个线程正在执行account的add()方法,另一个线程是可以调用到另一个对象的add方法的。

2、启动一个线程是用run()还是start(),调用的时候有什么区别?

当然是start()了,当调用线程的start()方法的时候,线程就会进入到就绪状态

run()方法是线程的执行入口,当线程从就绪状态进入到执行状态时首先要从run()方法开始执行。

当然,我们也是可以直接通过线程对象调用该对象的run()方法的,只是这只是一次普通的调用,并没有启动任何一个线程。

当我们调用start()方法时,是另外启动了一个线程去执行线程类的代码,并不影响主程序的执行,但是调用run()方法的时候要等待run()方法内的代码执

行完主程序才可以向下执行,举个例子:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class ThreadDemo2 {  
  2.     public static void main(String[] args) {  
  3.         Thread5 t1=new Thread5();  
  4.         Thread6 t2=new Thread6();  
  5.         t1.run();  
  6.         t2.start();  
  7.         for(int i=0;i<100;i++){  
  8.             System.out.println("主进程执行:"+i);  
  9.         }  
  10.     }  
  11. }  
  12.   
  13. class Thread5 extends Thread{  
  14.     @Override  
  15.     public void run() {  
  16.         for(int i=0;i<100;i++){  
  17.             System.out.println("Thread5执行:"+i);  
  18.         }  
  19.     }  
  20. }  
  21.   
  22. class Thread6 extends Thread{  
  23.     @Override  
  24.     public void run() {  
  25.         for(int i=0;i<100;i++){  
  26.             System.out.println("Thread6执行:"+i);  
  27.         }  
  28.     }  
  29. }  
输出结果的顺序是:Thread5全部打印完后 Thread6和主程序交替打印。验证了上面的说法

3、当一个线程进入到一个对象的synchronized方法,那么其他线程是否可以进入该对象的其它方法?

不一定,看情况

如果其它方法加了static关键字,那么该方法属于类,不属于对象,不能与对象的方法保持同步(即使有synchronized关键字),是能进入的。

如果其它方法不带有static关键字且带有synchronized关键字,那么不能进入,如果不带,则能。

再其次就看方法内部有没有wait()方法释放锁了

4、子线程循环2次,接着主线程循环3次,接着子线程循环3次,接着主线程循环3次,如此循环5次,请写出程序代码。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class ThreadDemo5 {  
  2.     static boolean thread_flag=false;//指示子线程循环是否结束  
  3.     static boolean main_flag=true;   //调用子线程的start方法后变为true,循环等待thread_flag为true(也就是子线程循环完)的时刻,主线程循环完又变为false;  
  4.     public static void main(String[] args) {  
  5.         for(int k=0;k<5;k++){  
  6.             Thread7 t=new Thread7();  
  7.             t.start();  
  8.             main_flag=true;  
  9.             while (main_flag) {//循环等待thread_f  
  10.                 if(thread_flag){  
  11.                     for(int i=0;i<3;i++){  
  12.                         System.out.println("主线程第一次循环"+(i+1)+"次");  
  13.                     }  
  14.                     thread_flag=false;  
  15.                     main_flag=false;  
  16.                 }     
  17.             }  
  18.         }  
  19.     }  
  20. }  
  21.   
  22. class Thread7 extends Thread {  
  23.     static boolean flag=true;//标志子线程第几次循环,当值为true的时候子线程循环2次,否则循环3次  
  24.     public void run() {  
  25.         if(flag){  
  26.             for(int i=0;i<2;i++){  
  27.                 System.out.println("子线程第一次循环"+(i+1)+"次");  
  28.             }  
  29.             flag=false;  
  30.         }else{  
  31.             for(int i=0;i<3;i++){  
  32.                 System.out.println("子线程第二次循环"+(i+1)+"次");  
  33.             }  
  34.             flag=true;  
  35.         }  
  36.         ThreadDemo5.thread_flag=true;  
  37.           
  38.           
  39.     }  
  40. }  

这个题就是要注意调用子线程的start方法的时候并不能阻止主程序继续向下执行,所以我们要用变量来标记。

5、sleep()和wait()有何异同?

(1)首先一个最明显的区别是  wait是Object类的方法,而sleep()是Thread类的静态方法,谁调用了该方法谁去休眠,即使在a线程里调用了b线程的sleep方法,实际上还是a线程去休眠.

(2)比较重要的一点是sleep没有释放出锁,而wait释放了锁,是其他线程可以使用同步块资源。

     sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要

     等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到

     只能调用interrupt()强行打断。

(3)使用范围:

     wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 
     synchronized(x){ 
       x.notify() 
       //或者wait() 
      }

(4)sleep需要捕获异常,而wait不需要。

6、现在有T1 T2 T3三个线程,怎样保证T2在T1执行完之后执行 T3在T2执行完之后执行

这题主要是考察对join()方法的使用。

当线程A当中执行了线程B.join(),那么A线程要等待B线程执行完才可以执行。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class JoinDemo {  
  2.     public static void main(String[] args) {  
  3.         T1 t1=new T1("T1");  
  4.         T2 t2=new T2("T2");  
  5.         T3 t3=new T3("T3");  
  6.         t1.start();  
  7.         try {  
  8.             t1.join();  
  9.         } catch (InterruptedException e) {  
  10.             // TODO Auto-generated catch block  
  11.             e.printStackTrace();  
  12.         }  
  13.         t2.start();  
  14.         try {  
  15.             t2.join();  
  16.         } catch (InterruptedException e) {  
  17.             // TODO Auto-generated catch block  
  18.             e.printStackTrace();  
  19.         }  
  20.         t3.start();  
  21.     }  
  22. }  
  23.   
  24. class T1 extends  Thread{  
  25.     private String name;  
  26.     public T1(String name) {  
  27.         this.name=name;  
  28.     }  
  29.   
  30.     @Override  
  31.     public void run() {  
  32.         for(int i=0;i<5;i++){  
  33.             try {  
  34.                 sleep(5);  
  35.             } catch (InterruptedException e) {  
  36.                 // TODO Auto-generated catch block  
  37.                 e.printStackTrace();  
  38.             }  
  39.             System.out.println(this.name+"循环"+i);  
  40.         }  
  41.     }  
  42. }  
  43. class T2 extends  Thread{  
  44.     private String name;  
  45.     public T2(String name) {  
  46.         this.name=name;  
  47.     }  
  48.     @Override  
  49.     public void run() {  
  50.         for(int i=0;i<5;i++){  
  51.             try {  
  52.                 sleep(5);  
  53.             } catch (InterruptedException e) {  
  54.                 // TODO Auto-generated catch block  
  55.                 e.printStackTrace();  
  56.             }  
  57.             System.out.println(this.name+"循环"+i);  
  58.         }  
  59.     }  
  60. }  
  61. class T3 extends  Thread{  
  62.     private String name;  
  63.     public T3(String name) {  
  64.         this.name=name;  
  65.     }  
  66.     @Override  
  67.     public void run() {  
  68.         for(int i=0;i<5;i++){  
  69.             System.out.println(this.name+"循环"+i);  
  70.         }  
  71.     }  
  72. }  

7、简述synchronized和java.util.concurrent.locks.Lock的异同?

主要相同点:Lock能完成synchronized所实现的所有功能

主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。

Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。

8、死锁问题

后续补充……


多线程面试二


0.Java 中多线程同步是什么? 

在多线程程序下,同步能控制对共享资源的访问。如果没有同步,当一个 Java 线程在修改一个共享变量时,另外一个线程正在使用或者更新同一个变量,这样容易导致程序出现错误的结果。 

1.解释实现多线程的几种方法? 

一 Java 线程可以实现 Runnable 接口或者继承 Thread 类来实现,当你打算多重继承时,优先选择实现 Runnable。 

2.Thread.start ()与 Thread.run ()有什么区别? 

Thread.start ()方法(native)启动线程,使之进入就绪状态,当 cpu 分配时间该线程时,由 JVM 调度执行 run ()方法。 

3.为什么需要 run ()和 start ()方法,我们可以只用 run ()方法来完成任务吗? 

我们需要 run ()&start ()这两个方法是因为 JVM 创建一个单独的线程不同于普通方法的调用,所以这项工作由线程的 start 方法来完成,start 由本地方法实现,需要显示地被调用,使用这俩个方法的另外一个好处是任何一个对象都可以作为线程运行,只要实现了 Runnable 接口,这就避免因继承了 Thread 类而造成的 Java 的多继承问题。 

4.什么是 ThreadLocal 类,怎么使用它? 

ThreadLocal 是一个线程级别的局部变量,并非“本地线程”。ThreadLocal 为每个使用该变量的线程提供了一个独立的变量副本,每个线程修改副本时不影响其它线程对象的副本(译者注)。 

下面是线程局部变量(ThreadLocal variables)的关键点: 

一个线程局部变量(ThreadLocal variables)为每个线程方便地提供了一个单独的变量。 

ThreadLocal 实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。 

当多个线程访问 ThreadLocal 实例时,每个线程维护 ThreadLocal 提供的独立的变量副本。 

常用的使用可在 DAO 模式中见到,当 DAO 类作为一个单例类时,数据库链接(connection)被每一个线程独立的维护,互不影响。(基于线程的单例) 

ThreadLocal 难于理解,下面这些引用连接有助于你更好的理解它。 

《Good article on ThreadLocal on IBM DeveloperWorks 》、《理解 ThreadLocal》、《Managing data : Good example》、《Refer Java API Docs》 

5.什么时候抛出 InvalidMonitorStateException 异常,为什么? 

调用 wait ()/notify ()/notifyAll ()中的任何一个方法时,如果当前线程没有获得该对象的锁,那么就会抛出 IllegalMonitorStateException 的异常(也就是说程序在没有执行对象的任何同步块或者同步方法时,仍然尝试调用 wait ()/notify ()/notifyAll ()时)。由于该异常是 RuntimeExcpetion 的子类,所以该异常不一定要捕获(尽管你可以捕获只要你愿意).作为 RuntimeException,此类异常不会在 wait (),notify (),notifyAll ()的方法签名提及。 

6.Sleep ()、suspend ()和 wait ()之间有什么区别? 

Thread.sleep ()使当前线程在指定的时间处于“非运行”(Not Runnable)状态。线程一直持有对象的监视器。比如一个线程当前在一个同步块或同步方法中,其它线程不能进入该块或方法中。如果另一线程调用了 interrupt ()方法,它将唤醒那个“睡眠的”线程。 

注意:sleep ()是一个静态方法。这意味着只对当前线程有效,一个常见的错误是调用t.sleep (),(这里的t是一个不同于当前线程的线程)。即便是执行t.sleep (),也是当前线程进入睡眠,而不是t线程。t.suspend ()是过时的方法,使用 suspend ()导致线程进入停滞状态,该线程会一直持有对象的监视器,suspend ()容易引起死锁问题。 

object.wait ()使当前线程出于“不可运行”状态,和 sleep ()不同的是 wait 是 object 的方法而不是 thread。调用 object.wait ()时,线程先要获取这个对象的对象锁,当前线程必须在锁对象保持同步,把当前线程添加到等待队列中,随后另一线程可以同步同一个对象锁来调用 object.notify (),这样将唤醒原来等待中的线程,然后释放该锁。基本上 wait ()/notify ()与 sleep ()/interrupt ()类似,只是前者需要获取对象锁。 

7.在静态方法上使用同步时会发生什么事? 

同步静态方法时会获取该类的“Class”对象,所以当一个线程进入同步的静态方法中时,线程监视器获取类本身的对象锁,其它线程不能进入这个类的任何静态同步方法。它不像实例方法,因为多个线程可以同时访问不同实例同步实例方法。 

8.当一个同步方法已经执行,线程能够调用对象上的非同步实例方法吗? 

可以,一个非同步方法总是可以被调用而不会有任何问题。实际上,Java 没有为非同步方法做任何检查,锁对象仅仅在同步方法或者同步代码块中检查。如果一个方法没有声明为同步,即使你在使用共享数据 Java 照样会调用,而不会做检查是否安全,所以在这种情况下要特别小心。一个方法是否声明为同步取决于临界区访问(critial section access),如果方法不访问临界区(共享资源或者数据结构)就没必要声明为同步的。 

下面有一个示例说明:Common 类有两个方法 synchronizedMethod1()和 method1(),MyThread 类在独立的线程中调用这两个方法。 
Java代码   收藏代码
  1. public class Common {  
  2.   
  3. public synchronized void synchronizedMethod1() {  
  4. System.out.println (“synchronizedMethod1 called”);  
  5. try {  
  6. Thread.sleep (1000);  
  7. catch (InterruptedException e) {  
  8. e.printStackTrace ();  
  9. }  
  10. System.out.println (“synchronizedMethod1 done”);  
  11. }  
  12. public void method1() {  
  13. System.out.println (“Method 1 called”);  
  14. try {  
  15. Thread.sleep (1000);  
  16. catch (InterruptedException e) {  
  17. e.printStackTrace ();  
  18. }  
  19. System.out.println (“Method 1 done”);  
  20. }  
  21. }  
  22. public class MyThread extends Thread {  
  23. private int id = 0;  
  24. private Common common;  
  25.   
  26. public MyThread (String name, int no, Common object) {  
  27. super(name);  
  28. common = object;  
  29. id = no;  
  30. }  
  31.   
  32. public void run () {  
  33. System.out.println (“Running Thread” + this.getName ());  
  34. try {  
  35. if (id == 0) {  
  36. common.synchronizedMethod1();  
  37. else {  
  38. common.method1();  
  39. }  
  40. catch (Exception e) {  
  41. e.printStackTrace ();  
  42. }  
  43. }  
  44.   
  45. public static void main (String[] args) {  
  46. Common c = new Common ();  
  47. MyThread t1 = new MyThread (“MyThread-1″, 0, c);  
  48. MyThread t2 = new MyThread (“MyThread-2″, 1, c);  
  49. t1.start ();  
  50. t2.start ();  
  51. }  
  52. }  

这里是程序的输出: 

Java代码   收藏代码
  1. Running ThreadMyThread-1  
  2. synchronizedMethod1 called  
  3. Running ThreadMyThread-2  
  4. Method 1 called  
  5. synchronizedMethod1 done  
  6. Method 1 done  


结果表明即使 synchronizedMethod1()方法执行了,method1()也会被调用。 

9.在一个对象上两个线程可以调用两个不同的同步实例方法吗? 

不能,因为一个对象已经同步了实例方法,线程获取了对象的对象锁。所以只有执行完该方法释放对象锁后才能执行其它同步方法。看下面代码示例非常清晰:Common 类有 synchronizedMethod1()和 synchronizedMethod2()方法,MyThread 调用这两个方法。 
Java代码   收藏代码
  1. public class Common {  
  2. public synchronized void synchronizedMethod1() {  
  3. System.out.println (“synchronizedMethod1 called”);  
  4. try {  
  5. Thread.sleep (1000);  
  6. catch (InterruptedException e) {  
  7. e.printStackTrace ();  
  8. }  
  9. System.out.println (“synchronizedMethod1 done”);  
  10. }  
  11.   
  12. public synchronized void synchronizedMethod2() {  
  13. System.out.println (“synchronizedMethod2 called”);  
  14. try {  
  15. Thread.sleep (1000);  
  16. catch (InterruptedException e) {  
  17. e.printStackTrace ();  
  18. }  
  19. System.out.println (“synchronizedMethod2 done”);  
  20. }  
  21. }  
  22. public class MyThread extends Thread {  
  23. private int id = 0;  
  24. private Common common;  
  25.   
  26. public MyThread (String name, int no, Common object) {  
  27. super(name);  
  28. common = object;  
  29. id = no;  
  30. }  
  31.   
  32. public void run () {  
  33. System.out.println (“Running Thread” + this.getName ());  
  34. try {  
  35. if (id == 0) {  
  36. common.synchronizedMethod1();  
  37. else {  
  38. common.synchronizedMethod2();  
  39. }  
  40. catch (Exception e) {  
  41. e.printStackTrace ();  
  42. }  
  43. }  
  44.   
  45. public static void main (String[] args) {  
  46. Common c = new Common ();  
  47. MyThread t1 = new MyThread (“MyThread-1″, 0, c);  
  48. MyThread t2 = new MyThread (“MyThread-2″, 1, c);  
  49. t1.start ();  
  50. t2.start ();  
  51. }  
  52. }  

10.什么是死锁 

死锁就是两个或两个以上的线程被无限的阻塞,线程之间相互等待所需资源。这种情况可能发生在当两个线程尝试获取其它资源的锁,而每个线程又陷入无限等待其它资源锁的释放,除非一个用户进程被终止。就 JavaAPI 而言,线程死锁可能发生在一下情况。 

当两个线程相互调用 Thread.join () 
当两个线程使用嵌套的同步块,一个线程占用了另外一个线程必需的锁,互相等待时被阻塞就有可能出现死锁。 
11.什么是线程饿死,什么是活锁? 

线程饿死和活锁虽然不想是死锁一样的常见问题,但是对于并发编程的设计者来说就像一次邂逅一样。 

当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。JavaAPI 中线程活锁可能发生在以下情形: 

当所有线程在程序中执行 Object.wait (0),参数为 0 的 wait 方法。程序将发生活锁直到在相应的对象上有线程调用 Object.notify ()或者 Object.notifyAll ()。 
当所有线程卡在无限循环中。Java程序员面试中的多线程问题


Java面试三


java线程面试题

1.实现线程的方法,有什么区别
 继承Thread与实现Runnable接口。
 启动方法不一样。Thread1继承,Thread2实现Runnable接口,则启动一

个Thread1线程可以使用new Thread1().start(),而启动Thread2线程则new

Thread(new Thread2()).start()。
 
2.可以使用run方法启动一个线程吗?
 启动一个线程应该使用start方法,线程的run方法可以直接调用,但是

不会启动一个新的线程,只是在原来的线程中调用了run方法而已。

3.sleep方法与wait方法的区别,带时间参数时有什么区别?
 sleep()是线程的静态方法,使当前线程(即调用该方法的线程)暂停执

行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是如果有

Synchronized同步块,其他线程仍然不可以访问共享数据。
 wait()方法使当前线程暂停执行并释放对象锁标志,让其他线程可以进

入Synchronized数据块,当前线程被放入对象等待池中。对象。

4.join() ,join(long mills)
join()方法使调用该方法的线程在此之前执行完毕,也就是等待调用该方法的线

程执行完毕后再往下继续执行。注意该方法也要捕获异常。join(mills)最长等

待mills时间。

5.wait(),wait(long timeout)和notify()、notifyAll() 
 这三个方法用于协调多个线程对共享数据的存取,所以必须在

Synchronized语句块内使用这三个方法。Synchronized这个关键字用于保护共享

数据,阻止其他线程对共享数据的存取。 
 wait()方法使当前线程暂停执行并释放对象锁标志,让其他线程可以进

入Synchronized数据块,当前线程被放入对象等待池中。
 wait(timeout)等待timeout长的时间,如果这段时间过了还没有被唤醒

,则自动唤醒。
 当调用 notify()方法后,将从对象的等待池中移走一个任意的线程并

放到锁标志等待池中,只有 锁标志等待池中的线程能够获取锁标志;如果锁标

志等待池中没有线程,则notify()不起作用。 
 notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁

标志等待池中。 
 注意 这三个方法都是java.lang.Ojbect的方法!

6.yield() 
 yield()方法暂停当前线程的执行,其它的线程有执行的机会

7.当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
 分情况而定。假设两个方法test()与test2()是一个类的两个方法,test是这个类的对象,现在thread1访问test()而thread2访问test2()方法。
若test与test2中没有访问相同的对象,则可以,如果test与test2访问了相同的对象,则不可以。
 这里是要明白synchronized锁定的是什么。synchronized块锁定的是它后面的括号里面的内容,而synchronized方法锁定的是该方法中访问的所有的对象。
----------------------------------------------------------------------
网上面试题摘录

60、java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用?
 答:有两种实现方法,分别是继承Thread类与实现Runnable接口。用synchronized关键字修饰同步方法
 反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。

61、sleep() 和 wait() 有什么区别? 
 答:sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
 wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

62、同步和异步有何异同,在什么情况下分别使用他们?举例说明。
 答:如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。
 当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

63、启动一个线程是用run()还是start()?
 答:启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。
 
64、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
 答:不能,一个对象的一个synchronized方法只能由一个线程访问。
 64网上的答案有点答非所问。

65、请说出你所知道的线程同步的方法。
 答:wait():使一个线程处于等待状态,并且释放所持有的对象的lock。sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
 notifyAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

66、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么? 
 答:多线程有两种实现方法,分别是继承Thread类与实现Runnable接口 
同步的实现方面有两种,分别是synchronized,wait与notify

67、线程的基本概念、线程的基本状态以及状态之间的关系
 答:线程指在程序执行过程中,能够执行程序代码的一个执行单位,每个程序至少都有一个线程,也就是程序本身。Java中的线程有四种状态分别是:运行、就绪、挂起、结束

68、简述synchronized和java.util.concurrent.locks.Lock的异同 ?
 答:主要相同点:Lock能完成synchronized所实现的所有功能
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。



面试解惑之  多线程


1,多线程 
线程或者说多线程,是我们处理多任务的强大工具。 
线程与进程的区别:[/size] 
线程和进程是不同的,每个进程都是一个独立运行的程序,拥有自己的变量,且不同进程间的变量不能共享;而线程是运行在进程内部的,每个正在运行的进程至少有一个线程,而且不同的线程之间可以在进程范围内共享数据。也就是说进程有自己独立的存储空间,而线程是和它所属的进程内的其他线程共享一个存储空间。线程的使用可以使我们能够并行地处理一些事情。线程通过并行的处理给用户带来更好的使用体验,比如你使用的邮件系统(outlook、Thunderbird、foxmail等),你当然不希望它们在收取新邮件的时候,导致你连已经收下来的邮件都无法阅读,而只能等待收取邮件操作执行完毕。这正是线程的意义所在。 
java中有两中方式实现多线程,一种是继承Thread类,一种实现Runnable接口线程启动是用start方法,而非run方法。 
线程状态的具体信息如下: 
1. NEW(新建状态、初始化状态):线程对象已经被创建,但是还没有被启动时的状态。 
这段时间就是在我们调用new命令之后,调用start()方法之前。 
2. RUNNABLE(可运行状态、就绪状态):在我们调用了线程的start()方法之后线程所 
处的状态。处于RUNNABLE状态的线程在JAVA虚拟机(JVM)上是运行着的,但是它可 
能还正在等待操作系统分配给它相应的运行资源以得以运行。 
3. BLOCKED(阻塞状态、被中断运行):线程正在等待其它的线程释放同步锁,以进 
入一个同步块或者同步方法继续运行;或者它已经进入了某个同步块或同步方法,在运行的 
过程中它调用了某个对象继承自java.lang.Object的wait()方法,正在等待重新返回这个同步块或同步方法。 
4. WAITING(等待状态):当前线程调用了java.lang.Object.wait()、 
java.lang.Thread.join()或者java.util.concurrent.locks.LockSupport.park()三个中的任意一个方法, 
正在等待另外一个线程执行某个操作。比如一个线程调用了某个对象的wait()方法,正在等 
待其它线程调用这个对象的notify() 或者notifyAll()(这两个方法同样是继承自Object类)方 
法来唤醒它;或者一个线程调用了另一个线程的join()(这个方法属于 Thread类)方法,正在等待这个方法运行结束。 
5. TIMED_WAITING(定时等待状态):当前线程调用了 java.lang.Object.wait(long 
timeout)、java.lang.Thread.join(long 
millis)、java.util.concurrent.locks.LockSupport.packNanos(long 
nanos)、java.util.concurrent.locks.LockSupport.packUntil(long deadline)四个方法中的任意一个, 
进入等待状态,但是与WAITING状态不同的是,它有一个最大等待时间,即使等待的条件 
仍然没有满足,只要到了这个时间它就会自动醒来。 
6. TERMINATED(死亡状态、终止状态):线程完成执行后的状态。线程执行完run()方 
法中的全部代码,从该方法中退出,进入TERMINATED状态。还有一种情况是run()在运行 
过程中抛出了一个异常,而这个异常没有被程序捕获,导致这个线程异常终止进入 
TERMINATED状态。 
在Java5.0及以上版本中,线程的全部六种状态都以枚举类型的形式定义在java.lang.Thread 
类中了,代码如下:
 

Java代码  复制代码  收藏代码
  1. public enum State {   
  2. NEW,   
  3. RUNNABLE,   
  4. BLOCKED,   
  5. WAITING,   
  6. TIMED_WAITING,   
  7. TERMINATED;   
  8. }  
[java]  view plain copy print ?
  1. public enum State {  
  2. NEW,  
  3. RUNNABLE,  
  4. BLOCKED,  
  5. WAITING,  
  6. TIMED_WAITING,  
  7. TERMINATED;  
  8. }  


sleep与wait的区别  
sleep()方法和wait()方法都成产生让当前运行的线程停止运行的效果 
sleep方法是本地方法,属于Thread
 
Java代码  复制代码  收藏代码
  1. public static native void sleep(long millis) throws InterruptedException;   
  2. public static void sleep(long millis, int nanos) throws InterruptedException {   
  3. //other code   
  4. }  
[java]  view plain copy print ?
  1. public static native void sleep(long millis) throws InterruptedException;  
  2. public static void sleep(long millis, int nanos) throws InterruptedException {  
  3. //other code  
  4. }  

其中的参数millis代表毫秒数(千分之一秒),nanos代表纳秒数(十亿分之一秒)。这两 
个方法都可以让调用它的线程沉睡(停止运行)指定的时间,到了这个时间,线程就会自动 
醒来,变为可运行状态(RUNNABLE),但这并不表示它马上就会被运行,因为线程调度 
机制恢复线程的运行也需要时间。调用sleep()方法并不会让线程释放它所持有的同步锁;而 
且在这期间它也不会阻碍其它线程的运行。上面的连个方法都声明抛出一个 
InterruptedException类型的异常,这是因为线程在sleep()期间,有可能被持有它的引用的其它线程调用它的 interrupt()方法而中断。中断一个线程会导致一个InterruptedException异常的产生,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。
 
Java代码  复制代码  收藏代码
  1. public class InterruptTest {   
  2. public static void main(String[] args) {   
  3. Thread t = new Thread() {   
  4. public void run() {   
  5. try {   
  6. System.out.println("我被执行了-在sleep()方法前");   
  7. // 停止运行10分钟   
  8. Thread.sleep(1000 * 60 * 60 * 10);   
  9. System.out.println("我被执行了-在sleep()方法后");   
  10. catch (InterruptedException e) {   
  11. System.out.println("我被执行了-在catch语句块中");   
  12. }   
  13. System.out.println("我被执行了-在try{}语句块后");   
  14. }   
  15. };   
  16. // 启动线程   
  17. t.start();   
  18. // 在sleep()结束前中断它   
  19. t.interrupt();   
  20. }   
  21. }  
[java]  view plain copy print ?
  1. public class InterruptTest {  
  2. public static void main(String[] args) {  
  3. Thread t = new Thread() {  
  4. public void run() {  
  5. try {  
  6. System.out.println("我被执行了-在sleep()方法前");  
  7. // 停止运行10分钟  
  8. Thread.sleep(1000 * 60 * 60 * 10);  
  9. System.out.println("我被执行了-在sleep()方法后");  
  10. catch (InterruptedException e) {  
  11. System.out.println("我被执行了-在catch语句块中");  
  12. }  
  13. System.out.println("我被执行了-在try{}语句块后");  
  14. }  
  15. };  
  16. // 启动线程  
  17. t.start();  
  18. // 在sleep()结束前中断它  
  19. t.interrupt();  
  20. }  
  21. }  

我被执行了-在sleep()方法前 
我被执行了-在catch语句块中 
我被执行了-在try{}语句块后 

wait方法也是本地方法,属于Object类,有三个定义: 
Java代码  复制代码  收藏代码
  1. public final void wait() throws InterruptedException {   
  2. //do something   
  3. }   
  4. public final native void wait(long timeout) throws InterruptedException;   
  5. public final void wait(long timeout, int nanos) throws InterruptedException {   
  6. //do something   
  7. }  
[java]  view plain copy print ?
  1. public final void wait() throws InterruptedException {  
  2. //do something  
  3. }  
  4. public final native void wait(long timeout) throws InterruptedException;  
  5. public final void wait(long timeout, int nanos) throws InterruptedException {  
  6. //do something  
  7. }  

wait()和wait(long timeout,int nanos)方法都是基于wait(long timeout)方法实现的。同样地,timeout代表毫秒数,nanos代表纳秒数。当调用了某个对象的wait()方法时,当前运行的线程就会转入等待状态(WAITING),等待别的线程再次调用这个对象的notify()或者notifyAll()方法(这两个方法也是本地方法)唤醒它,或者到了指定的最大等待时间,线程自动醒来。如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()方法的对象。wait()方法同样会被Thread类的interrupt()方法中断,并产生一个 InterruptedException异常,效果同sleep()方法被中断一样。  
如何同步 同步的使用可以保证在多线程运行的环境中,程序不会产生设计之外的错误结果。同步的实现方式有两种,同步方法和同步块,这两种方式都要用到synchronized关键字。 
给一个方法增加synchronized修饰符之后就可以使它成为同步方法,这个方法可以是静态方 
法和非静态方法,但是不能是抽象类的抽象方法,也不能是接口中的接口方法。
 
线程在执行同步方法时是具有排它性的。当任意一个线程进入到一个对象的任意一个同步方 
法时,这个对象的所有同步方法都被锁定了,在此期间,其他任何线程都不能访问这个对象 
的任意一个同步方法,直到这个线程执行完它所调用的同步方法并从中退出,从而导致它释 
放了该对象的同步锁之后。在一个对象被某个线程锁定之后,其他线程是可以访问这个对象 
的所有非同步方法的。 
同步块的形式虽然与同步方法不同,但是原理和效果是一致的。同步块是通过锁定一个指定 
的对象,来对同步块中包含的代码进行同步;而同步方法是对这个方法块里的代码进行同步, 
而这种情况下锁定的对象就是同步方法所属的主体对象自身。如果这个方法是静态同步方法 
呢?那么线程锁定的就不是这个类的对象了,也不是这个类自身,而是这个类对应的 
java.lang.Class类型的对象。同步方法和同步块之间的相互制约只限于同一个对象之间,所以静态同步方法只受它所属类的其它静态同步方法的制约,而跟这个类的实例(对象)没有 
关系。
 
Java代码  复制代码  收藏代码
  1. public void test() {   
  2. // 同步锁   
  3. String lock = "LOCK";   
  4. // 同步块   
  5. synchronized (lock) {   
  6. // do something   
  7. }   
  8. int i = 0;   
  9. // ...   
  10. }  
[java]  view plain copy print ?
  1. public void test() {  
  2. // 同步锁  
  3. String lock = "LOCK";  
  4. // 同步块  
  5. synchronized (lock) {  
  6. // do something  
  7. }  
  8. int i = 0;  
  9. // ...  
  10. }  

对于作为同步锁的对象并没有什么特别要求,任意一个对象都可以。如果一个对象既有同步 
方法,又有同步块,那么当其中任意一个同步方法或者同步块被某个线程执行时,这个对象 
就被锁定了,其他线程无法在此时访问这个对象的同步方法,也不能执行同步块。
 

synchronized和Lock  
Lock是一个接口,它位于Java 5.0新增的java.utils.concurrent包的子包locks中。 
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。 

锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。 
concurrent包及其子包中的类都是用来处理多线程编程的。实现 Lock接口的类具有与synchronized关键字同样的功能,但是它更加强大一些。 java.utils.concurrent.locks.ReentrantLock是较常用 
的实现了Lock接口的类。下面是 ReentrantLock类的一个应用实例:
 
Java代码  复制代码  收藏代码
  1. private Lock lock = new ReentrantLock();   
  2. public void testLock() {   
  3. // 锁定对象,获取锁   
  4. lock.lock();   
  5. try {   
  6. // do something   
  7. finally {   
  8. // 释放对对象的锁定,释放锁   
  9. lock.unlock();   
  10. }   
  11. }  
[java]  view plain copy print ?
  1. private Lock lock = new ReentrantLock();  
  2. public void testLock() {  
  3. // 锁定对象,获取锁  
  4. lock.lock();  
  5. try {  
  6. // do something  
  7. finally {  
  8. // 释放对对象的锁定,释放锁  
  9. lock.unlock();  
  10. }  
  11. }  

使用synchronized关键字实现的同步,会把一个对象的所有同步方法和同步块看做一个整体,只要有一个被某个线程调用了,其他的就无法被别的线程执行,即使这些方法或同步块与被调用的代码之间没有任何逻辑关系,这显然降低了程序的运行效率。而使用Lock就能够很 
好地解决这个问题。我们可以把一个对象中按照逻辑关系把需要同步的方法或代码进行分组, 
为每个组创建一个Lock类型的对象,对实现同步。那么,当一个同步块被执行时,这个线 
程只会锁定与当前运行代码相关的其他代码最小集合,而并不影响其他线程对其余同步代码 
的调用执行。
 

死锁  
死锁就是一个进程中的每个线程都在等待这个进程中的其他线程释放所占用的资源,从而导 
致所有线程都无法继续执行的情况。 
死锁的必要条件: 
1. 互斥(Mutual exclusion):线程所使用的资源中至少有一个是不能共享的,它在同一 
时刻只能由一个线程使用。 
2. 持有与等待(Hold and wait):至少有一个线程已经持有了资源,并且正在等待获取 
其他的线程所持有的资源。 
3. 非抢占式(No pre-emption):如果一个线程已经持有了某个资源,那么在这个线程释 
放这个资源之前,别的线程不能把它抢夺过去使用。 
4. 循环等待(Circular wait):假设有N个线程在运行,第一个线程持有了一个资源,并 
且正在等待获取第二个线程持有的资源,而第二个线程正在等待获取第三个线程持有的资源, 
依此类推……第N个线程正在等待获取第一个线程持有的资源,由此形成一个循环等待。
 

线程池  
线程池就像数据库连接池一样,是一个对象池。所有的对象池都有一个共同的目的,那就是 
为了提高对象的使用率,从而达到提高程序效率的目的。比如对于Servlet,它被设计为多线 
程的(如果它是单线程的,你就可以想象,当1000个人同时请求一个网页时,在第一个人 
获得请求结果之前,其它999个人都在郁闷地等待),如果为每个用户的每一次请求都创建 
一个新的线程对象来运行的话,系统就会在创建线程和销毁线程上耗费很大的开销,大大降 
低系统的效率。因此,Servlet多线程机制背后有一个线程池在支持,线程池在初始化初期就 
创建了一定数量的线程对象,通过提高对这些对象的利用率,避免高频率地创建对象,从而 
达到提高程序的效率的目的。 

事实上Java5.0及以上版本已经为我们提供了线程池功能,无需再重新实现。这些类位于 
java.util.concurrent包中。 
Executors类提供了一组创建线程池对象的方法,常用的有一下几个:
Java代码  复制代码  收藏代码
  1. public static ExecutorService newCachedThreadPool() {   
  2. // other code   
  3. }   
  4. public static ExecutorService newFixedThreadPool(int nThreads) {   
  5. // other code   
  6. }   
  7. public static ExecutorService newSingleThreadExecutor() {   
  8. // other code   
  9. }  
[java]  view plain copy print ?
  1. public static ExecutorService newCachedThreadPool() {  
  2. // other code  
  3. }  
  4. public static ExecutorService newFixedThreadPool(int nThreads) {  
  5. // other code  
  6. }  
  7. public static ExecutorService newSingleThreadExecutor() {  
  8. // other code  
  9. }  

newCachedThreadPool()方法创建一个动态的线程池,其中线程的数量会根据实际需要来创建 
和回收,适合于执行大量短期任务的情况;newFixedThreadPool(int nThreads)方法创建一个 
包含固定数量线程对象的线程池,nThreads代表要创建的线程数,如果某个线程在运行的过 
程中因为异常而终止了,那么一个新的线程会被创建和启动来代替它;而 
newSingleThreadExecutor()方法则只在线程池中创建一个线程,来执行所有的任务。 
这三个方法都返回了一个ExecutorService类型的对象。实际上,ExecutorService是一个接口, 
它的submit()方法负责接收任务并交与线程池中的线程去运行。submit()方法能够接受 
Callable和Runnable两种类型的对象。它们的用法和区别如下: 
1. Runnable接口:继承Runnable接口的类要实现它的run()方法,并将执行任务的代码放 
入其中,run()方法没有返回值。适合于只做某种操作,不关心运行结果的情况。 
2. Callable接口:继承Callable接口的类要实现它的call()方法,并将执行任务的代码放入其中,call()将任务的执行结果作为返回值。适合于执行某种操作后,需要知道执行结果的情况。 
无论是接收Runnable型参数,还是接收Callable型参数的submit()方法,都会返回一个 
Future(也是一个接口)类型的对象。该对象中包含了任务的执行情况以及结果。调用 
Future的boolean isDone()方法可以获知任务是否执行完毕;调用Object get()方法可以获得任务执行后的返回结果,如果此时任务还没有执行完,get()方法会保持等待,直到相应的任务执行完毕后,才会将结果返回。 
我们用下面的一个例子来演示Java5.0中线程池的使用:
 

Java代码  复制代码  收藏代码
  1. import java.util.concurrent.*;   
  2. public class ExecutorTest {   
  3. public static void main(String[] args) throws InterruptedException,   
  4. ExecutionException {   
  5. ExecutorService es = Executors.newSingleThreadExecutor();   
  6. Future fr = es.submit(new RunnableTest());// 提交任务   
  7. Future fc = es.submit(new CallableTest());// 提交任务   
  8. // 取得返回值并输出   
  9. System.out.println((String) fc.get());   
  10. // 检查任务是否执行完毕   
  11. if (fr.isDone()) {   
  12. System.out.println("执行完毕-RunnableTest.run()");   
  13. else {   
  14. System.out.println("未执行完-RunnableTest.run()");   
  15. }   
  16. // 检查任务是否执行完毕   
  17. if (fc.isDone()) {   
  18. System.out.println("执行完毕-CallableTest.run()");   
  19. else {   
  20. System.out.println("未执行完-CallableTest.run()");   
  21. }   
  22. // 停止线程池服务   
  23. es.shutdown();   
  24. }   
  25. }   
  26. class RunnableTest implements Runnable {   
  27. public void run() {   
  28. System.out.println("已经执行-RunnableTest.run()");   
  29. }   
  30. }   
  31. class CallableTest implements Callable {   
  32. public Object call() {   
  33. System.out.println("已经执行-CallableTest.call()");   
  34. return "返回值-CallableTest.call()";   
  35. }   
  36. }  
[java]  view plain copy print ?
  1. import java.util.concurrent.*;  
  2. public class ExecutorTest {  
  3. public static void main(String[] args) throws InterruptedException,  
  4. ExecutionException {  
  5. ExecutorService es = Executors.newSingleThreadExecutor();  
  6. Future fr = es.submit(new RunnableTest());// 提交任务  
  7. Future fc = es.submit(new CallableTest());// 提交任务  
  8. // 取得返回值并输出  
  9. System.out.println((String) fc.get());  
  10. // 检查任务是否执行完毕  
  11. if (fr.isDone()) {  
  12. System.out.println("执行完毕-RunnableTest.run()");  
  13. else {  
  14. System.out.println("未执行完-RunnableTest.run()");  
  15. }  
  16. // 检查任务是否执行完毕  
  17. if (fc.isDone()) {  
  18. System.out.println("执行完毕-CallableTest.run()");  
  19. else {  
  20. System.out.println("未执行完-CallableTest.run()");  
  21. }  
  22. // 停止线程池服务  
  23. es.shutdown();  
  24. }  
  25. }  
  26. class RunnableTest implements Runnable {  
  27. public void run() {  
  28. System.out.println("已经执行-RunnableTest.run()");  
  29. }  
  30. }  
  31. class CallableTest implements Callable {  
  32. public Object call() {  
  33. System.out.println("已经执行-CallableTest.call()");  
  34. return "返回值-CallableTest.call()";  
  35. }  
  36. }  

已经执行-RunnableTest.run() 
已经执行-CallableTest.call() 
返回值-CallableTest.call() 
执行完毕-RunnableTest.run() 
执行完毕-CallableTest.run() 
使用完线程池之后,需要调用它的shutdown()方法停止服务,否则其中的所有线程都会保持 
运行,程序不会退出。 



Java面试之程序设计题


1.写一个方法,用二分查找法判断任意整数(已排序)在任意整数数组里面是否存在,若存在就返回它在数组中的索引位置,不存在返回-1

Java代码  复制代码
  1. /**   
  2.  *二分查找特定整数在整型数组中的位置(递归)   
  3.  *@param dataset   
  4.  *@param data   
  5.  *@param beginIndex   
  6.  *@param endIndex   
  7.  *@return index   
  8.  */    
  9.  public int binarySearch(int[] dataset,int data,int beginIndex,int endIndex){    
  10.    int midIndex = (beginIndex+endIndex)/2;    
  11.    //如果查找的数要比开始索引的数据要小或者是比结束索引的书要大,或者开始查找的索引值大于结束的索引值返回-1没有查到   
  12.    if(data <dataset[beginIndex]||data>dataset[endIndex]||beginIndex>endIndex){   
  13.        return -1;   
  14.    }   
  15.    if(data <dataset[midIndex]){     
  16.        return binarySearch(dataset,data,beginIndex,midIndex-1);    
  17.    }else if(data>dataset[midIndex])    
  18.    {    
  19.        return binarySearch(dataset,data,midIndex+1,endIndex);    
  20.    }else {    
  21.        return midIndex;    
  22.    }    
  23.  }    
  24.     
  25.  /**   
  26.   *二分查找特定整数在整型数组中的位置(非递归)   
  27.   *@param dataset   
  28.   *@param data   
  29.   *@return index   
  30.   */    
  31.   public int binarySearch(int[] dataset ,int data)    
  32.   {    
  33.     int beginIndex = 0;     
  34.     int endIndex = dataset.length - 1;     
  35.     int midIndex = -1;    
  36.     if(data <dataset[beginIndex]||data>dataset[endIndex]||beginIndex>endIndex){   
  37.         return -1;    
  38.     }   
  39.     while(beginIndex <= endIndex) {    
  40.         midIndex = (beginIndex+endIndex)/2;    
  41.         if(data <dataset[midIndex]) {     
  42.            endIndex = midIndex-1;     
  43.         } else if(data>dataset[midIndex]) {     
  44.           beginIndex = midIndex+1;     
  45.         }else {    
  46.           return midIndex;    
  47.         }    
  48.     }    
  49.     return -1;    
  50.   }  
[java]  view plain copy print ?
  1. /**        *二分查找特定整数在整型数组中的位置(递归)        *@param dataset        *@param data        *@param beginIndex        *@param endIndex        *@return index        */        public int binarySearch(int[] dataset,int data,int beginIndex,int endIndex){          int midIndex = (beginIndex+endIndex)/2;          //如果查找的数要比开始索引的数据要小或者是比结束索引的书要大,或者开始查找的索引值大于结束的索引值返回-1没有查到         if(data <dataset[beginIndex]||data>dataset[endIndex]||beginIndex>endIndex){             return -1;         }         if(data <dataset[midIndex]){               return binarySearch(dataset,data,beginIndex,midIndex-1);          }else if(data>dataset[midIndex])          {              return binarySearch(dataset,data,midIndex+1,endIndex);          }else {              return midIndex;          }        }               /**         *二分查找特定整数在整型数组中的位置(非递归)         *@param dataset         *@param data         *@return index         */         public int binarySearch(int[] dataset ,int data)         {           int beginIndex = 0;            int endIndex = dataset.length - 1;            int midIndex = -1;           if(data <dataset[beginIndex]||data>dataset[endIndex]||beginIndex>endIndex){              return -1;           }          while(beginIndex <= endIndex) {               midIndex = (beginIndex+endIndex)/2;               if(data <dataset[midIndex]) {                   endIndex = midIndex-1;                } else if(data>dataset[midIndex]) {                  beginIndex = midIndex+1;                }else {                 return midIndex;               }           }           return -1;         }  

6.java排序汇总

Java代码  复制代码
  1. package com.softeem.jbs.lesson4;    
  2.      
  3. import java.util.Random;       
  4.      
  5. /**    
  6.    
  7.  * 排序测试类    
  8.    
  9.  *     
  10.    
  11.  * 排序算法的分类如下:    
  12.    
  13.  * 1.插入排序(直接插入排序、折半插入排序、希尔排序);    
  14.    
  15.  * 2.交换排序(冒泡泡排序、快速排序);    
  16.    
  17.  * 3.选择排序(直接选择排序、堆排序);    
  18.    
  19.  * 4.归并排序;    
  20.    
  21.  * 5.基数排序。    
  22.    
  23.  *     
  24.    
  25.  * 关于排序方法的选择:    
  26.    
  27.  * (1)若n较小(如n≤50),可采用直接插入或直接选择排序。    
  28.    
  29.  *  当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。    
  30.    
  31.  * (2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;    
  32.    
  33.  * (3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。    
  34.    
  35.  *     
  36.    
  37.  */     
  38.      
  39. public class SortTest {      
  40.      
  41.        
  42.      
  43.        /**    
  44.    
  45.         * 初始化测试数组的方法    
  46.    
  47.         * @return 一个初始化好的数组    
  48.    
  49.         */     
  50.      
  51.        public int[] createArray() {      
  52.      
  53.               Random random = new Random();      
  54.      
  55.               int[] array = new int[10];      
  56.      
  57.               for (int i = 0; i < 10; i++) {      
  58.      
  59.                      array[i] = random.nextInt(100) - random.nextInt(100);//生成两个随机数相减,保证生成的数中有负数      
  60.      
  61.               }      
  62.      
  63.               System.out.println("==========原始序列==========");      
  64.      
  65.               printArray(array);      
  66.      
  67.               return array;      
  68.      
  69.        }      
  70.      
  71.        
  72.      
  73.        /**    
  74.    
  75.         * 打印数组中的元素到控制台    
  76.    
  77.         * @param source    
  78.    
  79.         */     
  80.      
  81.        public void printArray(int[] data) {      
  82.      
  83.               for (int i : data) {      
  84.      
  85.                      System.out.print(i + " ");      
  86.      
  87.               }      
  88.      
  89.               System.out.println();      
  90.      
  91.        }      
  92.      
  93.        
  94.      
  95.        /**    
  96.    
  97.         * 交换数组中指定的两元素的位置    
  98.    
  99.         * @param data    
  100.    
  101.         * @param x    
  102.    
  103.         * @param y    
  104.    
  105.         */     
  106.      
  107.        private void swap(int[] data, int x, int y) {      
  108.      
  109.               int temp = data[x];      
  110.      
  111.               data[x] = data[y];      
  112.      
  113.               data[y] = temp;      
  114.      
  115.        }      
  116.      
  117.        
  118.      
  119.        /**    
  120.    
  121.         * 冒泡排序----交换排序的一种    
  122.    
  123.         * 方法:相邻两元素进行比较,如有需要则进行交换,每完成一次循环就将最大元素排在最后(如从小到大排序),下一次循环是将其他的数进行类似操作。     
  124.    
  125.         * 性能:比较次数O(n^2),n^2/2;交换次数O(n^2),n^2/4    
  126.    
  127.         *     
  128.    
  129.         * @param data 要排序的数组    
  130.    
  131.         * @param sortType 排序类型    
  132.    
  133.         * @return    
  134.    
  135.         */     
  136.      
  137.        public void bubbleSort(int[] data, String sortType) {      
  138.      
  139.               if (sortType.equals("asc")) { //正排序,从小排到大      
  140.      
  141.                      //比较的轮数      
  142.      
  143.                      for (int i = 1; i < data.length; i++) {      
  144.      
  145.                             //将相邻两个数进行比较,较大的数往后冒泡      
  146.      
  147.                             for (int j = 0; j < data.length - i; j++) {      
  148.      
  149.                                    if (data[j] > data[j + 1]) {      
  150.      
  151.                                           //交换相邻两个数      
  152.      
  153.                                           swap(data, j, j + 1);      
  154.      
  155.                                    }      
  156.      
  157.                             }      
  158.      
  159.                      }      
  160.      
  161.               } else if (sortType.equals("desc")) { //倒排序,从大排到小      
  162.      
  163.                      //比较的轮数      
  164.      
  165.                      for (int i = 1; i < data.length; i++) {      
  166.      
  167.                             //将相邻两个数进行比较,较大的数往后冒泡      
  168.      
  169.                             for (int j = 0; j < data.length - i; j++) {      
  170.      
  171.                                    if (data[j] < data[j + 1]) {      
  172.      
  173.                                           //交换相邻两个数      
  174.      
  175.                                           swap(data, j, j + 1);      
  176.      
  177.                                    }      
  178.      
  179.                             }      
  180.      
  181.                      }      
  182.      
  183.               } else {      
  184.      
  185.                      System.out.println("您输入的排序类型错误!");      
  186.      
  187.               }      
  188.      
  189.               printArray(data);//输出冒泡排序后的数组值      
  190.      
  191.        }      
  192.      
  193.        
  194.      
  195.        /**    
  196.    
  197.         * 直接选择排序法----选择排序的一种    
  198.    
  199.         * 方法:每一趟从待排序的数据元素中选出最小(或最大)的一个元素, 顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。    
  200.    
  201.         * 性能:比较次数O(n^2),n^2/2    
  202.    
  203.         *       交换次数O(n),n    
  204.    
  205.         *       交换次数比冒泡排序少多了,由于交换所需CPU时间比比较所需的CUP时间多,所以选择排序比冒泡排序快。    
  206.    
  207.         *       但是N比较大时,比较所需的CPU时间占主要地位,所以这时的性能和冒泡排序差不太多,但毫无疑问肯定要快些。    
  208.    
  209.         *     
  210.    
  211.         * @param data 要排序的数组    
  212.    
  213.         * @param sortType 排序类型    
  214.    
  215.         * @return    
  216.    
  217.         */     
  218.      
  219.        public void selectSort(int[] data, String sortType) {      
  220.      
  221.        
  222.      
  223.               if (sortType.equals("asc")) { //正排序,从小排到大      
  224.      
  225.                      int index;      
  226.      
  227.                      for (int i = 1; i < data.length; i++) {      
  228.      
  229.                             index = 0;      
  230.      
  231.                             for (int j = 1; j <= data.length - i; j++) {      
  232.      
  233.                                    if (data[j] > data[index]) {      
  234.      
  235.                                           index = j;      
  236.      
  237.        
  238.      
  239.                                    }      
  240.      
  241.                             }      
  242.      
  243.                             //交换在位置data.length-i和index(最大值)两个数      
  244.      
  245.                             swap(data, data.length - i, index);      
  246.      
  247.                      }      
  248.      
  249.               } else if (sortType.equals("desc")) { //倒排序,从大排到小      
  250.      
  251.                      int index;      
  252.      
  253.                      for (int i = 1; i < data.length; i++) {      
  254.      
  255.                             index = 0;      
  256.      
  257.                             for (int j = 1; j <= data.length - i; j++) {      
  258.      
  259.                                    if (data[j] < data[index]) {      
  260.      
  261.                                           index = j;      
  262.      
  263.        
  264.      
  265.                                    }      
  266.      
  267.                             }      
  268.      
  269.                             //交换在位置data.length-i和index(最大值)两个数      
  270.      
  271.                             swap(data, data.length - i, index);      
  272.      
  273.                      }      
  274.      
  275.               } else {      
  276.      
  277.                      System.out.println("您输入的排序类型错误!");      
  278.      
  279.               }      
  280.      
  281.               printArray(data);//输出直接选择排序后的数组值      
  282.      
  283.        }      
  284.      
  285.        
  286.      
  287.        /**    
  288.    
  289.         * 插入排序    
  290.    
  291.         * 方法:将一个记录插入到已排好序的有序表(有可能是空表)中,从而得到一个新的记录数增1的有序表。    
  292.    
  293.         * 性能:比较次数O(n^2),n^2/4    
  294.    
  295.         *       复制次数O(n),n^2/4    
  296.    
  297.         *       比较次数是前两者的一般,而复制所需的CPU时间较交换少,所以性能上比冒泡排序提高一倍多,而比选择排序也要快。    
  298.    
  299.         *    
  300.    
  301.         * @param data 要排序的数组    
  302.    
  303.         * @param sortType 排序类型    
  304.    
  305.         */     
  306.      
  307.        public void insertSort(int[] data, String sortType) {      
  308.      
  309.               if (sortType.equals("asc")) { //正排序,从小排到大      
  310.      
  311.                      //比较的轮数      
  312.      
  313.                      for (int i = 1; i < data.length; i++) {      
  314.      
  315.                             //保证前i+1个数排好序      
  316.      
  317.                             for (int j = 0; j < i; j++) {      
  318.      
  319.                                    if (data[j] > data[i]) {      
  320.      
  321.                                           //交换在位置j和i两个数      
  322.      
  323.                                           swap(data, i, j);      
  324.      
  325.                                    }      
  326.      
  327.                             }      
  328.      
  329.                      }      
  330.      
  331.               } else if (sortType.equals("desc")) { //倒排序,从大排到小      
  332.      
  333.                      //比较的轮数      
  334.      
  335.                      for (int i = 1; i < data.length; i++) {      
  336.      
  337.                             //保证前i+1个数排好序      
  338.      
  339.                             for (int j = 0; j < i; j++) {      
  340.      
  341.                                    if (data[j] < data[i]) {      
  342.      
  343.                                           //交换在位置j和i两个数      
  344.      
  345.                                           swap(data, i, j);      
  346.      
  347.                                    }      
  348.      
  349.                             }      
  350.      
  351.                      }      
  352.      
  353.               } else {      
  354.      
  355.                      System.out.println("您输入的排序类型错误!");      
  356.      
  357.               }      
  358.      
  359.               printArray(data);//输出插入排序后的数组值      
  360.      
  361.        }      
  362.      
  363.        
  364.      
  365.        /**    
  366.    
  367.         * 反转数组的方法    
  368.    
  369.         * @param data 源数组    
  370.    
  371.         */     
  372.      
  373.        public void reverse(int[] data) {      
  374.      
  375.        
  376.      
  377.               int length = data.length;      
  378.      
  379.               int temp = 0;//临时变量      
  380.      
  381.        
  382.      
  383.               for (int i = 0; i < length / 2; i++) {      
  384.      
  385.                      temp = data[i];      
  386.      
  387.                      data[i] = data[length - 1 - i];      
  388.      
  389.                      data[length - 1 - i] = temp;      
  390.      
  391.               }      
  392.      
  393.               printArray(data);//输出到转后数组的值      
  394.      
  395.        }      
  396.      
  397.        
  398.      
  399.        /**    
  400.    
  401.         * 快速排序    
  402.    
  403.         * 快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。    
  404.    
  405.         * 步骤为:    
  406.    
  407.         * 1. 从数列中挑出一个元素,称为 "基准"(pivot),    
  408.    
  409.         * 2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分割之后,该基准是它的最后位置。这个称为分割(partition)操作。    
  410.    
  411.         * 3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。    
  412.    
  413.         * 递回的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递回下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。    
  414.    
  415.         * @param data 待排序的数组    
  416.    
  417.         * @param low    
  418.    
  419.         * @param high    
  420.    
  421.         * @see SortTest#qsort(int[], int, int)    
  422.    
  423.         * @see SortTest#qsort_desc(int[], int, int)    
  424.    
  425.         */     
  426.      
  427.        public void quickSort(int[] data, String sortType) {      
  428.      
  429.               if (sortType.equals("asc")) { //正排序,从小排到大      
  430.      
  431.                      qsort_asc(data, 0, data.length - 1);      
  432.      
  433.               } else if (sortType.equals("desc")) { //倒排序,从大排到小      
  434.      
  435.                      qsort_desc(data, 0, data.length - 1);      
  436.      
  437.               } else {      
  438.      
  439.                      System.out.println("您输入的排序类型错误!");      
  440.      
  441.               }      
  442.      
  443.        }      
  444.      
  445.        
  446.      
  447.        /**    
  448.    
  449.         * 快速排序的具体实现,排正序    
  450.    
  451.         * @param data    
  452.    
  453.         * @param low    
  454.    
  455.         * @param high    
  456.    
  457.         */     
  458.      
  459.        private void qsort_asc(int data[], int low, int high) {      
  460.      
  461.               int i, j, x;      
  462.      
  463.               if (low < high) { //这个条件用来结束递归      
  464.      
  465.                      i = low;      
  466.      
  467.                      j = high;      
  468.      
  469.                      x = data[i];      
  470.      
  471.                      while (i < j) {      
  472.      
  473.                             while (i < j && data[j] > x) {      
  474.      
  475.                                    j--; //从右向左找第一个小于x的数      
  476.      
  477.                             }      
  478.      
  479.                             if (i < j) {      
  480.      
  481.                                    data[i] = data[j];      
  482.      
  483.                                    i++;      
  484.      
  485.                             }      
  486.      
  487.                             while (i < j && data[i] < x) {      
  488.      
  489.                                    i++; //从左向右找第一个大于x的数      
  490.      
  491.                             }      
  492.      
  493.                             if (i < j) {      
  494.      
  495.                                    data[j] = data[i];      
  496.      
  497.                                    j--;      
  498.      
  499.                             }      
  500.      
  501.                      }      
  502.      
  503.                      data[i] = x;      
  504.      
  505.                      qsort_asc(data, low, i - 1);      
  506.      
  507.                      qsort_asc(data, i + 1, high);      
  508.      
  509.               }      
  510.      
  511.        }      
  512.      
  513.        
  514.      
  515.        /**    
  516.    
  517.         * 快速排序的具体实现,排倒序    
  518.    
  519.         * @param data    
  520.    
  521.         * @param low    
  522.    
  523.         * @param high    
  524.    
  525.         */     
  526.      
  527.        private void qsort_desc(int data[], int low, int high) {      
  528.      
  529.               int i, j, x;      
  530.      
  531.               if (low < high) { //这个条件用来结束递归      
  532.      
  533.                      i = low;      
  534.      
  535.                      j = high;      
  536.      
  537.                      x = data[i];      
  538.      
  539.                      while (i < j) {      
  540.      
  541.                             while (i < j && data[j] < x) {      
  542.      
  543.                                    j--; //从右向左找第一个小于x的数      
  544.      
  545.                             }      
  546.      
  547.                             if (i < j) {      
  548.      
  549.                                    data[i] = data[j];      
  550.      
  551.                                    i++;      
  552.      
  553.                             }      
  554.      
  555.                             while (i < j && data[i] > x) {      
  556.      
  557.                                    i++; //从左向右找第一个大于x的数      
  558.      
  559.                             }      
  560.      
  561.                             if (i < j) {      
  562.      
  563.                                    data[j] = data[i];      
  564.      
  565.                                    j--;      
  566.      
  567.                             }      
  568.      
  569.                      }      
  570.      
  571.                      data[i] = x;      
  572.      
  573.                      qsort_desc(data, low, i - 1);      
  574.      
  575.                      qsort_desc(data, i + 1, high);      
  576.      
  577.               }      
  578.      
  579.        }      
  580.      
  581.        
  582.      
  583.        /**    
  584.    
  585.         *二分查找特定整数在整型数组中的位置(递归)    
  586.    
  587.         *查找线性表必须是有序列表    
  588.    
  589.         *@paramdataset    
  590.    
  591.         *@paramdata    
  592.    
  593.         *@parambeginIndex    
  594.    
  595.         *@paramendIndex    
  596.    
  597.         *@returnindex    
  598.    
  599.         */     
  600.      
  601.        public int binarySearch(int[] dataset, int data, int beginIndex,      
  602.      
  603.                      int endIndex) {      
  604.      
  605.               int midIndex = (beginIndex + endIndex) >>> 1//相当于mid = (low + high) / 2,但是效率会高些      
  606.      
  607.               if (data < dataset[beginIndex] || data > dataset[endIndex]      
  608.      
  609.                             || beginIndex > endIndex)      
  610.      
  611.                      return -1;      
  612.      
  613.               if (data < dataset[midIndex]) {      
  614.      
  615.                      return binarySearch(dataset, data, beginIndex, midIndex - 1);      
  616.      
  617.               } else if (data > dataset[midIndex]) {      
  618.      
  619.                      return binarySearch(dataset, data, midIndex + 1, endIndex);      
  620.      
  621.               } else {      
  622.      
  623.                      return midIndex;      
  624.      
  625.               }      
  626.      
  627.        }      
  628.      
  629.        
  630.      
  631.        /**    
  632.    
  633.         *二分查找特定整数在整型数组中的位置(非递归)    
  634.    
  635.         *查找线性表必须是有序列表    
  636.    
  637.         *@paramdataset    
  638.    
  639.         *@paramdata    
  640.    
  641.         *@returnindex    
  642.    
  643.         */     
  644.      
  645.        public int binarySearch(int[] dataset, int data) {      
  646.      
  647.               int beginIndex = 0;      
  648.      
  649.               int endIndex = dataset.length - 1;      
  650.      
  651.               int midIndex = -1;      
  652.      
  653.               if (data < dataset[beginIndex] || data > dataset[endIndex]      
  654.      
  655.                             || beginIndex > endIndex)      
  656.      
  657.                      return -1;      
  658.      
  659.               while (beginIndex <= endIndex) {      
  660.      
  661.                      midIndex = (beginIndex + endIndex) >>> 1//相当于midIndex = (beginIndex + endIndex) / 2,但是效率会高些     
  662.      
  663.                      if (data < dataset[midIndex]) {      
  664.      
  665.                             endIndex = midIndex - 1;      
  666.      
  667.                      } else if (data > dataset[midIndex]) {      
  668.      
  669.                             beginIndex = midIndex + 1;      
  670.      
  671.                      } else {      
  672.      
  673.                             return midIndex;      
  674.      
  675.                      }      
  676.      
  677.               }      
  678.      
  679.               return -1;      
  680.      
  681.        }      
  682.      
  683.        
  684.      
  685.        public static void main(String[] args) {      
  686.      
  687.               SortTest sortTest = new SortTest();      
  688.      
  689.        
  690.      
  691.               int[] array = sortTest.createArray();      
  692.      
  693.        
  694.      
  695.               System.out.println("==========冒泡排序后(正序)==========");      
  696.      
  697.               sortTest.bubbleSort(array, "asc");      
  698.      
  699.               System.out.println("==========冒泡排序后(倒序)==========");      
  700.      
  701.               sortTest.bubbleSort(array, "desc");      
  702.      
  703.        
  704.      
  705.               array = sortTest.createArray();      
  706.      
  707.        
  708.      
  709.               System.out.println("==========倒转数组后==========");      
  710.      
  711.               sortTest.reverse(array);      
  712.      
  713.        
  714.      
  715.               array = sortTest.createArray();      
  716.      
  717.        
  718.      
  719.               System.out.println("==========选择排序后(正序)==========");      
  720.      
  721.               sortTest.selectSort(array, "asc");      
  722.      
  723.               System.out.println("==========选择排序后(倒序)==========");      
  724.      
  725.               sortTest.selectSort(array, "desc");      
  726.      
  727.        
  728.      
  729.               array = sortTest.createArray();      
  730.      
  731.        
  732.      
  733.               System.out.println("==========插入排序后(正序)==========");      
  734.      
  735.               sortTest.insertSort(array, "asc");      
  736.      
  737.               System.out.println("==========插入排序后(倒序)==========");      
  738.      
  739.               sortTest.insertSort(array, "desc");      
  740.      
  741.        
  742.      
  743.               array = sortTest.createArray();      
  744.      
  745.               System.out.println("==========快速排序后(正序)==========");      
  746.      
  747.               sortTest.quickSort(array, "asc");      
  748.      
  749.               sortTest.printArray(array);      
  750.      
  751.               System.out.println("==========快速排序后(倒序)==========");      
  752.      
  753.               sortTest.quickSort(array, "desc");      
  754.      
  755.               sortTest.printArray(array);      
  756.      
  757.        
  758.      
  759.               System.out.println("==========数组二分查找==========");      
  760.      
  761.               System.out.println("您要找的数在第" + sortTest.binarySearch(array, 74)      
  762.      
  763.                             + "个位子。(下标从0计算)");      
  764.      
  765.        }      
  766.      
  767. }  

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值