线程概念

进程:启动一个程序就是一个进程。
线程:在一个程序里面,多个事情同步进行,这个事情是由线程来完成

不使用多线程的效果

如果我们不使用线程,会怎么样呢?看下面代码

新建立一个hero类包含英雄的name,血量,攻击力,内置一个攻击方法

package charactor;
import java.io.Serializable;
public class Hero{
    public String name;
    public float hp; 
    public int damage;
    public void attackHero(Hero h) {
        try {
            //为了表示攻击需要时间,每次攻击暂停1000毫秒
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        h.hp-=damage;
        System.out.format("%s 正在攻击 %s, %s的血变成了 %.0f%n",name,h.name,h.name,h.hp);
        if(h.isDead())
            System.out.println(h.name +"死了!");
    }
    public boolean isDead() {
        return 0>=hp?true:false;
    }
}

再编写一个主函数,new三个英雄,我们实现盖伦攻击提莫与盲僧

package multiplethread;
import charactor.Hero;
public class TestThread {
    public static void main(String[] args) { 
        Hero gareen = new Hero();
        gareen.name = "盖伦";
        gareen.hp = 616;
        gareen.damage = 50;

        Hero teemo = new Hero();
        teemo.name = "提莫";
        teemo.hp = 300;
        teemo.damage = 30;
                  
        Hero leesin = new Hero();
        leesin.name = "盲僧";
        leesin.hp = 455;
        leesin.damage = 80;
         
        Hero bh = new Hero();
        bh.name = "赏金猎人";
        bh.hp = 500;
        bh.damage = 65;
        
        //盖伦攻击提莫
        while(!teemo.isDead()){
            gareen.attackHero(teemo);
        }
 
        //赏金猎人攻击盲僧
        while(!leesin.isDead()){
            bh.attackHero(leesin);
        }
    }
}

运行代码,发现只有先攻击完提莫,才能再次攻击盲僧

现在我们想一下,在打游戏的过程中,这种效果有点向回合制游戏,你一下我一下,只有一次攻击结束,才能执行下一次攻击,可是在LOL中,盖伦可以转圈圈开大范围伤害,是可以实现同时攻击提莫与盲僧的,这事就需要用到我们的多线程。

三种创建多线程的方法

1. 继承线程类Thread

使用多线程,就可以做到盖伦在攻击提莫的同时,赏金猎人也在攻击盲僧

设计一个类KillThread 继承Thread,并且重写run方法

package multiplethread;
import charactor.Hero;
public class KillThread extends Thread{     
    private Hero h1;
    private Hero h2;
    public KillThread(Hero h1, Hero h2){
        this.h1 = h1;
        this.h2 = h2;
    }
    public void run(){
        while(!h2.isDead()){
            h1.attackHero(h2);
        }
    }
}

启动线程办法: 实例化一个KillThread对象,并且调用其start方法

package multiplethread;
import charactor.Hero;
public class TestThread {
    public static void main(String[] args) { 
        Hero gareen = new Hero();
        gareen.name = "盖伦";
        gareen.hp = 616;
        gareen.damage = 50;
 
        Hero teemo = new Hero();
        teemo.name = "提莫";
        teemo.hp = 300;
        teemo.damage = 30;
         
        Hero bh = new Hero();
        bh.name = "赏金猎人";
        bh.hp = 500;
        bh.damage = 65;
         
        Hero leesin = new Hero();
        leesin.name = "盲僧";
        leesin.hp = 455;
        leesin.damage = 80;
         
        KillThread killThread1 = new KillThread(gareen,teemo);
        killThread1.start();
        KillThread killThread2 = new KillThread(bh,leesin);
        killThread2.start();  
    }   
}

就可以观察到 赏金猎人攻击盲僧的同时,盖伦也在攻击提莫


多线程的执行的功能都应该在run()方法中进行定义。但是run()方法是不能直接被调用的,这里涉及到操作系统的资源调度问题。要想要启动多线程必须使用start()方法完成(public void start()).
虽然调用的是start()方法,但是最终执行的是run()方法,并且所有的线程是交替执行的。

在JAVA执行过程之中考虑到对于不同层次的开发者的需求,所以其支持有本地的操作系统函数调用,而这项技术就被称为JNI(java本地接口)技术。利用这项技术可以使用一些操作系统提供的底层函数进行一些特殊处理,而在Thread类里面提供的start0()就表示需要将此方法依赖于不同的操作系统实现。

*.class在jvm中执行
任何情况下,只要定义了多线程,多线程的启动永远只有一种方案:Thread类中的start()方法。

2. 实现Runnable接口

创建类Battle,实现Runnable接口

package multiplethread;
import charactor.Hero;
public class Battle implements Runnable{
    private Hero h1;
    private Hero h2;
    public Battle(Hero h1, Hero h2){
        this.h1 = h1;
        this.h2 = h2;
    }
    public void run(){
        while(!h2.isDead()){
            h1.attackHero(h2);
        }
    }
}

启动的时候,首先创建一个Battle对象,然后再根据该battle对象创建一个线程对象,并启动

package multiplethread;
import charactor.Hero;
public class TestThread {
    public static void main(String[] args) {   
        Hero gareen = new Hero();
        gareen.name = "盖伦";
        gareen.hp = 616;
        gareen.damage = 50;
 
        Hero teemo = new Hero();
        teemo.name = "提莫";
        teemo.hp = 300;
        teemo.damage = 30;
         
        Hero bh = new Hero();
        bh.name = "赏金猎人";
        bh.hp = 500;
        bh.damage = 65;
         
        Hero leesin = new Hero();
        leesin.name = "盲僧";
        leesin.hp = 455;
        leesin.damage = 80;
         
        Battle battle1 = new Battle(gareen,teemo);
        new Thread(battle1).start();
        Battle battle2 = new Battle(bh,leesin);
        new Thread(battle2).start();
 
    }
     
}

battle1 对象实现了Runnable接口,所以有run方法,但是直接调用run方法,并不会启动一个新的线程。
必须,借助一个线程对象的start()方法,才会启动一个新的线程。
所以,在创建Thread对象的时候,把battle1作为构造方法的参数传递进去,这个线程启动的时候,就会去执行battle1.run()方法了

3. 匿名类

使用匿名类,继承Thread,重写run方法,直接在run方法中写业务代码匿名类的一个好处是可以很方便的访问外部的局部变量。
前提是外部的局部变量需要被声明为final。(JDK7以后就不需要了)

package multiplethread;
import charactor.Hero;
public class TestThread {
    public static void main(String[] args) {  
        Hero gareen = new Hero();
        gareen.name = "盖伦";
        gareen.hp = 616;
        gareen.damage = 50;
  
        Hero teemo = new Hero();
        teemo.name = "提莫";
        teemo.hp = 300;
        teemo.damage = 30;
          
        Hero bh = new Hero();
        bh.name = "赏金猎人";
        bh.hp = 500;
        bh.damage = 65;
          
        Hero leesin = new Hero();
        leesin.name = "盲僧";
        leesin.hp = 455;
        leesin.damage = 80;
          
        //匿名类
        Thread t1= new Thread(){
            public void run(){
                //匿名类中用到外部的局部变量teemo,必须把teemo声明为final
                //但是在JDK7以后,就不是必须加final的了
                while(!teemo.isDead()){
                    gareen.attackHero(teemo);
                }              
            }
        };
        t1.start();
        Thread t2= new Thread(){
            public void run(){
                while(!leesin.isDead()){
                    bh.attackHero(leesin);
                }              
            }
        };
        t2.start();    
    }   
}

Thread和Runnable方式的优缺点

采用继承Thread类方式:

  • 优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
  • 缺点:
  1. 因为线程类已经继承了Thread类,所以不能再继承其他的父类。
  2. 多个线程不能共享同一份资源;

采用实现Runnable接口方式:

  • 优点:
  1. 线程类只是实现了Runable接口,还可以继承其他的类。
  2. 在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
  • 缺点:
  1. 编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

start()和run()的区别

  • start()方法用来,开启线程,但是线程开启后并没有立即执行,他需要获取cpu的执行权才可以执行
  • run()方法是由jvm创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)