多线程

来源:互联网 发布:网络授课怎么赚钱 编辑:程序博客网 时间:2024/06/03 12:40
并发通常是提高运行在单处理器上程序的性能,在单处理器上运行的并发程序开销确实应该比该程序的所有部分顺序执行的开销大,因为其中增加了所谓上下文切换的代价(从一个任务切换到另一个任务)。如果程序中的某个任务因为该程序控制之外的某些条件(通常是I/O)而导致不能继续执行,即阻塞,但是使用并发来编程,当其中一个任务阻塞时,程序中的其他任务还可以继续执行因此这个程序可以保持继续向前执行。但是从性能角度看,如果没有任务阻塞,那么单处理器机器上使用并发就没有任何意义。使用并发最吸引人的一个原因就是产生具有可响应的用户界面。实现并发最直接的方式是在操作系统级别使用进程。进程是运行在它自己的地址空间内的自包含的程序。多任务操作系统可以通过周期性的将CPU从一个进程切换到另一个进程,来实现同时运行多个进程,尽管这使得每个进程看起来在其执行过程中总是歇歇停停。进程总是很吸引人,因为操作系统通常会将进程相互隔离开,因此它们不会相互干涉,这使得进程编程相对容易些。与此相反的是,像java所使用的这种并发系统会共享诸如内存和I/O这样的资源,因此编写多线程程序最基本的困难在于在协调不同线程驱动的任务之间对这些资源的使用,以使得这些资源不会同时被多个任务访问。上文摘自《thinking in java》第二十一章,只是简单的介绍下,多线程编程带来的好处。

下面简单的介绍下进程与线程,关于进程与线程的更多知识,可以参考操作系统书籍,这里不作叙述。

线程的基本概念:
线程是一个程序内部的顺序控制流。
线程和进程的区别:
每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。线程可以看成时轻量级的进程,同一类线程共享代码和数据空间,每个线程都有独立的运行栈,和程序计数器(PC),线程切换的开销小。
多进程:在操作系统中能同时运行多个任务(程序)。
多线程:在同一应用程序有多个顺序流同时执行。

说了那么多,那如果创建线程呢:
java中可以有两种方式创建新的线程
1.
定义线程类实现Runnable接口:(建议使用该种方法创建线程,比较灵活)

点击(此处)折叠或打开

  1. Thread myThread = new Thread(target);//target为Runnable接口类型
  2. Runnable中只有一个方法run();
  3. public void run():
  4. When an object implementing interface Runnable is used to create a thread, starting the thread causes the object's run method to be called in that separately executing thread.The general contract of the method run is that it may take any action whatsoever.

示例:

点击(此处)折叠或打开

  1. public class Runner1 implements Runnable{

  2.     public void run(){
  3.         
  4.         for(int i=0;i<20;i++){
  5.             
  6.             System.out.println("子线程:" + i);
  7.         }
  8.     }
  9. }

  10. public class TestTread1 {
  11.   
  12.     public static void main(String[] args){
  13.         
  14.         Runner1 a = new Runner1();
  15.         Thread c = new Thread(a);
  16.          c.start();生成新的线程,不能只调用c.run()(该步只是简单的函数调用),必须调用c.start()方法
  17.         for(int i = 0;i<20;i++){
  18.             
  19.             System.out.println("Main线程:" + i);
  20.         }
  21.     }
  22. }
示意图大致表示上面的执行过程:
                                                                                   
观察打印结果,子线程和主线程输出结果间隔几乎一样,但是如果上面不调用start()方法,而只调用run()方法,则结果是子线程执行完,主线程再执行,用下面的示意图大致表示:(执行顺序是粗箭头 --->细)
                                                                                        
2.
可以定义一个Thread的子类并重写其run方法,如:

点击(此处)折叠或打开

  1. class myThread extends Thread{

  2.  public void run(){
  3.      ......
  4.      ......
  5.    }
  6. }

