Java多线程编程技术之入门基础

一、进程与线程:

1、进程:
操作系统结构的基础,是一次程序的执行,是系统进行资源分配以及调度的一个独立单元;想要直观具体地理解进程到底是什么,可以打开电脑的任务管理器,在进程选项下,有许多以.exe结尾的文件正在操作系统中运行,这些运行着的exe文件都可以称之为进程;
2、线程:
进程中独立运行的子任务;如QQ.exe运行时对应的进程中,同时(这里的“同时”其实不是绝对意义上的同时,实际上是顺序执行,但是由于CPU在这些子任务执行的切换上速度很快,给人感觉是“同时”执行)运行着多个子任务,这些子任务包含下载文件子任务、视频聊天子任务以及发送文字信息子任务等等,这些子任务都可以称之为线程;
3、线程与进程之间的关系:
进程中至少包含一个及以上的线程,进程的运行中包含多个子线程的运行;
4、多线程技术的优势:
在程序中使用多线程,相当于使用异步执行机制,可以实现异步处理不同任务的效果。使用多线程,可以在CPU处于空闲状态时,继续去执行其他任务,从而最大限度地利用CPU资源,从而解决了同步执行程序所带来的CPU资源浪费的问题;
5、使用多线程技术时需要牢记在心的点:
多线程的执行是异步的,不可以将程序中代码的顺序看做是线程执行的顺序,线程执行的顺序是由随机分配的;

二、使用多线程技术的一般顺序:

①线程类的创建
②线程对象的实例化
③线程对象相关属性的配置
④线程对象的准备
⑤线程对象的启动(运行)
⑥线程对象的销毁

1、线程类的创建:
途径一:通过继承Thread类,创建自己的线程类
示例代码:

public class MyThread extends Thread
{
    @Override
    public void run()
    {
        //线程所要完成的任务对应的代码
    }
 }

途径二:将实现Runnable接口的类的实例作为构造参数传递给Thread类的对应构造函数,进行线程对象的获取;
示例代码:

public class MyRunnable implements Runnable
{
    @Override
    public void run()
    {
        //线程需要完成的任务对应的代码
    }
}

两种途径的比较:
本质上没有区别,但是在Java中,由于Java的单继承特性,导致如果使用途径一——继承Thread类,那么该线程类将不能再继承其他的类,这就限制了线程类的扩展性;如果采用途径二——实现Runnable接口,则可以避免这种局限性,使得线程类既可以继承其他类又可以实现为线程类,从而使线程类具有更好的扩展性和灵活性;

2、线程对象的实例化
通过调用对应的构造函数即可实例化线程对象;

途径一对应的构造函数有:

Thread()//分配新的Thread类的对象
Thread(String name)//将分配的对象取名为name
Thread(ThreadGroup group,String name)//将分配的对象取名为name,并放入线程组group中

途径二对应的构造函数有:

Thread(Runnable targrt)//分配新的Thread类的对象
Thread(Runnable target , String name)//将分配的对象取名为name
Thread(ThreadGroup group,Runnable target)//将分配的对象放入线程组group中
Thread(ThreadGroup group,Runnable target,String name)//将分配的对象放入线程组group中,并取名为name

补充:采用途径二获取线程对象,需要通过将实现了Runnable接口的类的实例对象传入Thread类的对应的构造函数中;
注意:由于Thread类本身已经实现了Runnable接口,所以可以将Thread类的对象实例放入Thread类的对应构造函数中,从而得到线程对象;

3、线程对象相关属性的配置
线程对象经常配置的相关属性有:线程名、线程优先级

4、线程对象的准备
线程对象的准备很简单,当线程对象实例化之后,通过调用线程对象的start()方法就完成了线程对象的准备工作;调用start()方法相当于通知线程规划器,此线程已经准备就绪,分配一个执行时间,来执行线程对象中的run()方法,完成此线程的任务;

5、线程对象的启动(运行)
线程对象的启动需要调用线程对象的run()方法;调用run()方法之后,线程进行运行状态,执行线程的任务代码;
线程对象实例化之后,并不会自动执行线程中的run()方法,一般执行线程对象中的run()方法有两种方法:一种是线程对象直接调用run()方法;另一种是线程对象调用start方法,通知线程规划器此线程已准备就绪,由CPU分配指定一个时间执行run()方法;

下面对这两种方法比较分析(这里假定线程对象为线程t):
第一种方法属于线程对象直接调用run()方法,此时run()方法的执行并不是在线程t中执行的,而是在执行t.run();这句代码的线程中执行的;因此,属于顺序执行,并不会实现线程之间的异步执行效果;
第二种方法中,由线程t调用start()方法,然后由线程规划器来指定run()方法的执行时间,因此,run()方法的执行实在线程t中执行的,线程t中的run()方法的执行与执行t.start();这句代码的线程中的代码属于异步执行;
如下面代码中:

