第十七章 Java线程

在这里插入图片描述

1. 线程基本概念

1.1进程

  1. 进程是指运行中的程序,比如启动了QQ,就相当于启动了一个进程,操作系统会为该进程分配空间;当我们使用迅雷,就相当于又启动了一个进程,操作系统将为迅雷分配新的内存空间;
  2. 进程是程序的一次执行过程,或是正在运行的一个程序,是动态过程,有它自身的产生、存在、消亡的过程;
  3. Run代表启动了一个进程,会马上进入main方法,这时进程就开启了一个线程,即主线程;
    在这里插入图片描述
    这时再启用start()方法就会在主线程里面再启动一个子线程:Thread-0;
    子线程在执行的时候,主线程不会阻塞,会继续执行;
    在这里插入图片描述

1.2线程

  1. 线程由进程创建的,是进程的一个实体;
  2. 一个进程可以拥有多个线程;
  3. 单线程:在同一个时刻,只允许执行一个线程;
  4. 多线程:同一个时刻,可以执行多个线程。比如,一个qq进程,可以同时打开多个聊天窗口;一个迅雷进程,可以同时下载多个文件;

1.3 并发

(1)单核CPU实现多任务;(2)同一个CPU,在某个时刻,多个任务交替执行;CPU速度很快,造成“QQ”和“迅雷”貌似同时执行的错觉;
在这里插入图片描述

1.4 并行

(1)在同一个时刻多个任务同时执行,多核CPU可以实现并行;

在这里插入图片描述

获取当前电脑Cpu数量
在这里插入图片描述

3. 线程基本使用

在这里插入图片描述

3.1 继承Thread类

  1. 当一个类继承了Thread类,该类就可以当作线程使用;
  2. Thread类实现了Runnable接口的run方法
    在这里插入图片描述
  3. 重写run方法,实现自己的业务逻辑
    在这里插入图片描述
  4. 调用start()方法会调用run()方法
    在这里插入图片描述

3.1.1 子线程

  1. Run代表启动了一个进程,会马上进入main方法,这时进程就开启了一个线程,即主线程;
  2. 这时再启用start()方法就会在主线程里面再启动一个子线程:Thread-0;
  3. 子线程在执行的时候,主线程不会阻塞;
  4. 子线程、主线程会交替执行;
    在这里插入图片描述
  5. 主线程的名称是main,子线程的名称从Thread-0开始;
3.1.1.1 JConsole监控
  • 验证:使用JConsole监控线程执行情况:
    在这里插入图片描述
    在这里插入图片描述
3.1.1.2 直接run()方法
  1. 此时的run()方法只是个普通方法,直接调用并不会开启子线程,此时Thread.currentThread().getName()是main
  2. 直接调用run方法不会开启子线程,默认在主线程内执行;
    在这里插入图片描述
  3. 会把run()方法执行完毕,才向下执行,串行化地执行,程序会阻塞;
    在这里插入图片描述
3.1.1.2 调用start()方法
  • 真正实现多线程的效果,是start0()方法
  1. cat调用start()方法,进入到Thread类的start()方法,start()方法调用start0()方法;
    在这里插入图片描述
  2. 进入到Thread类中的start()方法;start0()是本地方法,JVM调用,用C/C++编写;
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3.2 实现Runable接口

  1. 假如一个A类继承了B类,那么它就不能再继承Thread类,这个时候让A类实现Runable接口,同样能实现线程;
  2. Runable接口里没有start()方法,只有个run方法;
    在这里插入图片描述
    解决方案:dog对象赋给了Thread类中的target,thread.start()方法调用start0()方法,start0()方法调用本类的run方法,再调用target的run方法;在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3.2.1 线程代理模式

用代码模拟实现Runnable接口实现线程的机制;

  1. 把Proxy类当作Thread类
public class Thread02 {
    public static void main(String[] args) {
        Tiger tiger = new Tiger();//实现了Runnable接口
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}

实现步骤: 先创建一个Proxy类,继承Runnable

class ThreadProxy implements Runnable {//把Proxy类当作 ThreadProxy 线程代理

    private Runnable target = null;
    @Override
    public void run() {
        if (target != null) {
            target.run();//动态绑定(运行类型Tiger)
        }
    }

    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    public void start() {
        start0();//真正实现多线程的方法
    }

