Java多线程

一、多线程的概念

想要知道什么是多线程?就会引出线程的概念,而线程和进程之间又是息息相关的。

进程:操作系统中一个程序的执行周期称为一个进程。

线程:一个程序同时执行多个任务。通常,每一个任务就称为一个线程。

多线程:一个进程运行时产生了多个线程。

 1.1 进程与线程的区别

  • 进程是资源分配的最小单位,线程是程序执行的最小单位。没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。

  • 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段。
    线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

  • 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。

  • 多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

1.2 为什么要使用多线程?

1)更好的利用cpu资源

2)线程之间可以共享数据

3)创建线程代价比较小

二、线程的生命周期

在线程的生命周期中,共有五种状态 :  新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)

当一个线程启动以后,它不能一直霸占着cpu独自运行,CPU需要在多个线程之间切换,于是每个线程就会在各种状态之间转换


1. 新建状态:当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值

2. 就绪状态:当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机为其创建方法调用栈和程序计数器,等待调度运行

3. 运行状态:如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态

4. 阻塞状态:当处于运行状态的线程失去所占用资源之后,便进入阻塞状态

5.死亡状态:当线程执行完了或者因异常退出了run()的执行,该线程的生命周期就结束了

线程状态转换图:

三、实现多线程的方法 

Java实现多线程一共有三种方法,分别为:继承Thread类、实现Runnable接口、实现Callable接口

注:想要实现多线程,必须实现Runnable接口,Thread类和Collable接口都间接实现了Runnable接口

3.1 继承Thread类实现多线程

新建一个线程最简单的方法就是直接继承Thread类,覆写该类中的run()方法,然后调用线程的start()方法

//1.继承Thread类
 class MyThread extends Thread{
    private String title;
    public MyThread(String title){
        this.title=title;
    }
//2.覆写run()方法
    @Override
    public void run(){  //所有线程从此处开始执行
  for(int i=0;i<10;i++){
      System.out.println(this.title+" i="+i);
  }
    }
}
public class Test{
    public static void main(String[] args) {
        MyThread myThread1=new MyThread("thread1");
        MyThread myThread2=new MyThread("thread2");
        MyThread myThread3=new MyThread("thread3");
        //3.调用线程的start()方法
        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
     }

注意:任务中的run()方法指明如何完成这个任务。Java虚拟机会自动调用该方法,无需特意调用它,直接调用run()方法只是在同一个线程中执行该方法,而没有新线程被启动。因此必须通过start()方法来启动线程。

3.2 Runnable接口实现多线程

实现多线程最简单的方法就是继承Thread类,但是该方法有单继承局限;使用Runable接口可以解决这一问题。

范例:利用Runable接口实现线程主体类

