多线程(一)

目录

一.线程与进程

1.进程与线程的概念

2.线程状态

二.Java多线程实现

1.继承Thread类实现多线程

2.实现Runnable接口来实现多线程

3.实现Callable接口实现多线程

三.多线程的常用操作方法

1.线程的命名与取得

2.线程休眠

3.线程让步

4.等待其他线程终止

5.线程停止

6.线程优先级

7.守护线程


 

 

一.线程与进程


1.进程与线程的概念

线程:线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

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

进程与线程的比较:

  1. 与进程相比,线程更加的“轻量级”,创建、撤销一个线程,比启动、撤销一个进程开销要小得多。一个进程中的所有线程共享此进程的所有资源。
  2. 没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。
  3. 进程是操作系统资源调度的基本单位,进程可以独享资源;                                                                                                                    线程是操作系统任务执行的进步单位,线程需要依托进程提供的资源,无法独立申请操作系统资源。                                                  

2.线程状态

 

二.Java多线程实现


1.继承Thread类实现多线程

java.lang.Thread是线程操作的核心类,新建一个线程最简单的办法就是直接继承Thread类,而后覆写run()方法。

无论哪种方式实现多线程,线程启动一定要调用Thread类提供的start()方法!!!

注意:线程的start()方法只能调用一次,多次会抛出异常。

那么,为什么要调用start()方法去实现run()方法,而不是直接实现run()方法?

我们来看一下线程创建的调用流程:

Java调用Thread的start()方法 -> start0()是本地方法,在start()方法里会调用start0()方法,start0()方法是被JVM调度使用的 -> JVM进行资源调度、系统分配 -> run()是Java中的方法,执行线程的具体操作任务(具体实现过程可以自行查看源码)

如果直接调用run()方法,那仅仅是一个普通方法,程序是顺序执行的,要等到上一个run()方法执行完毕后才可以执行下一个run()方法。

如果使用start()方法来启动线程,才是真正实现了多线程。通过Thread类的start()方法来启动一个线程,这时线程处于就绪状态,并没有运行,可以接着执行下面的代码,无需等到run()方法执行完毕。然后通过Thread类调用run()方法来完成具体的操作。

举个栗子:

package www.like.java;

class MyThread extends  Thread{
    private String title;

    public MyThread(String title) {
        this.title = title;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            System.out.println(this.title + "、" + i);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("线程1");
        MyThread myThread2 = new MyThread("线程2");
        myThread1.start();
        myThread2.start();
    }
}

运行结果:

 我们可以观察到,线程1和线程2是交替执行的,两个线程的执行顺序是随机的。

2.实现Runnable接口来实现多线程

上图为Thread类的源码,是Thread类的一个构造方法。可以看出来,我们可以直接通过构造方法传进Runnable对象,然后启动线程。

举个栗子:

package www.like.java;

class MyThread implements Runnable{
    private String title;

    public MyThread(String title) {
        this.title = title;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            System.out.println(this.title + "、" + i);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("线程1");
        MyThread myThread2 = new MyThread("线程2");
        new Thread(myThread1).start();
        new Thread(myThread2).start();
    }
}

运行结果:

继承Thread类与实现Runnable接口的关系:

  1. Thread类(本身就实现了Runnable接口)与自定义类(实现了Runnable接口),是一个典型的代理设计模式。

         Thread类负责辅助真实业务操作(资源调度、创建线程并启动)。

         自定义线程类负责真实业务的实现(run()具体要做哪些事)。

    2.使用Runnable接口实现的多线程程序类可以更好地描述共享的概念(多个线程共同使用一个Runnable),不是说Thread不能。

举个卖票的例子来看看两者的差别:

package www.like.java;

class MyThread extends Thread{
    private String title;
    private int ticket = 10;

    public MyThread(String title) {
        this.title = title;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            System.out.println(this.title + "还剩下" + ticket-- + "张票");
        }
    }
}

public class Test {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("售票人1");
        MyThread myThread2 = new MyThread("售票人2");
        myThread1.start();
        myThread2.start();

    }
}

我们可以看到运行结果,两个线程是交替执行的,但是各卖各的票,互不干预。

package www.like.java;

class MyThread implements Runnable{
    private String title;
    private int ticket = 10;

    public MyThread(String title) {
        this.title = title;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            System.out.println(this.title + "还剩下" + ticket-- + "张票");
        }
    }
}

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread("售票人");
        Thread t1 = new Thread(myThread);
        Thread t2 = new Thread(myThread);
        Thread t3 = new Thread(myThread);
        t1.start();
        t2.start();
        t3.start();
    }
}

两个线程共同卖10张票,实现了共享。

3.实现Callable接口实现多线程

我们先来看一下Callable接口的源码:

 我们在实现Callable接口之后,需要实现call()方法来完成相应的操作。与Thread类和Runnable接口不同的是,Callable是有返回值的。

继承Callable接口而后覆写call()方法,有返回值!!!

举个栗子:

package www.like.java;

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

class MyThread implements Callable<String> {
    private int ticket = 10;

