Java零基础入门:多线程总结

本文详细介绍了Java中的多线程概念,包括进程、线程的区别,以及创建线程的两种方式:继承Thread类和实现Runnable接口。文中强调了实现Runnable接口的好处,并通过实例展示了如何创建和管理线程。此外,还讲解了线程同步的重要性,如互斥锁和死锁,并给出了实际的售票系统模拟案例,展示了线程同步机制的运用。
摘要由CSDN通过智能技术生成

博主简介:
博客主页:Java知识分享博主
Java零基础入门专栏:Java零基础入门专栏
Java交流社区:飞鸟社区
欢迎阅读,如果文章对你有帮助点赞,支持一下!

什么是多线程

程序、进程、线程

  • 程序:从所周知的,编写代码组成的。

  • 进程:可以理解为运行中的程序,比如启动QQ,就启动一个进程,操作系统会为进程分配内存空间。

  • 线程:由进程创建的,是进程的一个实体。

单线程和多线程的区分:

  • 同一个时刻,只允许执行一个线程

  • 同一个时刻,可以执行多个线程,比如QQ可以同时打开多个聊天窗口

并发和并行(理解)

  • 一个多个任务交替执行,一个多个任务同时执行

创建线程的两种方式

通过继承 Thread 的方法和实现 Runnable 接口的方式创建多线程,哪个好?

实现Runnable接口好,原因有两个:

  • ①、避免了Java单继承的局限性
  • ②、适合多个相同的程序代码去处理同一资源的情况,把线程、代码和数据有效的分离,更符合面向对象的设计思想。

①、继承 Thread 的方法

  1. 当一个类(cat类)继承了 Thread 类, 该类就可以当做线程使用
  2. 我们会重写 run 方法,写上自己的业务代码
  3. run Thread 类 实现了 Runnable 接口的 run
package threaduse;

public class Thread01 {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();//启动线程
    }
}
class Cat extends Thread{
int times=0;
        public void run () { //重写 run 方法,写上自己的业务逻辑
            while(true) {
        System.out.println("猫会跑"+times++);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(times==8){ //当时间到第8秒时,自动跳出
                    break;
                }

            }

    }

}

②、runnable接口实现run方法

1、dog.start();这里不能调用 start
2、创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread

package threaduse;

public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        
        // dog.start();这里不能调用 start
        //创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread
        
        Thread thread = new Thread(dog);
        thread.start();
    }
}
class Dog implements Runnable{//通过实现 Runnable 接口,开发线程
    int count=0;
    public void run(){
        while (true){
            System.out.println("小狗会汪汪叫。。"+(++count)+Thread.currentThread().getName());
            try {
                Thread.sleep(1000); //休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count==8){
                break;
            }
        }

    }
}

为什么要重写run方法

因为run方法是用来封装被线程执行的代码。

run()方法和start()方法有什么区别?

  • run():封装线程执行的代码,直接调用相当于调用普通方法。
  • start():启动线程,然后由JVM 调用此线程的 run() 方法。

设计模式

package threaduse;

public class Thread02 {
    public static void main(String[] args) {
//        Dog dog = new Dog();
//       // dog.start();这里不能调用 start
//        //创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread
//        Thread thread = new Thread(dog);
//        thread.start();


        tiger tiger = new tiger();
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}
class Animal{}
class tiger extends Animal implements Runnable{
    public void run(){
        System.out.println("老虎嗷嗷叫");
    }
}
//线程代理类 , 模拟了一个极简的 Thread
class ThreadProxy implements Runnable{
private Runnable target =null;//属性,类型是Runnable
    public void run() {
        if(target!=null){
            target.run();//动态绑定(运行类型 Tiger)
        }
    }
    public ThreadProxy(Runnable target){
        this.target=target;//构造方法
    }
    public void start(){
        start0();//这个方法时真正实现多线程方法
    }
    public void start0(){
        run();
    }
}
class Dog implements Runnable{//通过实现 Runnable 接口,开发线程
    int count=0;
    public void run(){
        while (true){
            System.out.println("小狗会汪汪叫。。"+(++count)+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count==8){
                break;
            }
        }

    }
}

手动实践编程题

编写一个程序,创建两个线程,一个线程每隔1秒输出“helloworld”,输出10次退出, 一个线程每隔1秒输出“hi”,输出5次退出(可以手动敲一下.狗头)

package threaduse;
//编写一个程序,创建两个线程,一个线程每隔1秒输出“helloworld”,输出10次退出,
// 一个线程每隔1秒输出“hi”,输出5次退出
public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        thread1.start();
        thread2.start();
    }
}
class T1 implements Runnable{
    int count=0;
    public void run(){
        while (true){
            System.out.println("helloworld"+(++count)+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count==10){
                break;
            }
        }
    }
}
class T2 implements Runnable{
    int count=0;
    public void run(){
        while (true){
            System.out.println("hi"+(++count)+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count==5){
                break;
            }
        }

    }
}

