15、线程(基础)

这篇博客详细介绍了Java中的线程基础知识,包括线程的概念、创建方式(继承Thread类和实现Runnable接口)、线程的生命周期及状态转换,重点讨论了线程同步机制如Synchronized关键字和互斥锁,并提醒了避免线程死锁的重要性。同时,文章还提到了线程锁的释放条件和相关练习题,帮助读者深入理解线程管理。

一、线程

1、概念介绍

2、单线程和多线程

3、并发和并行

二、线程基本使用

1、创建线程的两种方式

(1)案例1:继承 Thread 类

注:在多线程编程里面,不是main方法结束了,就意味着进程就结束了

//演示通过继承 Thread 类创建线程
public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        //创建一个Cat 对象,可以当成线程使用
        Cat cat = new Cat();
        cat.start();//启动线程
        // cat.run(); // run方法就是一个普通的方法,并没有真正的启动一个线程,
        // 就会把run方法执行完毕,才会向下执行, 即会阻塞
        //说明:当main线程启动一个子线程后,主线程不会阻塞,会继续执行
        //这时,主线程和子线程是交替执行的
        System.out.println("主线程继续执行"+"名字为:"+Thread.currentThread().getName());//名字为main
        for (int i = 0; i < 60; i++) {
            System.out.println("主线程 i+"+i);
            //让主线程休眠
            Thread.sleep(1000);
        }
    }
}


//说明
//1、当一个类继承了 Thread 类,该类就可以当做线程使用
//2、我们会重写 run 方法,写上自己的业务代码
//3、run Thread 类实现了 Runnable 接口的run方法
class Cat extends Thread{
    int times = 0;
    @Override
    public void run() { //重写 run 方法,写上自己的逻辑
        while (true) {
            //每隔一秒,在控制台输出“喵喵,我是小猫咪”
            System.out.println("喵喵,我是小猫咪"+(++times)+"线程名:"+Thread.currentThread().getName());
            //让该线程休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times == 80){
                break;//当times 等于 80,退出 while循环,这时线程也就退出了
            }
        }
    }
}

案例2:实现 Runnable 接口

//通过实现接口 Runnable 来开发线程
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;
    @Override
    public void run() {
        while (true) {
            System.out.println("hi"+"次数="+(++count)+Thread.currentThread().getName());
            //休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count == 10){
                break;
            }
        }
    }
}

案例3:多线程执行

//在main线程启动两个子线程
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;
    @Override
    public void run() {
        //每隔一秒输出“Hello,world”,输出10次
        while (true) {
            System.out.println("Hello,world "+(++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10){
                break;
            }
        }
    }
}
class T2 implements Runnable {


    int count = 0;
    @Override
    public void run() {
        //每隔一秒输出“hi”,输出5次
        while (true) {
            System.out.println("hi "+(++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 5){
                break;
            }
        }
    }
}

2、继承 Thread 与实现 Runnable 的区别

3、线程终止

案例:

public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        t1.start();


        //如果希望main线程去控制t1线程的终止,必须可以修改 loop(设置一个set方法)
        //让t1退出run方法,从而终止t1线程 -> 通知方式


        //让主线程休眠 10s,再通知t1线程退出
        Thread.sleep(10*1000);
        t1.setLoop(false);
    }
}
class T extends Thread{
    private int count = 0;
    //设置一个控制变量
    private boolean loop = true;
    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T在运行中"+(++count));
        }
    }


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

4、线程常用方法

public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        T2 t2 = new T2();
        t2.start();
        for (int i = 0; i <= 20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程" + i);
            if (i == 5) {
                System.out.println("主线程让子线程");
                t2.join();//这里相当于让 t2 线程先执行完毕
                //Thread.yield();//礼让,不一定成功
                System.out.println("子线程运行完毕");
            }
        }
    }
}
class T2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <= 20; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程" + i);
        }
    }
}

5、用户线程和守护线程

案例:如何将一个线程设置为守护线程

三、线程的生命周期(七种状态)

线程状态转化图:

四、线程同步机制(Synchronized

1、线程同步机制

2、同步具体方法---Synchronized

案例:三个窗口同时卖票

/使用多线程模拟三个窗口同时售票 100 张
public class SellTicket {
    public static void main(String[] args) {


        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
    }
}
//使用接口方式,使用同步方法 synchronized 实现线程同步
class SellTicket03 implements Runnable {
    private int ticketNum = 100;//让多个线程共享ticketNum
    private boolean loop = true;//控制run方法的变量
    public synchronized void sell(){//用的同步方法,在同一时刻,只能有一个线程来执行 sell 方法
        if (ticketNum <= 0){
            System.out.println("售票结束");
            loop = false;
            return;//退出方法
        }


        //休眠50毫秒,模拟
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        System.out.println("窗口 "+ Thread.currentThread().getName()+" 售出一张票"
                +" 剩余票数"+(--ticketNum));
    }


    @Override
    public void run() {
        while (loop) {
            sell();//sell是一个同步方法
        }
    }
}

同步原理:

3、互斥锁

//使用多线程模拟三个窗口同时售票 100 张
public class SellTicket {
    public static void main(String[] args) {


        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
    }
}
//使用接口方式,使用同步方法 synchronized 实现线程同步
class SellTicket03 implements Runnable {
    private int ticketNum = 100;//让多个线程共享ticketNum
    private boolean loop = true;//控制run方法的变量
    Object object = new Object();


    //同步方法(静态的)的锁为当前对象
    //说明:
    //1、public synchronized static void m1(){} 锁是加在 SellTicket03.class 上的
    //2、如果在静态方法中,实现一个同步代码块
    /*
        synchronized(SellTicket03.class){
            System.out.println("m2");
        }
     */
    public synchronized static void m1(){
        
    }
    public static void m2(){
        synchronized(SellTicket03.class){
            System.out.println("m2");
        }
    }


    //说明:
    //1、public synchronized void sell(){} 就是一个同步方法
    //2、这时锁在 this 对象
    //3、也可以在代码块上写 synchronized,同步代码块,互斥锁还是在 this 对象
    public /*synchronized*/ void sell() {//用的同步方法,在同一时刻,只能有一个线程来执行 sell 方法


        //三个线程操作的是同一个Object
        synchronized (/*this*/object) {//同步代码块,互斥锁还是在 this 对象
            if (ticketNum <= 0) {
                System.out.println("售票结束");
                loop = false;
                return;//退出方法
            }


            //休眠50毫秒,模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                    + " 剩余票数" + (--ticketNum));
        }
    }
    @Override
    public void run() {
        while (loop) {
            sell();//sell是一个同步方法
        }
    }
}

互斥锁的注意事项:

注:1、要求多个线程的锁对象为同一个

       2、尽量选择同步代码块,因为同步代码块锁的范围小,效率高

五、线程死锁

注:死锁非常危险,写代码时一定要避免

六、释放锁

1、下面操作会释放锁

2、下面操作不会释放锁

练习1:

public class HomeWork01 {
    public static void main(String[] args) {
        A a = new A();
        a.start();
        B b = new B(a);//一定要注意传入a
        b.start();
    }
}
//创建A线程类
class A extends Thread{
    private boolean loop = true;


    public void setLoop(boolean loop) {//可以修改loop变量
        this.loop = loop;
    }


    @Override
    public void run() {
        //输出1到100
        while (loop) {
            System.out.println((int)(Math.random()*100+1));
            //休眠
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


    }
}


//直到第2个线程从键盘读取了“Q”命令
class B extends Thread{
    private A a;
    private Scanner scanner = new Scanner(System.in);
    
    public B(A a) {//构造器中,直接传入A类对象
        this.a = a;
    }


    @Override
    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:(一定要理解原理)

public class HomeWork02 {
    public static void main(String[] args) {
        T t = new T();
        Thread thread1 = new Thread(t);
        thread1.setName("t1");
        Thread thread2 = new Thread(t);
        thread2.setName("t2");
        thread1.start();
        thread2.start();


    }
}


//编辑取款线程
//1、因为这里涉及到多个线程共享资源,所以我们使用实现Runnable方式
class T implements Runnable {
    private int money = 10000;


    @Override
    public void run() {
        while (true) {
            //1、这里使用了synchronized实现了线程同步
            //2、当多个线程执行到这里时,就会去争夺 this 对象锁
            //3、哪个线程争夺到(获取到)this对象锁就执行 synchronized 代码块,执行完后,会释放this对象锁
            //4、争夺不到的this对象锁的就会被阻塞(blocked),准备继续下次争夺
            synchronized(this) {
                //判断余额是否足够
                if (money < 1000) {
                    System.out.println("余额不足");
                    break;
                }
                money -= 1000;
                System.out.println(Thread.currentThread().getName()
                        + "取出了1000块,当前余额= " + money);
            }
            //休眠
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值