java多线程实现方式

java内部支持多线程,在JDK1.0的时候就确定了基础的线程模型,在其后的多个版本都是在此基础上面的优化,多线程的实现由多种方式,最常用的是实现runnable接口创建线程

多线程
只有一个执行流程的程序被称为单线程程序,相对而言,拥有多个执行流程的程序,被称为多线程程序。

Java程序运行原理

Java使用Thread类代表线程,所有线程对象都是Thread类或者其子类的实例,new Thread()时JVM会创建一个新的线程(Thread类内部调用实现,反正就是新建了一个线程) ,调用线程对象的start()方法线程启动(java线程启动的固定实现方式,直接调用run()方法不会启动线程,只是普通的方法调用),main方法运行在主线程中,main线程也是守护线程,在程序完全运行结束后才结束,即从头跑到尾的线程,在Thread启动之前的所有程序都是单线程的。

java虚拟机是多线程的,因为除了主线程外,还有垃圾回收线程

线程分类

用户线程就是主线程,用户线程结束完JVM就会停止。

后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。
用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束。

前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。

当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。

线程的创建和启动

Java使用Thread类代表线程,所有线程对象都是Thread类或者其子类的实例。创建线程的方式有四种,分别是:
1)继承Thread类创建线程;
2)实现Runnable接口创建线程;
3)利用Runnable内部类创建线程;
4)使用Callable和Future创建线程。

以上四种方式均可以创建线程,不过它们各有优劣,我将在分别叙述完每一种创建线程的方式后总结概括。

   3.1 继承Thread类创建线程
   主要步骤为:

   ① 定义一个类并继承Thread类,此类中需要重写Thread类中的run()方法,这个run()方法就是多线程都需要执行的方法;整个run()方法也叫做线程执行体;

   ② 创建此类的实例(对象),这时就创建了线程对象;

   ③ 调用线程对象的start()方法来启动该线程。

   举例说明:
public class MyThread1 extends Thread {
    private int a;
    @Override
    public void run(){
        for(;a<100;a++){
            //获取线程的名称并输出
            System.out.println("Thread线程创建示例1===="+getName());
        }
    }

    public static void main(String[] args) {
        new MyThread1().start();
        new MyThread1().start();
    }

}
   上面通过一个简单的例子演示了创建线程的第一种方法(通过继承Thread类创建线程);通过运行以上代码发现有两个线程在并发执行,由于没有对线程进行显示的命名,所以系统默认这两个线程的名称为Thread-0和Thread-1。
   那么在上述例子中一共有多少个线程在运行呢?答案是三个!

   分别是main(主线程)、Thread-0和Thread-1;我们在多线程编程时一定不要忘记Java程序运行时默认的主线程,main()方法的方法体就是主线程的线程执行体;同理,run()方法就是新建线程的线程执行体。

 在程序中如果想要获取当前线程对象可以使用方法:Thread.currentThread();

  如果想要返回线程的名称,则可以使用方法:getName();

  故如果想要获取当前线程的名称可以使用以上二者的搭配形式:Thread.currentThread().getName();

  此外,还可以通过setName(String name)方法为线程设置名字;具体操作步骤是在定义线程后用线程对象调用setName()方法:

  在讨论完设置线程名称及获取线程名称的话题后,我们来分析下变量的共享。从以上代码运行结果来看,线程Thread0和线程Thread1分别输出0-99,由此可以看出,使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。

  3.2 实现Runnable接口创建线程类
  主要步骤为:

   ① 定义一个类并实现Runnable接口,重写该接口的run()方法,run()方法的方法体依旧是该线程的线程执行体;

  ② 创建定义的类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象;

  ③ 调用线程的start()方法来启动该线程。

  举例说明:
    public class MyThread2 implements Runnable {
        public static void main(String[] args)   
        {  
            MyThread2 m1 = new MyThread2();  
            Thread t1 = new Thread(m1,"线程1");  
            Thread t2 = new Thread(m1,"线程2");  
            t1.start();  
            t2.start();  
        }  
       private int a;  
        public void run()  
        {  
            for ( ; a<100 ; a++ )  
            {  
                System.out.println(Thread.currentThread().getName()+"  "+a);  
            }  
        }  
    }  
 运行上面的程序可以看出:两个子线程的i变量是连续的,也就是说采用Runnable接口的方式创建的两个线程可以共享线程类的实例属性,这是因为我们创建的两个线程共用同一个target(m1),即为两个线程持有同一个对象的锁。
  通过对以上两种创建新线程的方法进行比较分析,可以知道两种创建并启动多线程方式的区别是:通过继承Thread类创建的对象即是线程对象,而通过实现Runnable接口创建的类对象只能作为线程对象的target。