示例:

点击(此处)折叠或打开

  1.    public class Runner1 extends Thread{
  2.     public void run(){
  3.         
  4.         for(int i=0;i<20;i++){
  5.             
  6.             System.out.println("子线程:" + i);
  7.         }
  8.     }
  9. }

  10. public class TestTread1 {
  11.   
  12.     public static void main(String[] args){
  13.         
  14.         Runner1 c = new Runner1();
  15.          c.start(); //同理,如果生成新的线程,必须调用start()方法
  16.         for(int i = 0;i<20;i++){
  17.             
  18.             System.out.println("Main线程:" + i);
  19.         }
  20.     }
  21. }

上面注意的是,java的线程是通过java.lang.Thread类来实现的,JVM启动时会有一个由主方法(public static void main(){})所定义的主线程。

既然创建了线程,那我们如何控制线程呢,下面简单的介绍控制线程的几种基本方法:(更多方法参考API文档http://docs.oracle.com/javase/8/docs/api/index.html)

interrupt():中断线程。还可以用stop()终止线程,但这不是一个好的选择,不建议使用
getPriority():获得线程的优先级数值
setPriority():设置线程的优先级数值
isAlive():判断线程是否还“活着”,即线程是否还未终止

Thread.sleep():将当前线程睡眠指定为毫秒数
yield():让出cpu,当前线程进入就绪队列等候调度
join():调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行

wait():当前线程进入就绪队列等待调度
notify()/notifyAll():唤醒对象的wait pool中的一个/所有等待线程

其中不免涉及到线程的状态转换,下面用示意图表示:
                                                                                                                                                                                                                              
通过上图可以直观的明白,创建了线程后,线程并不是立刻进入运行状态,而是就绪状态,通过cpu调度,分给了该线程一时间片后,进入运行状态。



sleep/join/yield方法:

点击(此处)折叠或打开

  1. public class SleepThread extends Thread {
  2.    
  3.     public void run(){
  4.         
  5.         for(int i =64;i<75;i++){
  6.         System.out.println("子线程:"+(char)i);
  7.         try{
  8.         Thread.sleep(1000);//Thread在哪个线程里,就指该线程。每隔1s,子线程打印一个字符
  9.         }catch(InterruptedException e){
  10.             e.printStackTrace();
  11.         }
  12.      }
  13.     }
  14.     @SuppressWarnings("static-access")
  15.     public static void main(String[] args){
  16.         
  17.         SleepThread sl = new SleepThread();
  18.         sl.start();
  19.         for(int i=0;i<10;i++){
  20.          System.out.println("主线程:"+i);
  21.         }
  22.     }
  23. }

点击(此处)折叠或打开

  1. public class SleepThread extends Thread {
  2.    
  3.     public void run(){
  4.         
  5.         for(int i =64;i<75;i++){
  6.         System.out.println("子线程:"+(char)i);
  7.         Thread.yield();//子线程让出cpu,让给子线程,但是让出多少时间片,有os决定
  8.      }
  9.     }
  10.     @SuppressWarnings("static-access")
  11.     public static void main(String[] args){
  12.         
  13.         SleepThread sl = new SleepThread();
  14.         sl.start();
  15.         for(int i=0;i<10;i++){
  16.          System.out.println("主线程:"+i);
  17.         }
  18.     }
  19. }

点击(此处)折叠或打开

  1. public class SleepThread extends Thread {
  2.    
  3.     public void run(){
  4.         
  5.         for(int i =64;i<75;i++){
  6.         System.out.println("子线程:"+(char)i);
  7.      }
  8.     }
  9.     @SuppressWarnings("static-access")
  10.     public static void main(String[] args){
  11.         
  12.         SleepThread sl = new SleepThread();
  13.         sl.start();
  14.         try {
  15.             sl.join(); //把子线程和主线程合并,执行过程就是子线程先执行完后,主线程再执行,该过程和直接调用sl.run()而不调用start()的结果相同
  16.         } catch (InterruptedException e) {
  17.             // TODO 自动生成的 catch 块
  18.             e.printStackTrace();
  19.         }
  20.         for(int i=0;i<10;i++){
  21.          System.out.println("主线程:"+i);
  22.         }
  23.     }
  24. }


线程的优先级别:
java提供了一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照线程的优先级别决定应调用哪个线程来执行,线程的优先级应数字表示,范围从1到10,一个线程的缺省优先级是5
Thread.MIN_PRIORITY= 1;
Thread.MAX_PRIORITY =10;
Thread.NORM_PRIORITY = 5;

点击(此处)折叠或打开

  1. public class SleepThread extends Thread {
  2.    
  3.     public void run(){
  4.         
  5.         for(int i =64;i<85;i++){
  6.         System.out.println("子线程:"+(char)i);
  7.      }
  8.     }

  9.     public static void main(String[] args){
  10.         
  11.         SleepThread sl = new SleepThread();
  12.         sl.start();
  13.         sl.setPriority(NORM_PRIORITY + 5); 
  14.         System.out.println(sl.getPriority());
  15.         for(int i=0;i<10;i++){
  16.          System.out.println("主线程:"+i);
  17.         }
  18.     }
  19. }

线程同步:

在java中,引入了对象互斥锁的概念,保证共享数据操作的完整性,每个对象都对应于一个可称为“互斥锁”的概念,每个标记保证在任一时刻,只能有一个线程
访问该对象。关键字synchronized来与对象的互斥锁联系。当某个对象synchronized修饰时,表明该对象在任一时刻只能由一个线程来访问。

synchronized的使用方法:
1.

synchronized(this){
    代码块
}
2.

synchronized public void add(String name){}

点击(此处)折叠或打开

  1. public class W {
  2.    
  3.     private static int i;

  4.     public void geti(){
  5.         
  6.         i++;
  7.         try{
  8.             Thread.sleep(8);
  9.         }catch(InterruptedException e){
  10.             
  11.             e.printStackTrace();
  12.         }
  13.         System.out.println(i);
  14.         
  15.     }
  16. }

  17. public class WW implements Runnable {
  18.      
  19.     private W w = new W();
  20.     public void run(){
  21.         
  22.         w.geti();
  23.     }
  24.     public static void main(String[] args){
  25.         
  26.         WW a = new WW();
  27.         Thread b1 = new Thread(a);
  28.         Thread b2 = new Thread(a);
  29.         b1.start();
  30.         b2.start();
  31.     }
  32. }
运行结果:

这和我们预期的结果不一样!这是怎么回事呢?上述代码是两个线程访问同一对象,然后打印出i的值,由于i是静态的,每次访问时,geti()中都会使i的值增加1.
当第一个线程访问时,i的值增加了1,此时线程1睡眠,这时线程2访问该对象,i的值有增加1,此时i为2,线程2睡眠,线程1醒过来后,打印出i,线程2也是。所以造成了上面这种错误。也就是线程1在访问对象时,被线程2打断了,导致错误了发生。有什么方法避免出现这种错误呢,那就是利用锁机制:

点击(此处)折叠或打开

  1. public class W {
  2.    
  3.     private static int i;

  4.     public synchronized void geti(){
  5.         //synchronized(this){
  6.         i++;
  7.         try{
  8.             Thread.sleep(8);
  9.         }catch(InterruptedException e){
  10.             
  11.             e.printStackTrace();
  12.         }
  13.         System.out.println(i);
  14.      }
  15.     //}
  16. }
运行结果:


本文中只是简单的介绍了多线程比较基础的知识,尤其线程同步是非常重要的内容,比如生产者与消费者问题涉及到多线程的一些综合性知识,后续文章也会更新。文章用来学习总结,如有错误不当之处,请读者指正!谢谢!
0 0
原创粉丝点击