 //1.定义类实现Runnable接口
 class MyThread implements Runnable{
    private String title;
    public MyThread(String title){
        this.title=title;
    }
    @Override
     //2.覆写Runnable接口中的run()方法
     //将线程要运行的代码放在该run方法中
    public void run(){  //所有线程从此处开始执行
  for(int i=0;i<10;i++){
      System.out.println(this.title+" i="+i);
  }
    }
}
public class Test{
    public static void main(String[] args) {
        MyThread myThread1=new MyThread("thread1");
        MyThread myThread2=new MyThread("thread2");
        MyThread myThread3=new MyThread("thread3");
        //3.通过Thread类建立线程对象
        //4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
        //5.调用Thread类的start方法开启线程
        new Thread(myThread1).start();
        new Thread(myThread2).start();
        new Thread(myThread3).start();
    }
     }

注意:使用Runnable接口实现多线程时,由于没有继承Thread类,不能直接调用start()方法,此时需要将Runnable接口的子类对象作为参数传递给Thread类的构造函数(该构造函数的参数就是Runnable对象),最后调用Thread类的start()方法,开启线程。


另外,对于此时的Runnable接口对象可以采用匿名内部类或者Lambda表达式来定义

范例1:使用匿名内部类进行Runnable对象创建

public class Test{
    public static void main(String[] args) {
        //新建Thread类、Runnable类的匿名对象
      new Thread(      //匿名内部类可以实例化接口
              new Runnable(){  //Runnable类的匿名对象作为Thread类构造方法的参数
                  @Override
          public void run(){   //Runnable类是一个接口,该类有一个抽象方法run(),
                      System.out.println("hello word");
                  }
              }
      ).start();
    }
     }

范例2:使用Lamdba表达式进行Runnable对象创建

public class Test{
    public static void main(String[] args) {
        Runnable runnable =()->System.out.println("hello word");//lamdba表达式
        new Thread(runnable).start();
    }
     }

3.3 Callable接口实现多线程

Thread类和Runnable接口中的run()方法都没有返回值,但是在某些情况下,线程执行完成后需要带回一些返回值,这时,就需要利用Callable接口来实现多线程。

范例:使用Callable接口实现多线程

//1.定义类实现Callable接口
class MyThread implements Callable<String> {
    private int ticket=10;//10张票
    @Override
    public String call()throws Exception {  //2.实现Callable接口的call()方法,该方法有返回值
        while (this.ticket > 0) {
            System.out.println("剩余票数:" + this.ticket--);
        }
        return("票卖完啦~~");
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException,ExecutionException {
   
//3.使用FutureTask类来包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值
      
  FutureTask<String> task=new FutureTask<>(new MyThread());
       
 //4.使用FutureTask对象作为Thread对象启动线程
     
 new Thread(task).start();    
 new Thread(task).start();
     
 //5.调用FutureTask对象的get()方法获取子线程执行后的返回值
    
  System.out.println(task.get());
    }
}

3.4 线程类继承结构

线程类继承结构图:

(1)根据以上继承结构图可知:Thread类是Runnable接口的子类,覆写了Runnable接口的run()方法

(2) 在多线程的处理上,使用的是代理设计模式:Runnable接口定义了相关协议,Thread类是代理类,MyThread类则是目标实现类。

3.5 三种实现多线程方法对比 

区别局限性共享性返回值
Thread类单继承/无返回值
Runnable接口/多个线程共享一个target对象无返回值
Callable接口/多个线程共享一个target对象有返回值

范例1:使用Thread类实现数据共享

class MyThread extends Thread{
    private int ticket=10;
    @Override
    public void run(){
        while (this.ticket>0){
            System.out.println("剩余票数"+this.ticket--);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        new MyThread().start();
        new MyThread().start();
       
    }
}

结果:
剩余票数10
剩余票数10
剩余票数9
剩余票数8
剩余票数7
剩余票数6
剩余票数5
剩余票数9
剩余票数4
剩余票数8
剩余票数3
剩余票数2
剩余票数1
剩余票数7
剩余票数6
剩余票数5
剩余票数4
剩余票数3
剩余票数2
剩余票数1

此时,启动三个线程实现买票处理,结果为三个线程各自卖自己的票,说明,Thread类实现的多线程不能共享同一个target对象

范例2:使用Runnable接口实现数据共享

class MyThread implements Runnable{
    private int ticket=10;
    @Override
    public void run(){
        while (this.ticket>0){
            System.out.println("剩余票数"+this.ticket--);
        }
    }
}
public class Test {
    public static void main(String[] args) {
       MyThread mythread=new MyThread();
       new Thread(mythread).start();
       new Thread(mythread).start();
       new Thread(mythread).start();
    }
}

结果:
剩余票数10
剩余票数8
剩余票数7
剩余票数6
剩余票数5
剩余票数4
剩余票数3
剩余票数1
剩余票数9
剩余票数2

Runnable实现的多线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

那么,为什么Runnable可以共享同一个target对象呢?

四、多线程的常用操作方法

 4.1 线程命名与取得

多线程的运行状态是不确定的,所以对于多线程操作必须有一个明确标识出线程对象的信息,这个信息往往通过名称来描述。

在Thread类中有以下几种关于线程名称的方法:

方法名称类型描述
public Thread(Runnable target, String name)构造创建线程的时候设置名称
public final synchronized void setName(String name)普通设置线程名称
public final String getName()普通取得线程名称

取得当前线程对象的方法:

public static native Thread currentThread();

范例:线程命名与取得

class MyThread implements Runnable{
    private int ticket=10;
    @Override
    public void run(){
       for(int i=0;i<10;i++){
            System.out.println("当前线程"+Thread.currentThread().getName()+" i="+i);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        MyThread mythread=new MyThread();
        new Thread(mythread).start();
        new Thread(mythread,"rachel").start();
    }
}

