Javaee - 线程

目录

一、认识线程

什么是线程?什么是进程,进程与线程之间有什么关系?

用java代码实现一个多线程程序(里面有一些方法后面会介绍)

二、创建线程的方式

继承Thread类创建线程

实现Runnable接口创建线程

使用匿名内部类形式创建线程

使用lambda表达式创建线程(主要使用:重要)

三、Thread中常用的方法

run()和start()

Thread的构造方法(常见的)

Thread中几个常见的方法

Thread.currentThread()

setName()/getName()/getID()

isAlive

sleep()

getPriority()

interrupt() 和isInterrupted()

四、线程的六种状态

NEW状态

TERMINATED

3.RUNNABLE

4.TIMED_WAITING

5.BLOCKED

6.WAITING

(1)t.join方法

 (2)wait和notify

线程关系图


一、认识线程

什么是线程?什么是进程,进程与线程之间有什么关系?

进程:进程是操作系统对一个正在运行的程序的一种抽象,就是可以把进程看作程序的一次运行程序。比如运行一个app,这个app就可能是一个进程。在操作系统内部,进程是操作系统进行资源分配的基本单位。

线程:一个线程就是一个“执行流”,每个线程之间都可以按照顺序执行自己的代码。每个线程都是一个单独的执行流,多个线程之间,也是并发执行的。在操作系统内部,线程是操作系统调度运行的基本单位。

这里因为可能有人不懂并发和并行,浅浅解释一下:

并行:同一时刻,两个核心,同时执行两个进程,这两个进程就是并行执行的。在操作系统中是指,一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的。

并发:一个核心,比如,先执行进程1,执行一会之后,再去执行进程2,然后又执行一会之后,再去执行进程3.....此时只要这里切换速度够快,进程123......就是“同时”执行,这时,我们可以称这个情况是并发的。在操作系统中,并发是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

进程和线程的关系(区别):

  • 进程包括线程,一个进程里可以有一个或者多个线程;
  • 进程和进程之间不共享内存空间,同一个进程的线程之间共享一个内存空间。

用java代码实现一个多线程程序(里面有一些方法后面会介绍)

public class ThreadDemo {
    private static class MyThread extends Thread {
        @Override
        public void run() {//提供线程的入口
            Random random = new Random();
            while (true) {
                // 打印线程名称
                System.out.println(Thread.currentThread().getName());
                try {

                    Thread.sleep(1000);
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
       }
   }
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();//开始创建线程
        t2.start();
        t3.start();
        Random random = new Random();
        while (true) {
            // 打印线程名称gerName()
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(random.nextInt(10));
           } catch (InterruptedException e) {
                // 随机停止运行 0-9 秒
                e.printStackTrace();
           }
       }
   }
}
运行结果:这里的结果打印的是线程运行的顺序,得到的名字都是系统自动分配的名字

Javaee - 线程_第1张图片

二、创建线程的方式

继承Thread类创建线程

class MyThread extends Thread {
    @Override
    public void run() {
        for(int i = 0;i < 10;i++){
            System.out.println("hello t");
        }
    }
}
public class ThreadDemol {
    public static void main(String[] args) {
        /*
        *start()会创建新的线程
        * */
        Thread t = new MyThread();
        t.start();
    }
}

实现Runnable接口创建线程

继承Thread类,可以直接使用this表示当前线程对象的引用,

实现Runnable接口,this则表示的是MyRunnable的引用,需要使用Thread.currentThread()。

lass MyRunable implements Runnable{ 
    @Override
    public void run() {
        for(int i = 0;i < 10;i++){
            System.out.println("hello t");
        }
    }
}
public class ThreadDemo2 {
    public static void main(String[] args)  {
        Thread t = new Thread();
        MyRunable myRunable = new MyRunable();
        t.start();
}

使用匿名内部类形式创建线程

public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                 for(int i = 0;i < 10;i++){
                    System.out.println("hellp t");
                 }
            }
        };
        t.start;
}

使用lambda表达式创建线程(主要使用:重要)

 public static void main(String[] args) {
        Thread t = new Thread(() -> {
          for(int i = 0;i < 10;i++){
               System.out.println("hellp t");
           }
        });
        t.start();
    }
}

三、Thread中常用的方法

run()和start()

首先,当一个类继承自Thread类,就需要重写run()方法,然后用start()方法启动该线程,如果该类实现了Runnable接口,则需要用new Thread(Runnable target).start()方法来启动。

run():该方法称为线程体,它包含了要执行这个线程的内容,当线程进入了运行状态,开始运行run函数当中的代码。run方法技术运行以后,此线程就终止。

start():用来启动某个线程,实现了多线程运行。通过调用Thread类的start()方法来启动一个线程,此时线程处于就绪状态,并没有运行,等线程调度程序将处于就绪状态的线程设置为了当前线程,此时线程就进行了运行状态,开始运行run函数中的代码。

