Java_线程

多线程

1.简介

在任何时间点,可以有多个程序同时执行,或者有多个程序逻辑同时执行的能力,称为并发执行。

1.1 线程与进程

进程(Process)指操作系统中一个独立运行的程序。进程也称任务,每个进程拥有独立的内存空间等系统资源,进程间的系统不能互用,所以进程间通信较困难。引申出多任务操作系统。cpu采用分时执行,操作系统的进程队列中每个进程执行一个时间片的时间长度(windows为20ms),保存状态后切换到另一个进程,快速切换产生并行执行的错觉。多核时增加几个进程队列。

线程(Thread)指同一个程序(进程)内部每个单独执行的流程。同一个程序中的线程之间变量是共享的,线程间数据交换变得简单。

无论线程还是进程,都是编程从串行编程(依次执行)进入到并行编程(同时执行)的领域,而在cpu内部实现的原理是按照时间片切换。

1.2 线程优势

多线程程序优势有两个:

1.提高界面程序响应速度

通过使用线程,将界面显示处理交给界面流程执行,而网络通讯交给通讯线程执行,前台、后台分别执行,可以加快整个程序执行速度。

2.充分利用系统资源

通过在一个程序内部同时执行多个流程,可以充分利用cpu等系统资源,从而最大限度发挥硬件的性能。

任何事情都有正反两面,多线程带来不便之处是提高编程难度,同时线程过多时会影响程序执行效率。通过线程池技术可以改善线程过多及回收问题。

1.3线程生命周期

线程在程序中从出现到消亡的各个阶段,在程序中统称为线程的生命周期。

Java中线程类是java.lang.Thread。它封装了线程的概念,并实现部分线程控制方法。

线程的生命周期包括如下阶段:

1.新建状态(New)

线程对象创建完成,但还没有启动。

2.运行状态(Run)

运行状态指线程的正常执行状态,处于该状态的线程在cpu内部执行程序,也就是线程正常运行时的状态

3.阻塞状态(Block)

阻塞状态指线程处于执行状态,但没有获得cpu执行时间,处于cpu外部等待线程执行的状态。

4.死亡状态(Dead)

死亡状态指线程执行结束,释放线程占用的系统资源,结束线程执行的状态。

实际使用中,首先创建一个线程对象,这时线程就处于New新建状态;通过线程对象中单start()方法,使线程进入Run执行状态,开始排队进入cpu执行;根据系统的调度,可能需要线程进入Block阻塞状态,即Run与Block的切换,可能多次;线程执行完毕后即进入Dead死亡状态。

线程执行中,可能需要改变线程的状态。使用线程对象的interrupt()方法中断线程执行,直接进入Dead死亡状态;而yield()方法使线程从运行状态进入阻塞状态。

2.线程实现方式

三种线程实现方式,分别是集成Thread类,实现Runnable接口,Time与TimeTask共用。第一种集成Thread类后不能继承其它类,不利于扩展。第二种常用。

2.1继承Thread类

一个类继承Thread类,则该类就具备了多线程的能力,则该类就可以以多线程的方式进行执行。但是由于Java中类的继承是单重继承,所以该方式受到一定的限制,下面演示这种用法。先定义一个线程类继承Thread,线程代码在run()方法中编写。之后在main方法中创建线程对象,然后用start()方法启动,这样就有两个线程:系统线程和类线程。

package Thread;

 publicclass ThreadClass {

    publicstaticvoid main(String[] args){

       ThreadTest tt=new ThreadTest();

       tt.start();

       for(int i=0;i<10;i++){

           //延时1

           try {

              Thread.sleep(1000);

              System.out.println("main:_____"+i);

           } catch (InterruptedException e) {

              e.printStackTrace();

       }

    }

  }

}

 class ThreadTestextends Thread{

    publicvoid run(){

       try{

           for(int i=0;i<10;i++){

              //延时1

              Thread.sleep(1000);

              System.out.println("test: "+i);

           }

       }catch (Exception e) {

       }

    }

}