 注意:如果线程没有命名,则会自动分配一个名称,另外,设置名称不要重复,中间不要修改

范例2:线程执行结果

class MyThread implements Runnable{
    @Override
    public void run(){
            System.out.println("当前线程"+Thread.currentThread().getName());
    }
}
public class Test {
    public static void main(String[] args) {
        MyThread mythread=new MyThread();
        mythread.run();//直接通过对象调用run()方法
        new Thread(mythread).start();//通过线程调用
    }
}

结果:
当前线程main
当前线程Thread-0

 通过以上程序可以发现:主方法本来就是一个线程,所有线程都是通过主方法来创建并启动的

4.2 线程休眠——sleep()方法

线程休眠:指的是让线程暂缓执行一下,等到了预计时间之后再恢复执行。
线程休眠会交出CPU,让CPU去执行其他的任务。但是sleep()方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。

方法定义: 

public static native void sleep(long millis) throws InterruptedException;

4.3 线程让步——yield()方法

线程让步:暂停当前正在执行的线程对象,并执行其他线程。
1)调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
2)调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的

方法定义:

public static native void yield();

4.4 等待线程终止——join()方法

等待该线程终止:如果在主线程中调用该方法时就会让主线程休眠,让调用该方法的线程run方法先执行完毕之后在开始执行主线程

class MyThread implements Runnable{
    @Override
    public void run() {
        try {
            System.out.println("主线程睡眠前的时间");
            Test.printTime();//线程睡眠开始时间
            Thread.sleep(2000);//线程休眠2秒钟
            System.out.println(Thread.currentThread().getName());//此时获取到的是子线程A的名称
            System.out.println("睡眠结束的时间");
            Test.printTime();//线程睡眠结束时间
        } catch (InterruptedException e) {
// TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException{
        MyThread mt = new MyThread();
        Thread thread = new Thread(mt,"子线程A");
        thread.start();//子线程A启动,执行run方法内的操作
        System.out.println(Thread.currentThread().getName());//此时获得的是主方法线程的名称
        thread.join();//主线程休眠,等待子线程A执行完run方法之后继续运行
        System.out.println("代码结束");
    }
    //打印当前时间
    public static void printTime() {
        Date date=new Date();
        DateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time=format.format(date);
        System.out.println(time);
    }
}

结果:
main
主线程睡眠前的时间
2019-05-06 13:10:56
子线程A
睡眠结束的时间
2019-05-06 13:10:58
代码结束

4.5 停止线程——stop()方法

多线程中有三种方式可以停止线程。
1. 设置标记位,可以使线程正常退出。
2. 使用stop方法强制使线程退出,但是该方法不太安全所以已经被废弃了。
3. 使用Thread类中的一个 interrupt() 可以中断线程。

范例1:设置标记位使线程停止

class MyThread implements Runnable {
    private boolean flag = true; //设置标志位为true
    @Override
    public void run() {
        int i = 1;
        while (flag) {
            try {
                Thread.sleep(1000);
                System.out.println("第"+i+"次执行,线程名称为:"+Thread.currentThread().getName());
                i++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        Thread thread1 = new Thread(myThread, "子线程A");
        thread1.start();
        Thread.sleep(2000);  //主方法休眠
        myThread.setFlag(false);//设置标志位为false
        System.out.println("代码结束");
    }
}

范例2:使用stop()方法使线程停止

class MyThread implements Runnable {
    @Override
    public void run() {
        int i = 1;
            try {
                Thread.sleep(1000);
                System.out.println("第"+i+"次执行,线程名称为:"+Thread.currentThread().getName());
                i++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        Thread thread1 = new Thread(myThread,"子线程A");
        thread1.start();
        Thread.sleep(3000);
        thread1.stop();
        System.out.println("代码结束");
    }
}

注:使用stop()方法会使线程立即停止,它是一种不安全的操作,某些情况下,会造成数据残留,不同步。

范例3:使用interrupt()方法使线程停止

class MyThread implements Runnable {
    private boolean flag = true;
    @Override
    public void run() {
        int i = 1;
        while (flag) {
            try {
/**
 * 这里阻塞之后,线程被调用了interrupte()方法,
 * 清除中断标志,就会抛出一个异常
 * java.lang.InterruptedException
 */
                Thread.sleep(1000);
                boolean bool = Thread.currentThread().isInterrupted();
                if (bool) {
                    System.out.println("非阻塞情况下执行该操作。。。线程状态" + bool);
                    break;
                }
                System.out.println("第"+i+"次执行,线程名称为:"+Thread.currentThread().getName());
                i++;
            } catch (InterruptedException e) {
                System.out.println("退出了");
/**
 * 这里退出阻塞状态,且中断标志被系统会自动清除,
 * 并且重新设置为false,所以此处bool为false
 */
                boolean bool = Thread.currentThread().isInterrupted();
                System.out.println(bool);
//退出run方法,中断进程
                return;
            }
        }
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        Thread thread1 = new Thread(myThread,"子线程A");
        thread1.start();
        Thread.sleep(3000);
        thread1.interrupt();
        System.out.println("代码结束");
    }

调用线程类的interrupted方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。因此,通过interrupted方法真正实现线程的中断原理是:开发人员根据中断标志的具体值,来决定如何退出线程。

4.6 线程优先级

线程的优先级指的是,线程的优先级越高越有可能先执行,但仅仅是有可能而已

Thread类中关于线程优先级的相关方法:

方法描述
public final void setPriority(int newPriority)设置优先级
public final int getPriority()取得优先级

对于优先级设置的内容可以通过Thread类的几个常量来决定

1. 最高优先级:public final static int MAX_PRIORITY = 10;
2. 中等优先级:public final static int NORM_PRIORITY = 5;
3. 最低优先级:public final static int MIN_PRIORITY = 1;

范例:设置优先级

class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5 ; i++) {
            System.out.println("当前线程:" + Thread.currentThread().getName()+" ,i = "
                    +i);
        }
    }
}
public class Test {
    public static void main(String[] args)  {
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt,"1") ;
        Thread t2 = new Thread(mt,"2") ;
        Thread t3 = new Thread(mt,"3") ;
        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MIN_PRIORITY);
        t3.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
        t3.start();
    }
}

1)主方法只是中等优先级

2)线程是有继承关系的,比如当A线程中启动B线程,那么B和A的优先级将是一样的

