线程相关概念

程序

程序就是为完成特定任务、用某种语言编写的一组指令的集合。简单来说就是我们写的代码。

Java线程初步理解_System

进程

  1. 进程是指运行中的程序,比如我们打开一个应用,就是启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用另一个应用,又启动了一个进程,操作系统就会为该应用分配新的内存空间。(简单来讲就是运行几个程序)
  2. 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程。

什么是线程

  1. 线程有进程创建的,是进程的一个实体。
  2. 一个进程可以有多个线程。

其他相关概念

  1. 单线程:同一个时刻,只允许执行一个线程。
  2. 多线程:同一个时刻,可以执行多个线程,比如一个迅雷进程,可以同时下载多个文件。
  3. 并发:同一时刻,多个任务交替执行,造成一种“貌似同时”的感觉,简单来说单核cpu实现的多任务就是并发。
  4. 并行:同一时刻,多个任务同时执行。多核cpu可以实现并行。

线程的基本使用

创建线程的两种方式

  1. 继承Thread类,重写run方法。
  2. 实现Runnable接口,重写run方法。

 继承Thread类

package com.study.srv.demo16;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2022/4/24 18:15
 */
public class Demo01 {
    public static void main(String[] args) throws InterruptedException {

        //创建Cat对象,可以当作线程使用
        Cat cat = new Cat();
        cat.start();//启动线程
       /*
       (1)
         public synchronized void start() {
            start0();
         }
       (2)
       start0();是本地方法,由jvm调用,底层用c/c++实现的
       真正实现多线程的效果的是start0(),而不是run
       private native void start0();
        */

        //当main(主)线程启动一个子线程(Thread-0)时,主线程不会阻塞,会继续执行。
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程" + i);
            Thread.sleep(1000);
        }
    }
}

//1.当一个类继承了Thread类,该类就可以当作线程使用
//2.程序员可以重写run方法,写上自己的业务代码
//3.run Thread类实现了Runnable接口的run方法
/*
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
 */
class Cat extends Thread {
    int times = 0;

    @Override
    public void run() {
        //该线程每隔1秒,在控制台打印"卷王上线,开始干活"
        while (true) {
            System.out.println("卷王上线,开始干活" + (times++) + "线程名:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);//线程休眠,单位为毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 10) {
                break;
            }
        }
    }
}

Java线程初步理解_System_02

 实现Runnable接口

  1. Java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然行不通。
  2. Java设计者们提供了另外一种方式创建线程,就是通过实现Runnable接口来创建线程。
package com.study.srv.demo16;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2022/4/25 11:17
 * 通过实现Runnable接口创建进程
 */
public class Demo02 {
    public static void main(String[] args) {
//        Door door = new Door();
//        //door.start();此时不能调用start方法
//        //可以创建Thread对象,把dog对象(即实现了Runnable接口的实现类)放入Thread
//        Thread thread = new Thread(door);
//        thread.start();

        Bee bee = new Bee();//此对象实现了Runnable接口
        Proxy proxy = new Proxy(bee);
        proxy.start();
    }
}
class Door implements Runnable{//通过实现Runnable接口,开发线程

    @Override
    public void run() {
        int times = 0;
        while (true) {
            System.out.println("卷王上线,开始干活" + (++times) + "线程名:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);//线程休眠,单位为毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 10) {
                break;
            }
        }
    }
}
//线程代理类,模拟了一个简单的Thread类
class Proxy implements Runnable{

    private Runnable bee=null;//定义一个属性,类型是Runnable
    @Override
    public void run() {
        if (bee!=null){
            bee.run();//动态绑定机制(运行类型时bee)
        }
    }
    public Proxy(Runnable bee){
        this.bee=bee;
    }

    public void start(){
        start0();//底层的这个star0方法才是真正实现多线程方法的,这里是模拟的
    }

    private void start0() {
        run();
    }
}
class Animal{}
class Bee extends Animal implements Runnable{
    int times=0;
    @Override
    public void run() {
        while (true) {
            System.out.println("蜜蜂嗡嗡嗡");
            ++times;
            try {
                Thread.sleep(1000);//线程休眠,单位为毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times >= 10) {
                break;
            }
        }
    }
}