    @Override
    public String call() {
        for(int i = 0; i < 10; i++){
            System.out.println("还剩下"+this.ticket--+"张票");
        }
        return "票卖完了";
    }
}

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread(); //Callable接口的对象
        FutureTask<String> futureTask = new FutureTask<>(myThread); //既可以传Callable对象,也可以传Runnable对象
        //futureTask可以直接给Thread,相当于扔了个Runnable对象(FutureTask是Runnable的子类)
        Thread thread1 = new Thread(futureTask);
        Thread thread2 = new Thread(futureTask);
        thread1.start();
        thread2.start();
        try {
            System.out.println(futureTask.get()); //get()接收Callable的返回值
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

 

三.多线程的常用操作方法


1.线程的命名与取得

I.通过构造方法在创建线程时设置线程名称

 

II.取得线程名称

III.设置线程名称

注意:无论在线程启动前还是启动后都可以修改名字。

2.线程休眠

线程休眠:让当前线程暂缓执行,等到了预计时间后,再恢复执行。

线程休眠会交出CPU,但不会释放锁。

线程休眠:运行状态--->阻塞状态       不会释放锁、立马交出CPU

休眠时间结束后:阻塞状态--->就绪状态

3.线程让步

线程让步:暂停执行当前的线程对象,并执行其他线程。

线程让步:运行状态--->就绪状态        不会释放锁

yield()方法会让当前线程交出CPU,同样不会释放锁。但是yield()方法无法控制具体交出CPU的时间,并且yield()方法只能让拥有相同优先级的线程有获取CPU的机会。

4.等待其他线程终止

等待该线程终止。如果在主线程中调用该方法,会让主线程休眠。让调用该方法的线程先执行完毕后再恢复执行主线程。(哪怕子线程休眠,主线程也不会执行,必须等子线程执行完毕)

join()方法:执行状态--->阻塞状态

join()方法结束:阻塞状态--->就绪状态

举个栗子:

package www.like.java;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println("线程休眠开始。。。");
        Test.printTime();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程休眠结束。。。");
        Test.printTime();
    }
}

public class Test {
    public static void main(String[] args) {
        System.out.println("main线程开始。。。");
        MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread,"子线程A");
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main线程结束");
    }
    public static void printTime(){
        Date date = new Date();
        DateFormat dateFormat = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
        String str = dateFormat.format(date);
        System.out.println(str);
    }
}

 在主线程中对子线程调用join()方法,主线程会等子线程执行完毕再开始执行。

5.线程停止

5.1设置标记位停止线程(推荐)

举个栗子:

package www.like.java;

class MyThread implements Runnable{
   private boolean flag = true;

    @Override
    public void run() {
        int i = 1;
        while(flag){
            System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            i++;
        }
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread,"子线程A");
        thread.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread.setFlag(false);

    }
}

5.2 调用Thread类的stop方法强制停止线程 

该方法不安全,已经被Deprecated。

5.3调用Thread类的 Interrupt 方法

interrupt()方法只是将线程状态置为中断状态而已,他不会中断一个正在运行的线程。此方法只是给线程传递一个中断信号,程序可以根据此信号来判断是否需要终止。

如果线程当前处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果当前线程的状态处于阻塞状态,那么在将中断标志设置为true后,会有以下情况:

当线程中使用了wait,sleep以及join方法导致此线程阻塞,则这个interrupt方法会在线程中抛出InterrputException,并且将线程的中断状态由true置为false。

举个栗子:

package www.like.java;

class MyThread implements Runnable{

    @Override
    public void run() {
        int i = 1;
        while(true){
            boolean bool = Thread.currentThread().isInterrupted(); //bool记录是否中断,默认没有中断
            System.out.println(Thread.currentThread().getName()+"第"+i+"次执行"+bool);
            if (bool){
                //当发生中断时,线程退出
                System.out.println("线程退出。。。");
                break;
            }
            i++;
        }
    }
}

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread,"子线程A");
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

当出现第一个true(即第一次中断)时,线程退出。

6.线程优先级

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

设置优先级:

取得优先级:

Thread类中的三个常量来代表优先级(1,5,10)

MAX_PRIORITY = 10;

NORM_PRIORITY = 5;

MIN_PRIORITY = 1;

主线程只是一个普通优先级而已。

线程具有继承性:只是继承优先级而已。

例:从A线程启动B线程,则B和A的优先级是一样的。

7.守护线程

守护线程时一种特殊的线程,有称为陪伴线程。Java中有两种线程:用户线程守护线程

Thread类提供isDaemon()区别两种线程:返回false表示该线程为用户线程,否则为守护线程。

典型的守护线程就是垃圾回收线程。

Thread提供setDaemon()将用户线程设置为守护线程。

thread1.setDaemon(true);//将子线程1设置成守护线程,默认为用户线程

 只要当前JVM进程中存在任何一个用户线程没有结束,守护线程就在工作,只有当最后一个用户线程结束时,守护线程才会随着JVM一同停止工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值