java线程

来源:互联网 发布:衢州学院网络课程 编辑:程序博客网 时间:2024/06/02 02:16


进程:一般指一个程序运行时的实例,每个进程有自己独立的地址空间。一个进程无法直接访问另一个进程的数据。

线程:是CPU调度的最小单元,一个进程可以有很多线程,每条线程可以并行执行不同的任务。并且每个线程有独立的运行栈和程序计数器

多线程:指一个进程运行时产生了不止一个线程。提高了CPU资源的利用率。

并行:多台机器同时执行一段处理逻辑,即真正的同一时刻。

并发:通过CPU调度算法,从CPU操作层面不是真正的同时。并发一般发生在多个线程同时访问同一数据时。

线程安全:代码在多线程下运行和在单线程下运行永远都是获得相同的结果。

 

1.  创建多线程的三种方式

1.1 ThreadRunnable的比较

Java 5以前实现多线程有两种实现方法:一种是继承Thread;另一种是实现Runnable接口

 实现Runnable接口相比继承Thread类有如下优势:

(1)可以避免由于Java的单继承特性而带来的局限。

(2)适合多个相同程序代码的线程区处理同一资源的情况。比如下面这个买票的例子。

(3)Callable接口的call()方法是与返回值的,是一个泛型,而Runnable的run()没有返回值,单纯运行其中的逻辑。

[java] view plain copy
  1. //使用Thread实现  
  2. public static class MyThread extends Thread{    
  3.     private int ticket = 5;    
  4.     public void run(){    
  5.         for (int i=0;i<10;i++)  {    
  6.             if(ticket > 0){    
  7.                 System.out.println("ticket = " + ticket--);    
  8.             }    
  9.         }    
  10.     }    
  11. }    
  12.   
  13. public class ThreadDemo{    
  14.     public static void main(String[] args){    
  15.         new MyThread().start();    
  16.         new MyThread().start();      
  17.     }    
  18. }   
[java] view plain copy
  1. //每个线程单独卖了5张票,即独立的完成了买票的任务  
  2. //输出结果为  
  3. ticket = 5  
  4. ticket = 4  
  5. ticket = 5  
  6. ticket = 3  
  7. ticket = 2  
  8. ticket = 1  
  9. ticket = 4  
  10. ticket = 3  
  11. ticket = 2  
  12. ticket = 1  
[java] view plain copy
  1. //通过实现Runnable接口实现  
  2. public static class MyThread implements Runnable{    
  3.     private int ticket = 5;    
  4.     public void run(){    
  5.         for (int i=0;i<10;i++)  {    
  6.             if(ticket > 0){    
  7.                 System.out.println("ticket = " + ticket--);    
  8.             }    
  9.         }    
  10.     }    
  11. }    
  12.   
  13. public class RunnableDemo{    
  14.     public static void main(String[] args){    
  15.         MyThread my = new MyThread();  
  16.  //同样也new了2个Thread对象,但只有一个Runnable对象  
  17.  // 2个Thread对象共享这个Runnable对象中的代码  
  18.         new Thread(my).start();    
  19.         new Thread(my).start();    
  20.     }    
  21. }  
[java] view plain copy
  1. //输出结果为  
  2. ticket = 5  
  3. ticket = 3  
  4. ticket = 2  
  5. ticket = 1  
  6. ticket = 4  

注意:

(1)上面第二段代码ticket输出的顺序并不是54321,这是因为线程执行的时机难以预测。因为ticket--并不是原子操作。这就需要加入同步操作。确保同一时刻只有一个线程在执行每次for循环中的操作。

(2)调用Thread.start()方法才会启动新线程;如果直接调用Thread.run() 方法,它的行为就和普通方法是一样的。