Java线程初步理解_ide_03

 继承Thread vs 实现Runnable的区别

  1. 从Java设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档可以看到Thread类本身就实现了Runnable接口
  2. 实现Runnable接口方式更加适合多线程共享一个资源的情况,并且避免了单继承的限制。
package com.study.srv.demo16;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2022/4/26 11:16
 */
public class Demo04 {
    public static void main(String[] args) {
//        SellTicket1 sellTicket01 = new SellTicket1();
//        sellTicket01.start();
//        SellTicket1 sellTicket02 = new SellTicket1();
//        sellTicket02.start();
//        SellTicket1 sellTicket03 = new SellTicket1();
//        sellTicket03.start();


//        出现票数超卖情况


        SellTicket2 sellTicket2 = new SellTicket2();
        new Thread(sellTicket2).start();
        new Thread(sellTicket2).start();
        new Thread(sellTicket2).start();
    }
}
class SellTicket1 extends Thread{
    private static int ticket=100;
    @Override
    public void run() {
        while (true) {
            if (ticket<=0){
                System.out.println("全部卖完");
                break;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数:"+(--ticket));
        }
    }
}
class SellTicket2 implements Runnable{

     int ticket=100;
    @Override
    public void run() {
        while (true) {
            if (ticket<=0){
                System.out.println("全部卖完");
                break;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数:"+(--ticket));
        }
    }
}

Java线程初步理解_ide_04

 线程终止

  1. 当线程完成任务后,会自动退出。
  2. 还可以根据变量来控制run方法退出的方式来停止线程。
package com.study.srv.demo16;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2022/4/26 14:21
 */
public class Demo05 {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        new Thread(t).start();
        for (int i = 1; i <=60; i++) {
            Thread.sleep(1000);
            System.out.println("main线程正在运行。。。");
            if (i==30){//设置定时终止线程
                t.setLoop(false);
            }
        }
    }
}
class T implements Runnable{
    private boolean loop=true;

    public boolean isLoop() {
        return loop;
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }

    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"正在运行。。。。");
        }
    }
}

Java线程初步理解_ide_05

 线程常用方法

方法名

方法说明

setName

//设置线程名称,使之与参数name相同

getName

//返回该线程的名称

start

//使该线程开始执行;Java虚拟机底层调用start0方法

run

//调用线程对象run方法

setPriority

//更改线程的优先级

getPriority

//获取线程优先级

sleep

//在指定的毫秒数让正在执行的线程休眠(暂停执行)

interrupt

//中断线程

注意事项

  1. start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程。
  2. 线程优先级的范围。
  3. interrupt,中断线程,但是没有真正的结束线程。所以一般用于中断正在休眠线程。
  4. sleep:线程的静态方法,使当前线程休眠。 
package com.study.srv.demo16;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2022/4/27 10:13
 */
public class Demo06 {
    public static void main(String[] args) throws InterruptedException {
        L l = new L();
        l.setName("猪八戒");
        l.setPriority(Thread.MIN_PRIORITY);
        l.start();
        //测试优先级
        System.out.println("默认优先级="+Thread.currentThread().getPriority());
        //测试interrupt
        Thread.sleep(3000);
        l.interrupt();
    }
}
class L extends Thread{

    @Override
    public void run() {
        for (int i = 0; i <=100; i++) {
            System.out.println(Thread.currentThread().getName()+"吃包子。。。");
        }
        try {
            System.out.println(Thread.currentThread().getName()+"休眠中。。。");
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            //当该线程执行了一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码
            System.out.println(Thread.currentThread().getName()+"被interrupt了");
        }
    }
}

Java线程初步理解_ide_06

常用方法二

  1. yield:线程的礼让。礼让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功。

Thre ad.yield();

  1. join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务。

想插队的线程.join();

package com.study.srv.demo16;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2022/4/27 10:56
 */