    public void start0() {
        run();
    }
}
  1. 再创建一个普通类,实现Runnable接口;
class Tiger extends Animal implements Runnable{

    @Override
    public void run() {
        System.out.println("老虎发威~~");
    }
}

3.2.2 多线程机制

两个子线程会交替执行
在这里插入图片描述
在这里,主线程看不到
在这里插入图片描述
在这里插入图片描述

3.3 继承Thread Vs 实现Runnable接口

  1. 本质没有区别,因为Thread类本身就实现了Runnable接口的run()方法
  2. 但是实现Runnable接口的方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制;(建议使用Runnable接口)
  3. 继承Thread实现线程无法做到资源共享,并且受到单继承的限制;
    在这里插入图片描述

3.3.1 售票系统超卖问题

sleep()要在–tickets前面才能超卖

public class SellTicket {
    public static void main(String[] args) {
        /*SellTicket1 sellTicket1 = new SellTicket1();
        SellTicket1 sellTicket2 = new SellTicket1();
        SellTicket1 sellTicket3 = new SellTicket1();
        sellTicket1.start();
        sellTicket2.start();
        sellTicket3.start();*/
        SellTicket1 sellTicket1 = new SellTicket1();
        Thread thread1 = new Thread(sellTicket1);
        Thread thread2 = new Thread(sellTicket1);
        Thread thread3 = new Thread(sellTicket1);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class SellTicket1 extends Thread {
    //private static int tickets = 100;
    private int tickets = 100;
    int count = 0;
    @Override
    public void run() {
        while (true) {
            if (tickets <= 0) {
                break;
            }
            System.out.println("窗台:" + Thread.currentThread().getName() +
                    "出售了" + (++count) + "张票,剩余票数:" + (--tickets));
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3.3.2 线程终止

  1. 当线程完成任务后,会自动退出;
  2. 可以通过变量来控制run方法退出线程,即通知方式
3.3.2.1 通知线程退出

启动一个线程t,要求在main线程中去停止线程t
解决方案:在主线程中通过Setter方法将loop变量设置为false
在这里插入图片描述
在这里插入图片描述

3.3.3 线程基本方法

方法名作用
setName设置线程名称
getName获取线程名称
setPriority设置线程优先级
getPriority获取线程优先级
interrupt中断线程

在这里插入图片描述

3.3.3 线程中断

  1. interrupt中断线程,但并没有真正的结束线程,所以一般用于中断正在休眠的线程;(中断休眠,唤醒线程)
  2. sleep(),线程的静态方法,使当前线程休眠;
public class ThreadMethod_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.setName("赵志伟");//设置线程名称
        t.setPriority(Thread.MIN_PRIORITY);//设置线程优先级
        t.start();//启动了子线程
        System.out.println(t.getName());//获取线程名称

        //主线程打印5个hi,然后就中断子线程的休眠
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi~~" + i);
        }
        System.out.println(t.getName() + "线程的优先级" + t.getPriority());
        t.interrupt();//执行到interrupt()方法时,中断线程的休眠
    }
}

class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                //获取当前线程的名称
                System.out.println(Thread.currentThread().getName() + " 吃包子~~" + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + " 正在休眠中~~");
                Thread.sleep(20 * 1000);
            } catch (InterruptedException e) {
                //当该线程执行到一个interrupt 方法时,就会catch 一个异常,可以加入自己的业务代码
                // InterruptedException捕获了一个中断异常
                System.out.println(Thread.currentThread().getName() + "被中断了");
            }
        }
    }
}

3.3.4 线程礼让

yield:线程的礼让,让出cpu,让其它线程执行,但礼让的时间不确定,所以不一定礼让成功;

3.3.5 线程插队

join:线程插队,一旦插入成功,则先执行完插入的线程的所有的任务;

public class Thread03 {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        Thread thread = new Thread(t);
        thread.start();
        for (int i = 1; i <= 20; i++) {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "(小弟)吃了" + i + "个");
            if (i == 10) {
                System.out.println("main线程(小弟)让子线程(大哥)先吃");
//                thread.join();//子线程(大哥)插队,大哥吃完小弟再吃
                Thread.yield();//main线程(小弟)礼让,不一定成功...
            }
        }
    }
}