Thread的构造方法(常见的)

方法

说明

Thread()

用法:Thread t = new Thread();

意义:创建一个线程对象ww

Thread(Runnable target)

用法:Thread t = new Thread(new MyRunnable());

意义:使用Runnable对象创建对象

Thread(String name)

用法:Thread t = new Thread("线程1");

意义:创建一个线程,并且给该线程命名,此处命只是为了区分线程

Thread(Runnable target,String name)

用法:Thread t = new Thread(new MyRunnable(),"线程1")

意义:使用Runnable对象创建线程对象,并且命名

Thread中几个常见的方法

Thread.currentThread()

获得当前线程,也就是执行当前代码的线程。

setName()/getName()/getID()

t.setName("线程名称");//设置线程名称
t.getName();//返回线程名称
t.getID;//得到线程id,每个线程对用的id都是唯一的

isAlive

t.isAlive()//判断当前线程是否处于活动状态

sleep()

Thread.sleep(millis);//让当前线程指定休眠数

getPriority()

优先级越高的线程,获得cpu资源的概率越大,

优先级本质上只是给线程调度器一个提示信息,以便于线程调度器决定先调度哪些线程。不能保证优先级高的线程先运行。java优先级设置不当,可能导致某些线程永远无法得到运行,产生了线程饥饿。线程的优先级并不是设置的越高越好,在开发时不必设置线程的优先级。

t.getPriority(num);//设置线程的优先级,num取值1-10,超出范围会抛出异常

interrupt() 和isInterrupted()

这里主要针对一个实例来展开说。

在这里大家需要明白这个多线程的执行顺序,多线程的代码并非是从上到下,每个线程是独立执行的,

比如以下代码,首先我们实例化一个线程t, Thread t = new Thread(() -> {.....},此时这里只有一个主线程正在执行,然后t.start(),此时t和主线程开始一起执行,主线程向下执行(休眠),t线程执行循环。

public class ThreadDemo10 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            //currentThreaed就是获取当前线程实例
            //此处currentThread得到的对象就是t,多线程的代码并非是从上到下,每个线程是独立执行的
            //isInterrupted就是t对象里的自带的一个标志位
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello t");
                try {
                    //这里sleep如果被唤醒,会清空标志位isInterrupted(true -> false),使得抛出异常后,仍然可以执行
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();//打印出当前异常位置的调用栈
                    break;
                }
            }

        });
        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //interrupt方法的作用:
        //1.把t内部的标志为设置为true
        //2.如果该线程正在阻塞中(比如正在执行sleep),此时就会把阻塞状态唤醒(比如通过抛出异常的方式让sleep立即结束)
        t.interrupt();
    }
}

然后关于interrupt和isInterrupted,首先t调用了一个isInterrupted(),这个方法主要是为了设置一个标志位(Boolean类型),这里默认为false,我们对这个条件进行取反,也就是true,然后当线程运行到 t.interrupt(),这个会把isInterrupted()设置为true的状态,这样hello t这个循环就会结束,线程也就会结束。以上代码还有几个需要注意的点:首先我们查看运行结果,

Javaee - 线程_第2张图片

此时发现,当3s时间到了,调用这个t.interrupt方法的时候,线程没有结束,二十打印了一个异常,并且继续执行,出现异常这个现象的原因如下:

主要是与interrupt方法的作用有关,主要有两个作用:

1.把t内部的标志为设置为true
2.如果该线程正在阻塞中(比如正在执行sleep),此时就会把阻塞状态唤醒(比如通过抛出异常的方式让sleep立即结束)

也就是说,这里本来这个sleep要执行1000秒,但是由于使用了interrupted(),这个会让sleep提前结束(比如只执行了500秒就结束了),由于这个interrupted(),阻塞状态被唤醒,以抛出异常的方式让sleep立即结束了,所以这里就会抛出异常。

这里还有一个需要注意的点:

本来t.interrupt()这个操作会使标志位isInterrupted()由false → true,然后抛出异常后hello t就不会再执行了,但是我们可以瞧见抛出异常后hello t还在执行,这是因为当sleep被提前唤醒的时候,sleep会自动把isInterrupted的标志位给清空,也就是isInterrupted又变了,由true → false ,导致后面hello t 又开始循环执行。加上break主要是为了结束线程。

以上的情况主要是因为sleep的使用引起的抛出异常,类似的情况还有使用join,wait时,也会出现和sleep这种情况。

四、线程的六种状态

得到线程状态的方法:getState(),直接调用即可。

NEW状态

系统中的线程还没创建出来,只是有一个Tread对象

 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        //在启动之前,获取线程状态,NEW
        System.out.println(t.getState());
}

执行结果:

Javaee - 线程_第3张图片

TERMINATED

系统中的线程已经执行完了,Thread对象还在。

 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("hello");
        });
        t.start();

        Thread.sleep(2000);
        System.out.println(t.getState());
    }

执行结果:

Javaee - 线程_第4张图片

3.RUNNABLE

就绪状态:

(1)正在CPU上运行

(2)准备好随时可以去CPU上运行

 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {

            }
        });
        t.start();

        Thread.sleep(2000);
        System.out.println(t.getState());
    }

执行结果:

Javaee - 线程_第5张图片

4.TIMED_WAITING

指定时间等待,sleep方法

public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t.start();

        Thread.sleep(2000);
        System.out.println(t.getState());
    }

执行结果:

Javaee - 线程_第6张图片

5.BLOCKED

线程安全(解决方式)_‍️藿香正气水的博客-CSDN博客

了解这个状态就要先知道什么是锁,参考以上链接的锁竞争。

有多个进程去竞争一个锁,没有拿到锁的进程就处于BLOCKED状态。

如下代码:当t2没有拿到锁资源时,t2就属于BLOCKED状态。

 public static void main(String[] args) throws InterruptedException {
        Object object= new Object();
        Thread t1 = new Thread(()->{
           synchronized (object){
               try {
                   Thread.sleep(1000);//sleep时并不会释放锁资源
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
 
        Thread t2 = new Thread(()->{
           synchronized (object){
               System.out.println("t2执行拿到锁执行完了");
           }
        });
        t1.start();
        t2.start();
        Thread.sleep(100);//防止Main线程执行过快
        System.out.println("t2的状态是"+t2.getState());
    }

 Javaee - 线程_第7张图片

6.WAITING

(1)t.join方法

一个正在运行的线程内部调用了join()方法,此时该线程就会进入阻塞状态,直到调用join方法的线程运行结束,该线程才恢复就绪状态。这种阻塞就是没有时间限制的WAITING状态。

public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");

        Thread t2 = new Thread(()->{
            try {
                t1.join();//调用join方法
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t2");
        t1.start();
        t2.start();
        Thread.sleep(1000);//避免主线程太快结束而看不到结果
        System.out.println(t2.getName()+"处于"+t2.getState()+"状态");
    }

Javaee - 线程_第8张图片

 (2)wait和notify

举例说明:

将几个线程比喻为要去ATM取钱的四个人

此时1号进去了ATM取钱,如下图,此时这个人(也就是这个线程)处于加锁状态,手里拿着锁,

(如果不知道什么是加锁,参考文章线程安全(解决方式)_‍️藿香正气水的博客-CSDN博客)

Javaee - 线程_第9张图片

 然后1号发现ATM里面没有钱,然后一号释放了锁,就出了ATM,如下图:

Javaee - 线程_第10张图片

但是当1号解锁之后,后面是哪个人(哪个线程)能拿到锁,是随机的不确定的,这会导致一个极端情况,每次都是1号拿到锁,然后此时因为ATM里面还是没有钱,导致1号一直进进出出,使后面的人(线程)一直拿不到锁。这种极端情况我们称为线程饿死

此时我们可以使用wait/notify解决上述问题。

当1号发现ATM里面没有钱,就会wait,wait就会释放锁,并且进行阻塞等待,此时这个1号就暂时不会参与CPU调度,不会参与锁的竞争。

后面,当有另外的人(另外的线程),将ATM里面冲上钱,就可以唤醒1号。

作用:

wait : 发现条件不满足/实际不成熟,就先阻塞等待。

wait操作主要分三步:

1.是当前执行代码的线程进行阻塞等待

2.释放当前的锁(所以使用wait/notify一定要搭配synchronized使用)

3.满足一定的条件时被唤醒,重新尝试获取这个锁

notify : 当其他线程构造了一个成熟的条件,就能唤醒阻塞等待的线程。

wait / notify的使用:


public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        //负责wait
        Thread t1 =  new Thread(() -> {
                try {
                    System.out.println("wait 开始");
                    synchronized (locker) {//需要加锁
                        locker.wait();
                    }
                    System.out.println("wait 结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        });
        t1.start();

        //负责notify
        Thread.sleep(1000);
        Thread t2 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("notify 开始");
                locker.notify();
                System.out.println("notify 结束");
            }
        });
        t2.start();
    }

Javaee - 线程_第11张图片

 也就是说,当我们使用wait时,阻塞等待会让线程进入WAITING状态。

join() 和wait / notify的区别:

join() :只能时让t2线程限制性完,再继续执行t1,一定是串行的。

wait / notify :可以让t2执行完一部分,再让t1执行......t1执行完一部分再让t2执行....... 

线程关系图:

Javaee - 线程_第12张图片

你可能感兴趣的:(线程,java,jvm,开发语言)