public class Demo07 {
    public static void main(String[] args) throws InterruptedException {
        S s = new S();
        s.setName("子线程");
        s.start();

        for (int i = 1; i <= 20; i++) {
            System.out.println("main线程吃包子"+i);
            Thread.sleep(1000);
            if (i==5){
                System.out.println("让子线程先吃");
                //s.join(); //线程插队,先让s线程执行完毕
                Thread.yield();//礼让,不一定成功
                System.out.println("子线程吃完,main线程继续");
            }
        }
    }
}
class S extends Thread{
    @Override
    public void run() {
        for (int i=1;i<=20;i++) {
            System.out.println(Thread.currentThread().getName()+"吃包子"+i);
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Java线程初步理解_System_07

用户线程和守护线程

  1. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束。
  2. 守护线程:一般是为了工作线程服务的,当所有的用户线程结束,守护线程自动结束。
  3. 常见的守护线程:垃圾回收机制。 
package com.study.srv.demo16;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2022/4/27 14:21
 */
public class Demo08 {
    public static void main(String[] args) throws InterruptedException {
        D d = new D();
        //将d设置为守护线程,当所有线程结束后,d也就自动结束
        //如果没有设置,那么即使main线程执行完毕,d也不退出。
        d.setDaemon(true);
        d.start();
        for (int i = 1; i <=100; i++) {
            Thread.sleep(50);
            System.out.println("hi"+i);
        }
    }
}
class D extends Thread{
    @Override
    public void run() {
        for (;;){
            try {
                sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hello");
        }
    }
}

Java线程初步理解_ide_08

线程的生命周期

JDK中Thread.State枚举表示线程几种状态

线程状态,线程状态可以处于以下几种状态之一:

  • NEW        至今尚未启动的线程处于这种状态。
  • RUNNABLE    正在Java虚拟机中执行的线程处于这种状态。
  • BLOCKED   受阻塞并等待某个监视器锁的线程处于这种状态。
  • WAIING     无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
  •  TIMED_WAIING  等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
  • TERMINATED   已退出的线程处于这种状态。

在给定时间点上,一个线程只能处于一种状态。这些状态是虚拟机状态,它们并没有反应所有操作系统线程状态。 

线程状态转换图

Java线程初步理解_i++_09

package com.study.srv.demo16;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2022/4/27 16:08
 */
public class Demo09 {
    public static void main(String[] args) throws InterruptedException {
        F f = new F();
        System.out.println(f.getName()+"状态:"+f.getState());
        f.start();

        while (Thread.State.TERMINATED!=f.getState()){
            System.out.println(f.getName()+"状态:"+f.getState());
            Thread.sleep(500);
        }
        System.out.println(f.getName()+"状态:"+f.getState());
    }
}
class F extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <=10 ; i++) {
            System.out.println("hi"+i);
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Java线程初步理解_i++_10

Synchronized

线程同步机制

  1. 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
  2. 也可以理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该地址内存地址进行操作。

 同步具体方法

 1.同步代码块

synchronized (对象){//得到对象的锁,才能操作同步代码

        //需要被同步代码

}

 2.synchronized还可以放到方法声明里,表示整个方法-为同步方法

pubilc synchronized void m(String name){

        //需要被同步代码

}

3. 如何理解:就像你走进银行24小时自主取款机里,门会自动落锁,只有当你从里面出来之后才能有下一个人进去操作取款机。

package com.study.srv.demo16;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2022/4/27 17:01
 */
public class Demo10 {
    public static void main(String[] args) {
        //解决超卖问题
        SellTicket4 sellTicket2 = new SellTicket4();
        new Thread(sellTicket2).start();
        new Thread(sellTicket2).start();
        new Thread(sellTicket2).start();
    }
}
class  SellTicket4 implements Runnable{

    private int ticket=100;
    private boolean loop =true;

    public synchronized void sell(){
        if (ticket<=0){
            System.out.println("全部卖完");
            loop=false;
            return;
        }
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数:"+(--ticket));
    }
    @Override
    public void run() {
        while (loop) {
            sell();
        }
    }
}

Java线程初步理解_ide_11

互斥锁

基本介绍

  1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
  2. 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
  3. 关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任意时刻只能由一个线程访问。
  4. 同步的局限性:导致程序的执行效率要降低。
  5. 同步方法(非静态)的锁可以是this,也可以是其他对象(要求是同一对象)。
  6. 同步方法(静态)的锁为当前类本身。
package com.study.srv.demo16;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2022/4/28 9:46
 * 互斥锁
 */
public class Demo11 {
    public static void main(String[] args) {
        //解决超卖问题
        SellTicket5 sellTicket2 = new SellTicket5();
        new Thread(sellTicket2).start();
        new Thread(sellTicket2).start();
        new Thread(sellTicket2).start();
        //同步方法(非静态)的锁可以是this,也可以是其他对象(要求是同一对象)

        //下面是不同对象,所以不会同步。
//        SellTicket5 sellTicket3 = new SellTicket5();
//        SellTicket5 sellTicket4 = new SellTicket5();
//        SellTicket5 sellTicket5 = new SellTicket5();
//        new Thread(sellTicket3).start();
//        new Thread(sellTicket4).start();
//        new Thread(sellTicket5).start();
    }
}
class  SellTicket5 implements Runnable{

    private int ticket=100;
    private boolean loop =true;
    Object object=new Object();

    //1.public synchronized static void m1(){}锁是加在SellTicket5.class
    //2.如果在静态方法中,实现一个同步代码块。
//    synchronized (SellTicket5.class){
//        System.out.println("m2");
//    }

    public synchronized static void m1(){

    }
    public void m2(){
        synchronized (SellTicket5.class){
            if (ticket <= 0) {
                System.out.println("全部卖完");
                loop = false;
                return;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余票数:" + (--ticket));
        }
    }

    //1.public synchronized void sell(){}就是一个同步方法
    //2.这时所在this对象
    //3.也可以在代码块上写synchronized,同步代码块,互斥锁还是在this对象
    public /*synchronized*/ void sell(){//同步方法,在同一时刻,只能有一个线程来执行sell方法
        synchronized (/*this*/object) {
            if (ticket <= 0) {
                System.out.println("全部卖完");
                loop = false;
                return;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余票数:" + (--ticket));
        }
    }
    @Override
    public void run() {
        while (loop) {
            //sell();
            m2();
        }
    }
}

Java线程初步理解_ide_12

注意事项

  1. 同步方法如果没有使用static修饰:默认锁对象为this
  2. 如果方法使用static修饰,默认锁对象:当前类.class
  3. 实现的具体步骤:
  1. 需要分析上锁的代码
  2. 选择同步代码块或同步方法
  3. 要求多个线程的锁对象为同一个

线程的死锁

 多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生。

package com.study.srv.demo16;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2022/4/28 11:28
 * 线程死锁
 */
public class Demo12 {
    public static void main(String[] args) {
        //模拟线程死锁
        DeadLock d1 = new DeadLock(true);
        DeadLock d2 = new DeadLock(false);
        d1.setName("A线程");
        d1.start();
        d2.setName("B线程");
        d2.start();
    }
}
class DeadLock extends Thread{
    static Object o1=new Object();//保证多线程,共享一个对象,这里使用static
    static Object o2=new Object();
    boolean flag;

    public DeadLock(boolean flag) {//构造器
        this.flag = flag;
    }

    @Override
    public void run() {
        //业务分析
        //1.如果flag为T,线程A就会先得到/持有o1对象锁,然后尝试去获取o2对象锁
        //2.如果线程A得不到o2对象锁,就会Blocked
        //3.如果flag为F,线程B就会先得到/持有o2对象锁,然后尝试去获取o1对象锁
        //4.如果线程B得不到o1对象锁,就会Blocked
        if (flag){
            synchronized (o1){
                System.out.println(Thread.currentThread().getName()+"进入o1");
                synchronized (o2){
                    System.out.println(Thread.currentThread().getName()+"进入o2");
                }
            }
        }else {
            synchronized (o2){
                System.out.println(Thread.currentThread().getName()+"进入o2");
                synchronized (o1){
                    System.out.println(Thread.currentThread().getName()+"进入o1");
                }
            }
        }
    }
}

Java线程初步理解_ide_13

释放锁

可以释放锁

  1. 当前线程的同步方法、同步代码块执行结束。
  1. 案例:上厕所,完事出来。
  1. 当前线程在同步代码块、同步方法中遇到break、return。
  1. 案例:没有正常的完事,经理叫他修改bug,不得已出来。
  1. 当前线程在同步代码块、同步方法中出现了未处理的Error或Excepttion,导致异常结束。
  1. 案例:没有正常的完事,发现忘带纸,不得已出来。
  1. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
  1. 案例:没有正常的完事,觉得需要酝酿一下,所以出来等会再进去。

不会释放锁

  1. 线程执行同步代码块同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁。
  1. 案例:上厕所太困,眯了一会。
  1. 该线程执行同步代码块,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
  1. 应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用