2.2实现Runnable接口

通过实现java.lang.Runnable接口,重写run()方法,类也可以实现线程能力。Java中类可以实现多个接口,所以通用性比继承Thread类要好。

package Thread;

 

publicclass RunnableClass {

    publicstaticvoid main(String[] args){

       //通过Runnable类生成Thread

       RunnableTest t1=new RunnableTest();

       Thread thread=new Thread(t1);

      //线程启动

       thread.start();

       for(int i=0;i<10;i++){

           try {

              Thread.sleep(1000);

           } catch (InterruptedException e) {

              //TODO Auto-generated catch block

              e.printStackTrace();

           }

           System.out.println("main:"+i);

       }

    }

}

class RunnableTestimplements Runnable{

    publicvoid run(){

       try{

           for(int i=0;i<10;i++){

              Thread.sleep(1000);

              System.out.println("thread:"+i);

           }

       }catch (Exception e) {

       }

    }

}

 

2.3使用Timer和TimerTask组合

Timer类实现的是类似闹钟的功能,也就是定时或者每隔一定时间触发一次线程,它用来调用其它线程。

TimerTask类是抽象类,实现了Runnable接口,该类具备多线程能力。

这种方式中,建立继承TimerTask的线程类,从而间接实现Runnable接口,将多线程执行放在run()方法内。然后通过Timer对象的schedule(a,b)方法启动。

package Thread;

 import java.util.Timer;

import java.util.TimerTask;

 

publicclass Timer_TimerTask_test {

    publicstaticvoid main(String[] args){

       //create Timer

       Timer t=new Timer();

       //create TimerTask

       MyTimerTask mtt=new MyTimerTask("thread 1 :");

       //launch thread,no delay

       t.schedule(mtt, 0);

    }

}

class MyTimerTaskextends TimerTask{

    String string;

    public MyTimerTask(String string){

       this.string=string;

    }

    @Override

    publicvoid run() {

       try{

           for(int i=0;i<10;i++){

              Thread.sleep(1000);

              System.out.println(string+i);

           }

       }catch (Exception e) {

       }  

    }

}

该类汇总,MyTimerTask类实现了多线程,通过Timer对象t启动。如果想启动多个实例,应该建立多个Timer对象,每个对象启动一个线程类。schedule()方法可以定时启动,可以哪天哪时,延迟多少时间启动都可以。

3.线程实例

3.1定时炸弹

多线程模拟定时炸弹功能。在程序启动以后进行倒计时,当10秒后程序结束,程序运行时可以在控制台输入q控制炸弹线程停止。

在该例中,开启一个系统线程(main方法所在的线程),该线程的作用是启动模拟定时炸弹的线程,并且在控制台接收用户的输入,并判断输入的内容是否为q,如果是则结束模拟定时炸弹的线程,程序结束。代码如下:

package Thread;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

publicclass TimeBomb1 {

    publicstaticvoid main(String[] args){

       BombThread r1=new BombThread();

       Thread t1=new Thread(r1);

        t1.start();

       System.out.println("bomb start to ...");

       BufferedReader br1=new BufferedReader(new InputStreamReader(System.in));

       String str;

       try {

           while(true){

              System.out.println("enter q to end bomb.");

             

              str=br1.readLine();//控制台输入

              if(str.endsWith("q")){

                  r1.setStatus();//结束线程

                  System.out.println("end Success!");

                  break;//结束循环

              }

           }

       } catch (IOException e) {

           e.printStackTrace();

       }

    }

}

 

//bomb

class BombThread implements Runnable{

    privateintn; //爆炸时间

    private Boolean status; //线程终止标志位

    public BombThread(){

       this.n=10;

       this.status=false;

    }

    //终止方法

    publicvoid setStatus(){

       this.status=true;

    }

    @Override