(3)start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行行态(Runnable,什么时候运行是由操作系统决定的。

(4)在Java中,每次程序运行至少启动2个线程。一个是main主线程,一个是垃圾收集线程


1.2  Callable

Java 5以后创建线程还有第三种方式:实现Callable接口,该接口中的call方法可以在线程执行结束时产生一个返回值,并且必须使用ExecutorService.submit()方法调用,submit()方法会返回产生的Future对象,它用Callable返回结果的特定类型进行了参数化。可以使用isDone()检测Future是否已完成,也可以不使用isDone()直接使用get(),get()将阻塞直至结果准备就绪。代码如下所示。

[java] view plain copy
  1. public static class MyTask implements Callable<Integer> {    
  2.         private int upperBounds;    
  3.   
  4.         public MyTask(int upperBounds) {    
  5.             this.upperBounds = upperBounds;    
  6.         }    
  7.   
  8.         @Override    
  9.         public Integer call() throws Exception {    
  10.             int sum = 0;     
  11.             for(int i = 1; i <= upperBounds; i++) {    
  12.                 sum += i;    
  13.             }    
  14.             return sum;    
  15.         }    
  16.   
  17.     }    
  18.   
  19.         public static void main(String[] args) throws Exception {    
  20.             List<Future<Integer>> list = new ArrayList<>();    
  21.             ExecutorService service = Executors.newFixedThreadPool(10);    
  22.             for(int i = 0; i < 10; i++) {    
  23.                 list.add(service.submit(new MyTask((int) (Math.random() * 100))));    
  24.             }    
  25.    
  26.             for(Future<Integer> future : list) {  
  27.                 int sum = 0;    
  28.                 while(!future.isDone()) ;  //如果任务已完成isDone返回true  
  29.                 sum += future.get(); //get()方法获取返回值   
  30.                 System.out.println(sum);  //打印十次求和  
  31.                     //future.cancel(true); 中断该线程的执行  
  32.                     //isCancelled(); 如果在任务正常完成前将其取消返回true  
  33.             }    
  34.     }    

2.   线程自身的信息

(1)线程的ID是唯一标识getId();

(2)线程的名称:getName(),如果不设置线程名称默认为“Thread-xx”;

(3)线程的优先级,一般不推荐设置线程的优先级,非法设置会出现IllegalArgumentException异常。

[java] view plain copy
  1. //static int MAX_PRIORITY  
  2. //线程可以具有的最高优先级,取值为10  
  3. //static int MIN_PRIORITY  
  4. //线程可以具有的最低优先级,取值为1  
  5. //static int NORM_PRIORITY  
  6. //分配给线程的默认优先级,取值为5  
  7. Thread.setPriority();  
  8. Thread.getPriority();//分别用来设置和获取线程的优先级  


3.  线程的六种状态

[java] view plain copy
  1. public enum State {  
  2.     //线程刚创建  
  3.     NEW,  
  4.   
  5.     //在JVM中正在运行的线程  
  6.     RUNNABLE,  
  7.   
  8.     //线程处于阻塞状态,等待监视锁,可以重新进行同步代码块中执行  
  9.     BLOCKED,  
  10.   
  11.     //等待状态  
  12.     WAITING,  
  13.   
  14.     //调用sleep() join() wait()方法可能导致线程处于等待状态  
  15.     TIMED_WAITING,  
  16.   
  17.     //线程执行完毕,已经退出  
  18.     TERMINATED;  
  19. }  

New状态表示刚刚创建的线程,这种线程还没开始执行,需要注意的是从New状态出发后不能再回到该状态。

等到线程的start()方法调用时才表示线程开始执行,当线程执行时,处于Runnable状态。表示所需的资源都准备好了。

如果线程执行时遇到了synchronized同步块,则进入Blocked阻塞状态,这时线程会暂停执行,直到获取到请求的锁。

Waiting和Time_Waiting都表示等待状态,前者会进入一个无时间限制的等待,后者会进入一个有时限的等待。

比如通过wait()等待的线程等待notify(),通过join()等待的线程等待目标线程的终止。一旦等到期望的时间,线程会进入Runnable状态。

当线程执行完毕,则进入Terminated状态,表示结束。结束后不再回到Runnable状态。


4.   多线程中的各种方法

(1)wait()导致线程进入等待状态,直到其他线程调用此对象的notify() 方法或notifyAll() 唤醒方法。

(2)notify()唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会随机选择唤醒其中一个线程。

(3)notifyAll()唤醒在此对象监视器上等待的所有线程

以上三个方法均为Object类的方法,并且调用时当前线程必须是锁的持有者,否则会抛出IllegalMonitorStateException。

(4)sleep()在指定时间内让当前正在执行的线程暂停执行wait()方法进入等待状态时会释放同步锁,而sleep() 方法不会释放同步锁。wait()通常被用于线程间交互,sleep()通常被用于暂停执行。wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。 

(5)join()定义在Thread中,如下代码解释。这种方法就可以确保两个线程的同步性。join()方法也可以设置一个最大等待时间,超过这个时间还在执行则继续往下执行。

[java] view plain copy
  1. Thread t1 = new Thread(计数线程一);    
  2. Thread t2 = new Thread(计数线程二);    
  3. t1.start();    
  4. t1.join(); // 等待计数线程一执行完成,再执行计数线程二  
  5. t2.start();    

(6)yield()线程让出CPU。让出后依然会进行CPU资源的争夺,而sleep() 方法在指定的睡眠内一定不会再得到运行机会。yield()方法一般用于某个线程不那么重要,又担心它占用太多CPU资源,就可以调用该方法让其他重要资源获得更多的工作机会。