package com.chapter.one;
public class MyRunnable implements Runnable 
{
    @Override
    public void run()   
    {  
        System.out.println("ThreadName="+ 
        Thread.currentThread().getName()+"执行完毕");
    }
    public static void main(String[] args) 
    {
        MyRunnable myrunnable = new MyRunnable();
        Thread thread = new Thread(myrunnable);
        thread.run();//位置1
        //thread.start();//位置2
        System.out.println("ThreadName="+
        Thread.currentThread().getName()+"执行完毕");
    }
}

执行结果如下图所示:
这里写图片描述

如果将位置1处的代码注释,位置2处的代码取消注释,执行结果如下图所示:
这里写图片描述
通过上面不同的代码的执行效果可见,两种执行run()方法的结果是大大不同的;其中线程名为main的线程是由JVM创建的,该线程会调用public static void main(String[] args)方法,这里的线程名main与main方法无关系;线程thread的线程名为Thread-0,是默认设置的

6、线程对象的销毁
线程对象的销毁分两种情况:正常销毁和意外销毁;
正常销毁:当线程正常执行完run()方法中的代码之后,线程对象被销毁,即属于正常销毁;
意外销毁:当线程在执行run()方法的过程中,遇到特殊情况,导致线程对象提前被销毁,即意外销毁;

三、与线程有关的常用方法

①currentThread()方法
②isAlive()方法
③sleep()方法
④getId()方法
⑤yield()方法
1、public static Thread currentThread():可以返回调用当前代码段的线程对象,此方法为静态方法,可以直接使用Thread类进行调用;
2、public final boolean isAlive():返回调用此方法的线程是否处于活动状态;活动状态:线程已经被启动(调用start())但是还未销毁;线程处于正在运行或者准备运行状态,则线程是处于活动状态的,否则,该线程不是存活的;
3.public static void sleep(long millis) throws InterruptedException:是正在执行代码的线程对象进入休眠,休眠时间为millis毫秒;此方法为静态方法,并且会抛出异常;(此线程如果拥有锁,则休眠期间不会释放锁)
4、public long getId():返回调用此方法的线程对象的ID;
5、yield():放弃当前的CPU资源,给其他任务去占用CPU执行时间;

四、线程的停止

线程的停止(终止)指的是在线程处理完任务之前停掉正在进行的操作;

线程的停止一般分为三种情况:
①正常停止:当线程执行完run()方法中的代码,线程终止;
②强行终止(stop()方法):调用stop()方法进行线程的终止是不安全的,该方法已经作废过期;因为强制让线程停止,一方面有可能使一些请理性的工作没有完成,另一方面,会对锁定的对象进行解锁;导致数据不能同步;
③使用interrupt()方法中断线程:interrupt()方法并不会直接终止线程;需要与一个判断语句配合使用,才能终止线程;

在多线程编程开发中,较为常用的是第三种方法,下面详细分析第三种方法的使用:
首先,interrupt方法不会直接终止线程,而是对线程的状态进行了标记,这一标记可以被用到线程的终止上;
其次,如果使用第三种方法进行终止线程,则必须用到判断线程对象是否处于停止状态的方法:public boolean isInterrupted()和public static boolean interrupted();
public boolean isInterrupted():判断调用此方法的线程是否已经调用interrupt()方法而处于中止状态;此方法不改变此线程的中止状态;
public static boolean interrupted();判断当前执行代码块的线程是否处于中止状态;此方法的调用会将线程的中止状态置为false;
最后,想要终止线程对象中run()方法中代码的执行,只需要在run()方法中加入判断此线程是否终止的控制语句,然后,在线程启动之后,对该线程调用interrupt()方法即可终止该线程;
一、采用interrupt()方法+if判断语句+return结合使用的方式进行线程的终止
示例代码:

package com.chapter.one;
public class MyThread extends Thread {
    @Override
    public void run() 
    {
        while(true) 
        {        
            System.out.println(Thread.currentThread().
            getName()+"正在执行");
            if(this.isInterrupted()) 
            {
                System.out.println("线程被终止,退出run方法");
                return;//用于终止run方法的执行
            }
        }
    }
    public static void main(String[] args) throws  InterruptedException{
        MyThread thread = new MyThread();
        thread.start();
        Thread.sleep(1);
        thread.interrupt();
    }
}

执行结果如下图所示:
这里写图片描述
二、采用interrupt()方法+if判断语句+抛异常结合使用的方式终止线程
示例代码:

package com.chapter.one;
public class MyThread extends Thread {
    @Override
    public void run() 
    {
        try {
            while(true)
            {   
                System.out.println(Thread.
                currentThread().getName()+"正在执行");
                if(this.isInterrupted())
                {
                    System.out.println("线程被终止,
                    退出run方法");
                    throw new InterruptedException();
                }
            }
        } catch (InterruptedException e) 
        {
            System.out.println("进入run方法中的catch中");
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException
    {
        MyThread thread = new MyThread();
        thread.start();
        Thread.sleep(1);
        thread.interrupt();
    }
}

代码执行效果如下图所示:
这里写图片描述

总结:建议采用抛异常的方式终止线程,这样可以在catch语句块中继续向上抛出异常,使线程停止的时间得以传播。

猜你喜欢

转载自blog.csdn.net/boker_han/article/details/78510791
今日推荐