3.3 Runnable匿名内部类实现thread

public class MyThread3  {  

    private static int a;

    public static void main(String[] args) {
        new Thread(new Runnable(){
            @Override
            public void run(){
                for(;a<100;a++){
                    System.out.println("Thread线程创建示例3====");
                }
            }
        }).start();
        new MyThread3().start();
    } 
} 

实现方式同3.2 只不过采用匿名的方式

  3.4 通过Callable和Future创建线程
  Callable接口是在Java5才提出的,它是Runnable接口的增强版;它提供了一个call()方法作为线程执行体,且call()方法比run()方法更为强大,主要体现在:

  ① call()方法可以有返回值;

  ② call()方法可以申明抛出异常。

  Java5提供了Future接口来代表Callable接口里call()方法的返回值,并为Futrue接口提供一个FutureTask实现类,此实现类实现了Future接口,并且实现了Runnable接口,可以作为Thread类的target。不过需要提出的是,Callable接口有泛型限制,Callable接口里的泛型形参类型于call()方法返回值类型相同。

  主要步骤为:(创建并启动有返回值的线程)

  ① 创建Callable接口的实现类,并实现call()方法作为线程的执行体,且该call()方法有返回值;

  //不再是void

  ② 创建Callable接口实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;

  ③ 使用FutureTask对象作为Thread对象的target创建并启动新线程;

  ④ 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

  举例说明:
public class MyThread4 implements Callable<Integer> {  
    public static void main(String[] args)  
    {  
        MyThread4 m1 = new MyThread4();//创建Callable对象  
        //使用FutureTask来包装Callable对象  
        FutureTask<Integer> task = new FutureTask<Integer>(m1);  
        Thread t1 = new Thread(task,"有返回值的线程");  
        t1.start();//启动线程  
        //获取线程返回值  
        try  
        {  
            System.out.println("子线程的返回值:"+task.get());  
        }  
        catch (Exception ex)  
        {  
            ex.printStackTrace();  
        }  
    } 
    public Integer call()//返回值类型为Integer  
    {  
        int i = 0;  
        for ( ; i<100 ; i++ )  
        {  
            System.out.println(Thread.currentThread().getName()+"  "+i);  
        }  
        return i;//call()可以有返回值  
    }  
} 
  其实,创建Callable实现类与创建Runnable实现类没有太大区别,只是Callable的call()方法允许声明抛出异常,而且允许带返回值。

  3.4 四种创建线程方法的对比
  由于实现Runnable接口和实现Callable接口创建新线程方法基本一致,这里我们姑且把他们看作是同一类型;这种方式同继承Thread方式相比较,优劣分别为:

  1.采用实现Runnable接口和Callable接口的方式创建多线程

  ① 优点:

        1)实现类只是实现了接口,所以它还可以继承其他类;

        2)多个线程可以共享一个target,所以适合多线程处理同一资源的模式,从而可以将CPU、代码和数据分开,较好的体现了面向对象的思想。

  ② 缺点:

        1)编程比较复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。

        2) 采用继承Thread类的方式来创建新线程

  ① 优点:

        1)编写简单,如果需要访问当前线程,则只需要使用this即可。

  ② 缺点:

        1)因为线程已经继承了Thread类,所以不能再继承其他类。
  3.总结

  ① 综合分析,我们一般采用实现Runnable接口和实现Callable接口的方式来创建多线程,更多的时候可以采用Runnable匿名内部类方便的实现多线程。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值