编程模拟三个售票窗口售票100张

package threaduse;
//编程模拟三个售票窗口售票100张,
// 分别使用继承Thread和实现Runnable方式,并分析有什么问题?
public class Thread04 {
    public static void main(String[] args) {
        SellTicket sellTicket = new SellTicket();

        new Thread(sellTicket).start();//第一个售票窗口
        new Thread(sellTicket).start();//第二个售票窗口
        new Thread(sellTicket).start();//第三个售票窗口

    }
}
class SellTicket extends Thread{
    private static int  num=100;
    public  void run(){
        while (true){
            if(num<=0){
                System.out.println("售票结束。。");
                break;

            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"+"剩余票数="+(--num));
        }
    }
}

手动实践编程题

启动一个线程t,要求main线程中去停止线程t,关键代码块

成员内部类中

package exit;

public class ThreadExit {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        new Thread(t).start();
        System.out.println("main线程休眠10秒");
        Thread.sleep(10*1000);
        t.setLoop(false);
    }
}
class T implements Runnable{
    private boolean loop=true;
    private int count=0;

    public void run(){
        while(loop){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("a运行中。。。"+(++count));
        }
    }
    public void setLoop(boolean loop){
        this.loop=loop;
    }

}

线程常用方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kwlvk0H3-1654516203869)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20220602184235465.png)]

interrupt 中断线程

在这里插入图片描述

编程题

两个线程同时执行,当主线程执行输出5次时,让子线程执行剩下15次输出,主线程再次执行。

public class ThreadMethodExercise {
    public static void main(String[] args) throws InterruptedException {
        Thread t3 = new Thread(new T3());//创建子线程
        for (int i = 1; i <= 10; i++) {
            System.out.println("hi " + i);
            if(i == 5) {//说明主线程输出了 5 次 hi
                t3.start();//启动子线程 输出 hello... t3.join();
                t3.join();// 立即将 t3 子线程,插入到 main 线程,让 t3 先执行
            }
            Thread.sleep(1000);//输出一次 hi, 让 main 线程也休眠 1s
        }
    }
}

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

Thread.yield;礼让不一定成功
t3.join();插队一定成功

用户线程和守护线程

在这里插入图片描述

//设t为子线程,即设为守护线程,当所有线程结束后,t也自动结束
//如果没有设置,那么main线程执行完毕,t也不退出
//前提是子线程无限循环
t.setDaemon(true);//关键字
t.start();

img

6种线程

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}
package state;

public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        System.out.println(t.getName() + " 状态 " + t.getState());
        t.start();

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

线程同步机制(重点)

在这里插入图片描述

在这里插入图片描述

  • 编程模拟三个售票窗口售票100张,
  • 分别使用继承Thread和实现Runnable方式,并分析有什么问题?
package sys;

public class Thread05 {

    public static void main(String[] args) {

        SellTicket3 sellTicket3 = new SellTicket3();

        new Thread(sellTicket3).start();//第一个售票窗口
        new Thread(sellTicket3).start();//第二个售票窗口
        new Thread(sellTicket3).start();//第三个售票窗口

    }

}
class SellTicket3 implements Runnable{

