Java多线程技术研究(一)-多线程的创建及常见名词

时间:2022-02-16 15:08:44

基础做起,首先围绕Java多线程进行研究,利用博客总结自己学习的内容。
1、线程基本概念
进程:指内存中运行的应用程序,进程在执行过程中拥有独立的内存单元。当我们启动一个应用程序时,就会生成一个新的进程。进程是资源分配的最小单位。
线程:指进程中的一个执行流程,一个进程中可以包含多个线程。线程是CPU调度的最小单位。引进线程的主要目的就是为了提高资源的合理利用率。线程之间切换比进程之间切换节约系统开销。
多进程:操作系统能并行式运行多个应用程序,比如你可以同时打开word软件和Excel软件。
多线程:一个应用程序中存在多个顺序流同时运行,比如QQ软件能够同时视频聊天,打字。
2、多线程的实现方式及区别
(1)继承式(Thread)
继承Thread类,重写类Thread中的run()方法,示例如下:

package wygu.multiThread.study;

/** * @author guweiyu * */
public class MultiThreadExtends extends Thread{
    String threadName = "";

    public MultiThreadExtends(String threadName){
        this.threadName = threadName;
    }

    @Override
    public void run(){
        for(int i=0;i<5;i++){
            System.out.println(threadName+":"+i);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public static void main(String []argv){
        MultiThreadExtends threadA = new MultiThreadExtends("A");
        MultiThreadExtends threadB = new MultiThreadExtends("B");
        threadA.start();
        threadB.start();
    }

}

第一次运行结果如下:
A:0
A:1
B:0
B:1
A:2
B:2
A:3
B:3
A:4
B:4
第二次运行结果如下:
A:0
A:1
A:2
B:0
B:1
B:2
B:3
B:4
A:3
A:4
如上所示,继承Thread类,通过重写run()方法定义了一个新的线程类MultiThreadExtends,通过线程对象调用start()方法,使得该线程处于就绪状态,执行run()方法。注意此时线程并不一定能够立刻执行,这取决于CPU的调度。

(2)实现接口式(Runnable)
实现接口Runnable,重写该接口的run()方法,示例代码如下:

package wygu.multiThread.study;

/** * @author guweiyu * */
public class MultiThreadImplem implements Runnable{

    String threadName = "";

    public MultiThreadImplem(String threadName){
        this.threadName = threadName;
    }

    @Override
    public void run(){
        for(int i=0;i<5;i++){
            System.out.println(threadName+":"+i);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread threadC = new Thread(new MultiThreadImplem("C"));
        Thread threadD = new Thread(new MultiThreadImplem("D"));
        threadC.start();
        threadD.start();

    }
}

第一次运行结果:
C:0
D:0
D:1
C:1
C:2
D:2
C:3
D:3
D:4
C:4
第二次运行结果:
C:0
C:1
C:2
C:3
D:0
D:1
C:4
D:2
D:3
D:4
如上所示,实现接口Runnable,通过重写run()方法定义了一个新的Runnable类MultiThreadImplem,线程对象调用start()方法,使得该线程处于可运行的状态。我们发现上述两种方式都能够实现多线程的并发执行,其实实际上类Thread类本身也是实现了Runnable接口,run()方法最先是在Runnable接口中定义的方法。

(3)实现接口式(Callable)
实现接口Callable,重写该接口call()方法

package wygu.multiThread.study;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MultiThreadCallable implements Callable<String>{

    private String threadName;

    public MultiThreadCallable(String threadName) {
        this.threadName = threadName;
    }

    @Override
    public String call() throws Exception {
        for(int i=0;i<5;i++){
            System.out.println(threadName+":"+i);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return threadName;
    }

    public static void main(String []argv){
        MultiThreadCallable mCallableE = new MultiThreadCallable("E");
        MultiThreadCallable mCallableF = new MultiThreadCallable("F");
        FutureTask<String> futureTaskE = new FutureTask<String>(mCallableE);
        FutureTask<String> futureTaskF = new FutureTask<String>(mCallableF);
        new Thread(futureTaskE,"带返回值的线程E").start();
        new Thread(futureTaskF,"带返回值的线程F").start();
        try {
            System.out.println("线程E返回值为:"+futureTaskE.get());
            System.out.println("线程F返回值为:"+futureTaskF.get());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

第一次运行结果:
E:0
F:0
F:1
F:2
E:1
F:3
E:2
F:4
E:3
E:4
线程E返回值为:E
线程F返回值为:F
第二次运行结果:
F:0
E:0
F:1
E:1
F:2
E:2
F:3
E:3
E:4
F:4
线程E返回值为:E
线程F返回值为:F

如上所述,实现接口Callable,通过重写call()方法定义了一个新的Callable类MultiThreadCallable,相对于Runnable,Callable可以带返回值。关于Callable深层次的研究放在后面的博客中。

(4)实现接口Runnable或Callable和继承Thread的区别
1):适合多个功能相同的线程去处理同一个资源;
2):避免java中的单继承的限制,可以利用接口实现多继承特性;
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
下面举例说明利用接口Runnable实现资源的共享功能:

package wygu.multiThread.study;

public class MultiThreadShare implements Runnable{

    private volatile int breakFast=10;

    @Override
    public void run() {
        for(int i=0;breakFast>0;i++){
            System.out.println(Thread.currentThread().getName()+"---->"+breakFast--);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {
        MultiThreadShare mThreadShare = new MultiThreadShare();
        Thread threadG = new Thread(mThreadShare, "KFC窗口1");
        Thread threadH = new Thread(mThreadShare, "KFC窗口2");
        Thread threadI = new Thread(mThreadShare, "KFC窗口3");
        threadG.start();
        threadH.start();
        threadI.start();
    }

}

运行结果:
KFC窗口1—->10
KFC窗口3—->9
KFC窗口1—->8
KFC窗口3—->7
KFC窗口1—->6
KFC窗口3—->5
KFC窗口2—->4
KFC窗口2—->3
KFC窗口2—->2
KFC窗口1—->1
通过上述实例可以发现利用Runnable实现不同线程之间的资源共享。

3、线程的生命周期及状态间的转换
图片来源:http://www.cnblogs.com/lwbqqyumidi/p/3804883.html
Java多线程技术研究(一)-多线程的创建及常见名词
Java线程的生命周期状态大致可以分为以下5中状态:
1)创建状态(New):通过New关键字创建了Thread类(或其子类)的实例。
2)就绪状态(Runnable):线程除了CPU资源外,其它资源都已准备充分,正等待CPU的调度
 Thread类的实例调用start()方法,等待时间片轮转(CPU资源调度),以便获得CPU资源;
 线程在处于Running状态时,但是时间片使用完后,回到Runnable状态;
 处于Blocked状态的线程结束了当前的Blocked状态之后重新回到Runnable状态。
3)运行状态(Running):线程获得CPU的资源,正在执行run(),Running状态是所有线程 都希望获得的状态。注意,就绪状态是进入到运行状态的唯一入口。
4)阻塞状态(Blocked):处于Running状态线程,由于某种原因,比如运行时执行到wait(),获取synchronized同步锁失败,发出I/O请求。
5)死亡状态(Dead):处于Running状态的线程执行完run()方法,或者出现异常,线程就处在Dead状态,即该线程的生命周期执行结束。
 Thread类的实例调用start()方法,等待时间片轮转(CPU资源调度),以便获得CPU资源;

4、线程的常见几种方法比较
 Thread.sleep(long millis):
正在运行的线程调用该方法,该线程进入阻塞,释放CPU资源,但不释放对象锁,millis后线程自动苏醒进入Runnable状态。
 Thread.yield():
正在运行的线程调用该方法,该线程释放CPU资源,重新由Running状态转变为Runnable状。其主要作用实现让相同优先级的线程轮流执行,但并不保证一定会轮流执行。但在实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
wait():
wait()方法是Object类里的方法,当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
 thread.join()/thread.join(long millis):
join()方法实现是通过wait()方法进行线程控制的。 当main线程调用thread.join时候,main线程会获得线程实例thread的锁,调用该对象的wait(long millis),直到该对象唤醒main线程,比如退出后,线程实例thread退出后,会在native方法中调用线程对象的notifyAll()方法,然后执行main线程的后续部分代码。

package wygu.multiThread.study;

public class MultiThreadJoin implements Runnable{
    @Override
    public void run() {
        System.out.println("开始执行线程--->"+Thread.currentThread().getName());
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("结束执行线程--->"+Thread.currentThread().getName());

    }
    public static void main(String[] args) {
        System.out.println("主线程开始执行");
        MultiThreadJoin mThreadJoin = new MultiThreadJoin();
        Thread thread = new Thread(mThreadJoin, "子线程");
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("主线程结束执行");
    }

}

运行结果:
主线程开始执行
开始执行线程—>子线程
结束执行线程—>子线程
主线程结束执行
可以看出主线程是在子线程执行结束后,才真正的结束。

 notify():
notify()是Object类里的方法,唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。

5、线程的中常见的名词
在Java中有两类线程:用户线程(User Thread)守护线程(Daemon Thread)。在JVM中,只要还存在一个运行的用户线程,所有的守护线程均没有结束;只有当所有的用户线程全部结束,守护线程会随JVM的退出而消亡。换句话说,JVM中任何一个守护线程都是所有用户线程的保姆。比如,JVM中负责垃圾回收的线程(GC 垃圾回收器)就是一个守护线程。
在JAVA中,将一个线程设置成守护线程非常简单,只需要:创建一个线程实例,然后设置thread.setDaemon(true)即可将该线程设置为守护线程。
以下几点需要注意:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则程序会抛出IllegalThreadStateException异常。因为不能把一个正在运行的线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。

package com.wygu.thread.study;

public class MultiThreadDaemon implements Runnable{

    @Override
    public void run() {
        System.out.println("线程开始运行:"+Thread.currentThread().getName()); 
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程结束运行:"+Thread.currentThread().getName()); 
    }

    public static void main(String[] args) {
        System.out.println("线程开始运行:"+Thread.currentThread().getName());
        MultiThreadDaemon mThreadDaemon = new MultiThreadDaemon();
        Thread thread = new Thread(mThreadDaemon,"Dameon Thread");
        thread.setDaemon(true);
        thread.start();
        System.out.println("线程结束运行:"+Thread.currentThread().getName());
    }

}

程序运行结果:
线程开始运行:main
线程结束运行:main
线程开始运行:Dameon Thread
我们发现线程thread还没有运行结束就随着主线程的结束而结束了,如果把thread.setDaemon(true);注释掉,会发现程序运行结果为:
线程开始运行:main
线程结束运行:main
线程开始运行:Dameon Thread
线程结束运行:Dameon Thread

守护线程除了被用于垃圾回收等方面,还可以被用在网络socket通信中,维持长连接或者收发信息,比如举个简单的例子:

package com.wygu.thread.study;

public class MultiThreadDaemon implements Runnable{

    @Override
    public void run() {
        int times = 0;
        while(true){
            System.out.println("发送消息:"+times);  
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("接受消息:"+times);  
            times++;
        }
    }

    public static void main(String[] args) {
        System.out.println("线程开始运行:"+Thread.currentThread().getName());
        MultiThreadDaemon mThreadDaemon = new MultiThreadDaemon();
        Thread thread = new Thread(mThreadDaemon,"Dameon Thread");
        thread.setDaemon(true);
        thread.start();
        while(true) {
            try {
                Thread.sleep(1000 * 60 * 10);
            } catch (InterruptedException e) {
                System.out.println("Main thread sleep error!");
                System.exit(0);
            }
        }
    }

}

程序运行结果:
线程开始运行:main
发送消息:0
接受消息:0
发送消息:1
接受消息:1
发送消息:2
接受消息:2
发送消息:3
接受消息:3