    publicvoid run() {

       System.out.println("炸弹要爆炸了,run!");

       try{

           while(!status){

              Thread.sleep(1000);//延迟1

              System.out.println("剩余时间:"+n);

              if(n<=0){

                  status=true;//结束线程

                  System.out.println("炸了!");

                  break;

              }

              n--;//时间减1

           }

       }catch (Exception e) {

       }

    }

}

系统线程启动炸弹线程,并接收用户控制台输入,而IO中readLine()是阻塞方法,阻止系统线程的执行,等待用户输入。炸弹类每个1秒输出剩余时间,时间为0时结束线程。这样两个线程就同时工作了,系统线程等待用户输入的同时,模拟定时炸弹的线程继续执行,这样程序就包含了两个同时执行的线程。

关于线程的结束,这里用到线程自然死亡的方式。实际控制线程时,当线程的run()方法执行结束则线程自然死亡,这里通过控制status变量使得线程可以自然结束。

3.2模拟网络数据发送

实际的网络程序开发中,由于网络通讯一般都需要消耗时间,所以网络通讯的内容一般都启动专门的线程进行处理。简单的网络通信程序包括两个进程:处理界面绘制和接收用户输入的系统线程,网络通讯线程。

package Thread;

import java.io.BufferedReader;

import java.io.InputStreamReader;

publicclass netdatasend {

    publicstaticvoid main(String[] args){

       BufferedReader br2;

       String info2;

       datasend ds;

       try {

           //循环读取并启动线程发送,直到quit放弃输入

           while(true){

              br2=new BufferedReader(new InputStreamReader(System.in));

              info2=br2.readLine();

              //放弃输入,跳出循环

              if(info2.equals("quit")){

                  break;

              }

              //启动线程类,发送数据

              ds=new datasend(info2);

              Thread thread=new Thread(ds);

              thread.start();

           }

       } catch (Exception e) {

       }

       System.out.println("quit++++++++++++++++++++quit");

    }

}

class datasend implements Runnable{

    String string;

    public datasend(String string){

       this.string=string;

    }

    publicvoid run(){

       try {

           //模拟的发送延迟,时间长的话,可以多次输入,共同发送

           Thread.sleep(1000);

           System.out.println("start to send"+string+"______end");

          

       } catch (InterruptedException e) {

           e.printStackTrace();

       }

    }

}

netdatasend类实现接收控制台输入,并在接收到用户输入后启动网络通信线程datasend发送数据,当输入为quit时,结束程序。

4.多线程问题:同步、死锁和优先级

多线程虽然简化程序开发,但也带来了不便之处。核心是如果多个线程同时访问一个资源,例如变量、文件等,如何保证访问安全的问题。在多线程中,会被多个线程同时访问的资源叫做临界资源。

下例演示多个线程访问临界资源时产生的问题。启动两个线程类DataThread的对象,该线程每隔200ms输出临界资源Data类的变量n的值,并将n的值减1。即两个线程类都访问Data对象,并对其属性n进行输出和减1操作。

package Thread;

publicclass multiThread_sameData {

    publicstaticvoid main(String[] args){

       Data data=new Data();

       DataThread dataThread=new DataThread(data,"线程1");

       DataThread dataThread2=new DataThread(data, "线程2");

       Thread thread=new Thread(dataThread);

       Thread thread2=new Thread(dataThread2);

       thread.start();

       thread2.start();

    }

}

class Data{

    privateintn;

    public Data(){

       this.n=20;

    }

    //同步区块,防止线程抢夺资源。

    publicsynchronizedvoid show(String name){

    System.out.println(name+": "+n);

    n--;

    }

}

class DataThread implements Runnable{

    private Data data;

    private String name;

    public DataThread(Data data,String name){

       this.data=data;

       this.name=name;

    }

    publicvoid run(){

       try {

           for(int i=0;i<10;i++){

              Thread.sleep(200);

              data.show(name);

           }

       } catch (Exception e) {

       }

    }

}

 

4.1同步

4.2死锁

4.3优先级

4.4总结

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值