    private int num=100;
    private boolean loop =true;
    public synchronized void sell(){ //同步线程,只允许一个线程执行

        if(num<=0){
            System.out.println("售票结束。。");
            loop=false;
            return;

        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"+"剩余票数="+(--num));
    }
    public void run(){
        while (loop){

            sell();

        }
    }
}

互斥锁

在这里插入图片描述

//1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
//2. 如果在静态方法中,实现一个同步代码块
/*
    synchronized (SellTicket4.class) {
        System.out.println("m2");
    }
*/
    public synchronized static void m1() {
    }
    public static void m2() {
        synchronized (SellTicket4.class) {
            System.out.println("m2");
        }
  • 1、 public synchronized void sell() {} 就是一个同步方法
  • 2、这时锁在this对象
  • 3、 也可以在代码块上写synchronize,同步代码块,互斥锁还是在this对象
package sys;
//编程模拟三个售票窗口售票100张,
// 分别使用继承Thread和实现Runnable方式,并分析有什么问题?
public class Thread06 {

    public static void main(String[] args) {

        SellTicket4 sellTicket3 = new SellTicket4();

        new Thread(sellTicket3).start();//第一个售票窗口
        new Thread(sellTicket3).start();//第二个售票窗口
        new Thread(sellTicket3).start();//第三个售票窗口

    }

}


class SellTicket4 implements Runnable{

    private int num=50;
    private boolean loop =true;
    Object object=new Object();
    

        public  void sell() { //同步方法,同一个时刻,只允许一个sell线程执行
        synchronized (object) {

            if (num <= 0) {
                System.out.println("售票结束。。");
                loop = false;
                return;

            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票" + "剩余票数=" + (--num));
        }
    }
    public void run(){
        while (loop){

            sell();

        }
    }
}

在这里插入图片描述

线程的死锁

多个线程都占用对方的锁资源,但不肯互让,导致了死锁

package DeadLock;

public class DeadLock{
    public static void main(String[] args) {
        //模拟死锁现象
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A 线程");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B 线程");
        A.start();
        B.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 为 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() + " 进入 1");
                synchronized (o2) { // 这里获得 li 对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入 2");
                }
            }
        } else {
            
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + " 进入 3");
                synchronized (o1) { // 这里获得 li 对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入 4");
                }
            }
        }
    }
}

编程题

在main方法中启动两个线程

第1个线程循环随机打印100以内的整数

直到第2个线程从键盘读取了“Q”命令

package Homework01;

import java.util.Locale;
import java.util.Scanner;

public class Homework01 {
    public static void main(String[] args) {
        A a = new A();
        B b = new B(a);
        a.start();
        b.start();
    }
}
class A extends Thread{
    private boolean loop =true;
    public void run(){

        while(loop){
            System.out.println((int)(Math.random()*100 +1));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}
class B extends Thread{
    private A a;
    private Scanner scanner=new Scanner(System.in);

    public B(A a){
        this.a=a;

    }
    public void run() {
        while (true) {
            //接受用户输入
            System.out.println("请输入(Q)指令结束退出:");
            char key=scanner.next().toUpperCase().charAt(0);
            if(key=='Q'){
                //以通知方式结束a线程
                a.setLoop(false);
                System.out.println("b线程退出");
                break;
            }
        }
    }
}

在这里插入图片描述

编程题

有2个用户分别从同一个卡上取钱(总额:1000)

每次都取1000,当余款不足时,就不能取款了

不能出现超取现象 线程同步问题

package Homework02;


public class Mon01 {
    public static void main(String[] args) {
        A a = new A();

        Thread thread1 = new Thread(a);
        thread1.setName("用户1");
        Thread thread2 = new Thread(a);
        thread2.setName("用户2");
        thread1.start();//第一个用户
        thread2.start();//第二个用户



    }


}
//涉及多个线程共享资源,使用接口的实现
class A implements Runnable{
    private int num=10000;
    public boolean loop=true;


    public void run(){
        while(loop){
           synchronized (this){
                if(num<0){
                    System.out.println("余款不足,不能取款");
                    loop=false;
                    return;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
               num -=1000;
                System.out.println(Thread.currentThread().getName()+" "+"剩余余款:"+num);
            }

        }

    }
}

在这里插入图片描述

评论 54
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序员飞鸟

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

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

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

打赏作者

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

抵扣说明:

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

余额充值