class T implements Runnable {
    private int count = 0;
    private boolean loop = true;
    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程(老大)吃了" + (++count) + "个");
            if (count == 20) {//吃到20个结束
                System.out.println("子线程(大哥)吃完了,main线程(小弟)继续吃");
                break;
            }
        }
    }
    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}
3.3.5.1 线程插队练习
public class ThreadMethodExercise {
    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        Thread thread = new Thread(t1);
        for (int i = 1; i <= 10; i++) {
            Thread.sleep(1000);
            System.out.println("main线程:hi " + i);
            if (i == 5) {
                thread.start();
                thread.join();//子线程启动后直接插队
            }
        }
    }
}

class T1 implements Runnable {
    private int count;
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程: hello " + (++count));
            if (count == 10) {
                break;
            }
        }
    }
}

3.3.6 守护线程

用户线程:也叫工作线程、当线程的任务执行完或通知方式结束;
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束;
常见的守护线程:垃圾回收机制;

主线程结束,子线程并不会结束;如果我们希望主线程结束时,子线程也结束,可以将子线程设置为守护线程;

public class ThreadMehod03 {
    public static void main(String[] args) {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        Thread thread = new Thread(myDaemonThread);
        thread.setDaemon(true);//将子线程设置为守护线程
        thread.start();
        for (int i = 1; i <= 10; i++) {
            System.out.println("主线程" + Thread.currentThread().getName() + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class MyDaemonThread implements Runnable {
    @Override
    public void run() {
        for (; ; ) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程" + Thread.currentThread().getName());
        }
    }
}

3.3.7 线程的声明周期

  1. JDK中用Thread.State枚举表示了线程的七种(六种)状态
    在这里插入图片描述
public class ThreadState_ {
    public static void main(String[] args) throws InterruptedException {
        T2 t2 = new T2();
        Thread thread = new Thread(t2);
        System.out.println(thread.getName() + "状态 " + thread.getState());//NEW
        thread.start();
        //子线程和主线程同时执行
        /*for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);//主线程休眠、主线程输出
            System.out.println(thread.getName() + "状态 " + thread.getState());//RUNNABLE
        }*/
        while (Thread.State.TERMINATED != thread.getState()) {
            Thread.sleep(1000);
            System.out.println(thread.getName() + "状态 " + thread.getState());//RUNNABLE、TIMED_WAITING
        }
        System.out.println(thread.getName() + "状态 " + thread.getState());//TERMINATED
    }
}

class T2 implements Runnable {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);//子线程输出
                try {
                    Thread.sleep(1000);//子线程休眠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}

3.3.8 线程同步机制

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

3.3.9 互斥锁

  1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性;

  2. 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象;

  3. 关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问;

  4. 同步的局限性:会导致程序的执行效率降低;

  5. 同步方法(非静态的)的锁可以是this,也可以是其它对象(要求是同一个对象)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  6. 同步方法(静态的)的锁为当前类本身;

  7. 手段:方法上加锁、代码块上加锁;

  8. 执行了线程对象的wait()方法,当前线程暂停,释放锁;

  9. 同步方法如果没有使用static修饰,默认锁对象为this;

  10. 如果方法使用static修饰,默认锁对象:当前类.class; 如果想在静态方法中,实现一个同步代码块,写类本身;
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3.4.0 死锁

public class DeadLock_ {
    public static void main(String[] args) {
        DeadLockDemo deadLockDemo = new DeadLockDemo(true);
        deadLockDemo.setName("A线程");
        deadLockDemo.start();
        DeadLockDemo deadLockDemo1 = new DeadLockDemo(false);
        deadLockDemo1.setName("B线程");
        deadLockDemo1.start();
    }
}

class DeadLockDemo extends Thread {
    static Object o1 = new Object();//保证多线程,共享一个对象,使用static
    static Object o2 = new Object();
    boolean flag;

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

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

3.4.1 释放锁

  1. 同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁;
  2. 同步方法中遇到break,return,不得不结束线程,释放锁;
  3. 同步方法中出现了未处理的error或者Exception,导致线程异常结束,释放锁;
3.4.1.1 不释放锁
  1. 程序调用Thread.sleep()、Thread.yield() 方法暂停当前线程的执行,但并不会释放锁;
  2. 线程执行同步代码块时,其它线程调用了该线程的suspend()方法将该线程挂起,而该线程不释放锁;
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~ 小团子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值