java多线程

1. 线程与进程

涉及到线程必须提到进程,先看下百度百科的解释:

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。

进程是先于线程先提出的,看下线程是如何引入的:60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。

因此在80年代,出现了能独立运行的基本单位——线程(Threads)。

线程百度百科的解释:

线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

       初看百度百科进程的解释很是晦涩,看下面一张图片:

      

 

       可以看出,当前正在运行的某一个程序就是一个进程,也就是图中exe程序,

也可以看出,进程是操作系统控制的基本单元。线程是在进程当中独立运行的子任务。比如QQ的exe程序运行时,就会有很多子任务(线程)需要执行。

2. 多线程

进程是线程的容器,进程中允许多个线程同时存在,并发执行不同的任务,从而涉及到了多线程。多线程定义:在单个程序中同时运行多个线程完成不同的工作。

为什么要使用多线程?

举个例子,假如在用QQ进行聊天时,同时还在传输文件,并进行语音聊天。如果在单任务系统当中,是不可能实现上述操作的。单任务系统的特点就是排队执行,也就是同步。上述任务不能同时并发执行。这样cpu的利用率会大大降低。然而多线程就能解决这种弊端,这也是多线程的优势。

3. 使用多线程

3.1实现多线程的方式。

1)继承Thread类。

示例:

 
 class MyThread extends Thread{  
     private int ticket=20;  
     public void run(){
         for(int i=0;i<20;i++){
             if(this.ticket>0){
               System.out.println(this.getName()+" 卖票:ticket"+this.ticket--);
             }
         }
     }
 };
 
 public class ThreadTest {  
     public static void main(String[] args) {  
        
         MyThread t1=new MyThread();
         MyThread t2=new MyThread();
         MyThread t3=new MyThread();
         t1.start();
         t2.start();
         t3.start();
     }  

2)实现Runnable接口。

示例:

 
 class MyThread implements Runnable{  
     private int ticket=10;  
     public void run(){
         for(int i=0;i<20;i++){
             if(this.ticket>0){
                 System.out.println(Thread.currentThread().getName()+" 卖票:ticket"+this.ticket--);
             }
         }
     }
 };
 
 public class RunnableTest {  
     public static void main(String[] args) {  
         MyThread mt=new MyThread();
 
        
         Thread t1=new Thread(mt);
         Thread t2=new Thread(mt);
         Thread t3=new Thread(mt);
         t1.start();
         t2.start();
         t3.start();
     }  
 }

小结:Thread 是类,而Runnable是接口;Thread本身是实现了Runnable接口的类。我们知道java是不支持多重继承的。但是可以实现多个接口。为了更好的扩展性,建议使用实现Runnable接口。

   注意:

(1)同一个对象多次调用start()方法,会报异常。

2)使用多线程时,代码执行的结果与代码的执行顺序或调用顺序是无关的。

3)执行start()方法的顺序,并不是线程启动的先后顺序。也就是线程的启动具有随机性。

3.2 实例变量与线程安全

用到多线程的时候,经常谈论到一个问题就是线程安全,那么什么是线程安全,什么是线程不安全,看下面的示例是如何引出线程安全的问题的。

自定义线程当中的示例针对其它线程有共享和不共享之分,这个在多线程进行交互时,是一个非常重要的一个技术点。

(1) 不共享数据的情况。

  public class MyThread extends Thread {

    private int count = 5;
    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("由" + this.currentThread().getName() + "计算,Count =" + count);
    }
}

运行类的代码如下:

MyThread a =new MyThread ();
MyThread b =new MyThread ();
MyThread c =new MyThread ();
a.start();
b.start();
c.start();

运行上边的程序可以得到,每个线程的变量各自都减少自己count的变量值,这

样就是变量不共享的情况。并不存在多个线程访问同一个实例变量的情况,也不会产生非线程安全性问题。

(2) 共享数据的情况。

public class ThreadB extends Thread {

    private int count = 5;

    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("由" + this.currentThread().getName() + "计算,Count =" + count);
    }
}

运行类的代码如下:

ThreadB threadB =new ThreadB();
Thread a =new Thread(threadB,"A");
Thread b =new Thread(threadB,"B");
Thread c =new Thread(threadB,"C");
Thread d =new Thread(threadB,"D");
Thread e =new Thread(threadB,"E");
a.start();
b.start();
c.start();
d.start();
e.start();

共享情况下就是多个线程同时访问同一个变量,这样的情况下就会产生非线程安全的问题。先看count--;这一行代码,因为在某些jvm当中count—并不是原子性的操作,一般分下面三步来执行:

(1) 取得原有的count值。

(2) 计算count-1.

(3) 对count进行赋值。

在这三个步骤当中,如果多线程同时执行这一段代码,必然会出现非线程安全的问题。非线程安全主要是值多个线程对同一对象中的同一个实例变量操作时会出现值被更改,变量值不同步的情况,进而影响程序的执行流程。解决这个问题就需要使多个线程之间进行同步,可以使用synchronized关键字,使多线程执行run方法的时候,以排队的方式进行处理。

3.3 Thread类里相关的API

(1) currentThread()

currentThread()方法可以返回代码段正在被哪个线程调用的信息。注意体会上面的关键字,代码段被哪个线程调用。

(2) isLive()

判断当前的线程是否处于活动状态。,这里需要注意一种情况,就是用Thread. currentThread().isLive()和线程当中调用this.isLive()方法的区别。

(3) sleep()

方法sleep()的作用是让当前执行的线程暂停执行,进行休眠。

(4) getId()

getId()方法的作用是取得线程唯一的标识。

(5) stop()和suspend()

停止线程和暂停线程都是非线程安全的,方法早已被废弃。

3.4 停止线程

3.4.1

既然stop()和suspend()方法都已经被废弃,我们该如何实现线程停止的功能呢?这里就涉及到interrupt()方法的调用。调用interrupt()方法后,线程并没有真正的停止,而是在当前的线程当中,打了一个停止的标记。那么如何判断线程是否是停止的状态呢?Thread类里提供了两个方法:

(1) this.interrupted();测试当前线程是否中断,当前线程是指运行this.interrupted()方法的线程。线程的中断状态由该方法清除。如果连续调用该方法,第二次将返回false。也就是interrupted()方法具有清除状态的功能。

(2) this.isinterrupted();测试线程Thread对象是否是中断状态,但不清楚状态标志。

3.4.2 能停止的线程异常法

看一个示例:

public class ThreadE extends Thread {
    @Override
    public void run() {
        super.run();
        try {
            for (int i = 0; i < 5000; i++) {
                if (this.interrupted()) {
                    System.out.println("已经是停止状态!我要退出了!!");
                    throw new InterruptedException();
                }
                System.out.println("i=:" + (i + 1));
            }
            System.out.println("我被输出,如果在for循环后面继续运行,该线程并未停止");
        } catch (InterruptedException e) {
            System.out.println("进入run方法的Catch了");
            e.printStackTrace();
        }
    }
}

看主线程的方法:

try {
    ThreadE threadE = new ThreadE();
    threadE.start();
    System.out.println("leading "+"睡眠开始");
    Thread.sleep(10);
    System.out.println("leading "+"睡眠结束");
    threadE.interrupt();
} catch (InterruptedException e) {
    System.out.println("main  catch");
    e.printStackTrace();
}

运行上面的程序,可以看到,线程可以被停止。

注意:如果在沉睡当中停止线程,或者先停止线程在进入睡眠,都会进入异常代码块。另外使用return也可以停止线程,但是还是建议用抛异常的方法来停止线程,因为catch语句当中还可以把异常向上抛,使线程停止的事件得以传播。

3.5 yield方法

yield()方法的作用是放弃当前CPU的资源,将它让给其他的任务去占用cpu执行的时间。但放弃的时间不确定,可能刚放弃,又立马获取到了CPU执行的时间片。

public class ThreadI extends Thread {


    @Override
    public void run() {
        super.run();
        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 500000; i++) {
            Thread.yield();
            count = count + (i + 1);
        }
        long endTime = System.currentTimeMillis();
        long cha = endTime - beginTime;
        System.out.println("用时:" + cha + "毫秒");
    }
}

将  Thread.yield();方法注释和解开分别运行,会得到两个不同的执行时间,可以看到解开的时候,运行速度会明显变慢,这说明Thread.yield()方法cpu的资源让出来,导致执行速度变慢。

3.6 线程的优先级

在操作系统当中线程可以划分优先级,优先级较高的线程得到cpu的资源也比较多,也就是cpu优先执行优先级比较高的线程当中的任务。线程的优先级具有以下几个特点:

(1) 线程优先级的继承性;

(2) 优先级具有规则性

(3) 优先级具有随机性

3.7 守护线程

守护线程是一种特殊的线程,当进程中没不存在非守护线程了,则守护线程自动销毁。典型的守护线程就是垃圾回收线程,当进程当中没有非守护线程了,则垃圾辉县的线程也就没有存在的必要了,自动销毁。可以调用setDaemon()方法来设置线程是否是当前线程的守护线程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值