4.7守护线程

守护线程是一种特殊的线程,它属于是一种陪伴线程。

Java 中有两种线程:用户线程和守护线程。可以通isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。

典型的守护线程就是垃圾回收线程。只要当前JVM进程中存在任何一个非守护线程没有结束,守护线程就在工作;只有当最后一个非守护线程结束时,守护线程才会随JVM一同停止工作。

主线程是用户线程

范例:守护线程

class A implements Runnable {
    private int i;
    @Override
    public void run() {
        try {
            while (true) {
                i++;
       System.out.println("线程名称:" + Thread.currentThread().getName() + ",i=" + i + ",是否为守护线程:"
                        + Thread.currentThread().isDaemon());
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "中断线程了");
        }
    }

}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new A(),"子线程A");
        thread1.setDaemon(true); // 设置线程A为守护线程,此语句必须在start方法之前执行
        thread1.start();
        Thread thread2 = new Thread(new A(),"子线程B");
        thread2.start();
        Thread.sleep(3000);//主线程休眠
        thread2.interrupt();// 中断非守护线程
        Thread.sleep(10000);//主线程休眠
        System.out.println("代码结束");
    }
    }

注:B是用户线程当它中断了之后守护线程还没有结束,是因为主线程(用户线程)还没有结束,所以说明是所有的用户线程结束之后守护线程才会结束。

  • 20
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值