Java多线程基础

本文详细介绍了Java多线程的基础知识,包括线程的概念、单线程与多线程的区别、并发与并行的解释,以及线程的创建、终止、常用方法和线程同步。还探讨了synchronized关键字在解决线程同步问题中的应用,以及互斥锁的原理和使用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.线程相关概念

1.1 程序(program)

1.2 进程

1.3 什么是线程

1.4 单线程和多线程

1.5 并发和并行

2. 线程的基本使用

2.1 创建线程的两种方式

2.1.1  继承Thread类创建线程

2.1.2 实现Runable接口创建线程

2.1.3 多线程执行

2.1.4 如何理解线程

3. 继承 Thread 实现 Runnable 的区别

4. 线程终止

4.1 基本说明

4.2 应用案例 

5. 线程常用方法

5.1 常用方法(1)

5.2 注意事项和细节

5.3 常用方法 (2)

5.4  yield和join案例

5.5 用户线程和守护线程

5.5.1 用户线程

5.5.2 守护线程

6.线程的生命周期

6.1 JDK中用Thread.State枚举了线程的几种状态

6.2 线程状态转换图

​6.3 查看线程状态

7. 线程的同步

7.1 问题引出

7.2 Synchronized

7.2.1 线程同步机制

7.2.2 同步具体方法-Synchronized

同步代码块

8. 分析同步原理

9. 互斥锁

9.1基本介绍

9.2 使用互斥锁来解决售票问题


1.线程相关概念

1.1 程序(program)

什么是程序:

是为了完成特定任务,用某种语言编写的一组指令的集合。简而言之,就是我们写的代码

1.2 进程

  1. 进程是指运行中的程序,每启动一个进程,操作系统就会为该进程分配内存空间。
  2. 进程是程序的一次执行过程,或是正在运行的一个程序,是动态过程:他有自身的产生、存在和消亡的过程。

1.3 什么是线程

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

1.4 单线程和多线程

  • 单线程:同一个时刻,只允许执行一个线程。
  • 多线程:同一个时刻,可以执行多个线程。ps:一个QQ进程,可以同时打开多个聊天窗口;一个迅雷进程,可以同时下载多个文件。

1.5 并发和并行

  • 并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉。ps:单核cpu实现的多任务就是并发。
  • 并行:同一个时刻,多个任务同时执行。 多核cpu可以实现并行

2. 线程的基本使用

2.1 创建线程的两种方式

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

2.1.1  继承Thread类创建线程

package thread;

//通过类继承Thread类实现创建线程
public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        //创建Cat,可以当做线程使用
        Cat cat = new Cat();

        //启动线程,最终会执行Cat的run()方法
        cat.start();
        //当main线程启动一个子线程Thread-0,主线程不会阻塞,会继续执行
        //这时,主线程和子线程是交替执行..
        System.out.println("主线程继续执行" + Thread.currentThread().getName());
        //Thread.currentThread().getName()指当前线程的名字
        for (int i = 0; i < 50; i++) {
            System.out.println("主线程i= " + i);
            //主线程休眠
            Thread.sleep(300);
        }
    }
}

//1. Cat继承了 Thread 类, 该类就可以当做线程使用
//2. 重写 run 方法,写自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的 run 方法
class Cat extends Thread {
    int times = 0;

    @Override
    //run方法是一个普通的方法,没有真正的启动一个线程,run方法执行完毕,才向下执行
    public void run() {

        while(true){
            //该线程每隔0.3秒,输出
            System.out.println("喵喵喵~~~" + (++times) + Thread.currentThread().getName() );
            //线程休眠0.3秒  1秒=1000毫秒
            try {
                Thread.sleep(300);//ctrl+alt+t快速处理异常
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times == 50){
                break;//当times到达50,退出while,线程也就退出
            }
        }
    }
}

2.1.2 实现Runable接口创建线程

  1. java是单继承的,在某些情况下一个类可能已经继承了某个父类,这是再用继承Thread类方法来创建线程显然不可能了。
  2. 通过实现Runnable接口来创建线程,可以避免1.的情况。

package thread;

//通过实现Runable接口,实现创建线程
public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        //创建了 Thread 对象,把 dog 对象(实现了 Runnable接口),放入 Thread
        Thread thread1 = new Thread(dog);
        Thread thread2 = new Thread(dog);
        //这里不能用dog调用start()方法,
        thread1.start();
        thread2.start();
    }
}
class Dog implements 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 == 50)
                break;
            }
        }
    }

2.1.3 多线程执行

package thread;

public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread2 = new Thread(t2);
        t1.start();
        thread2.start();
    }
}
class T1 extends Thread{
    int counts = 0;

    @Override
    public void run() {
        while(true){
            System.out.println("hello,world!" + ++counts);
            try {
                Thread.sleep( 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(counts == 10)
                break;
        }
    }
}
class T2 implements Runnable{
    int counts = 0;
    @Override
    public void run() {
        while(true){
            System.out.println("hi!" + ++counts);
            try {
                Thread.sleep( 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(counts == 5)
                break;
        }
    }
}


2.1.4 如何理解线程

3. 继承 Thread 实现 Runnable 的区别

  1. 从Java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,因为Thread类本身就实现了Runnable接口
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的机制,可以理解为对于单继承机制创建线程的一个补充(建议使用

4. 线程终止

4.1 基本说明

  1. 当线程完成任务后,会自动退出。
  2. 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式

4.2 应用案例 

需求:启动一个线程,要求在main线程中去停止线程

package thread;

public class Thread04 {
    public static void main(String[] args) throws InterruptedException {
        int maincounts = 0;
        AThread aThread = new AThread();
        new Thread(aThread).start();
        for (int i = 0; i < 50 ; i++) {
            System.out.println("main线程 运行中" + ++maincounts);
            Thread.sleep(500);
            //中断AThread线程
            if(i == 20)
                aThread.setLoop(false);
        }
    }
}

class AThread implements Runnable{
    boolean loop = true;
    int AThreadcounts = 0;

    @Override
    public void run() {
        while(loop){
            System.out.println("AThread线程 运行中..." + ++AThreadcounts);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //提供一个公共的set方法,修改loop
    public boolean setLoop(boolean loop){
        this.loop = loop;
        return loop;
    }
}

5. 线程常用方法

5.1 常用方法(1)

1setName()设置线程名称,使之与参数name相同
2getName()      返回该线程的名称
3start()使该线程开始执行;Java虚拟机底层调用该线程的start()方法
4run()调用线程对象run方法
5setPriority()更改线程的优先级
6getPriority()获取线程的优先级
7sleep()在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
8interrupt()中断线程(不是终止线程,而是中断线程休眠

5.2 注意事项和细节

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

5.3 常用方法 (2)

  1. yield:线程的礼让。让出cpu,让其他线程执行,但礼让时间不确定,所以也不一定礼让成功。
  2. join:线程的插队。插队的进程一旦插队成功,则先执行完插入的线程的所有任务。

 

5.4  yield和join案例

ThreadMethod02.java 

package thread;

public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        T02 t02 = new T02();
        t02.start();
        for (int i = 1; i < 20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程(小弟) 吃了 " + i + "个包子");
            if (i == 5) {

                //t02.join();//当i=5时,让t02线程插队,即先让子线程(老大)先吃包子,老大吃完,小弟才能吃

                Thread.yield();//主线程主动礼让,让其他线程执行,但礼让的时间不确定,不一定礼让成功
            }
        }

    }
}

class T02 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i < 20; i++) {
            System.out.println("子线程(老大) 吃了 " + i + "个包子");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

5.5 用户线程和守护线程

5.5.1 用户线程

用户线程:也叫工作线程,当线程的任务执行完或通知方式结束。

Java中有3条主要线程:

1. main主线程

2.垃圾回收线程

3.异常处理线程

5.5.2 守护线程

1.守护线程:一般是为工作线程服务的,当所有用户线程结束,守护线程自动结束。

2.常见的守护线程:垃圾回收机制(Garbage Collector),简称GC。


守护线程案例:ThreadMethod03.java

设置子线程(t05)为守护线程,当主线程结束后,该线程也就自动结束

package thread;

public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        Thread t05 = new Thread(new T05());
        //设置子线程(t05)为守护线程,即将setDaemon(true),这样当主线程结束时,子线程也会自动结束
        t05.setDaemon(true);//守护线程要在启动线程之前定义,否则会报异常
        t05.start();

        for (int i = 1; i <= 10; i++) {
            System.out.println("主线程在工作中..." + i);
            Thread.sleep(500);
        }
    }
}

class T05 implements Runnable {
    @Override
    public void run() {
        int counts = 0;
        //设置一个无限循环的子线程,若不加守护线程,则主线程执行完毕后,子线程仍然会不停执行循环
        while (true) {
            System.out.println("子线程在工作中..." + ++counts);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

6.线程的生命周期

6.1 线程的状态

JDK中用Thread.State枚举了线程的几种状态:

NEW(创建状态)尚未启动的线程(new创建后未启动)
RUNNABLE(可运行状态)

在Java虚拟机中执行的线程,还可划分为就绪态(Ready)和运行态(running)

BLOCKED(阻塞状态)被阻塞等待监视器锁定的线程
WAITING(等待状态)正在等待另一个线程执行特定动作的线程
TIMED WAITING(超时等待状态)正在等待另一个线程执行动作达到指定等待时间的线程
TERMINATED(终止状态)已经退出的线程

6.2 线程状态转换图

6.3 查看线程状态

使用getState()方法查看线程状态

案例:ThreadState.java

package thread;

public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        T08 t08 = new T08();
        Thread thread08 = new Thread(t08);
        //new之后的状态为
        System.out.println(thread08.getName() + "的状态为" + thread08.getState());

        thread08.start();
        //启动后的状态
        while(Thread.State.TERMINATED != thread08.getState()){
            System.out.println(thread08.getName() + "的状态为" + thread08.getState());
            Thread.sleep(300);
        }
        //结束后的状态
        System.out.println(thread08.getName() + "的状态为" + thread08.getState());

    }
}

class T08 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Hello~" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

7. 线程的同步

7.1 问题引出

售票超卖问题

class Sell implements Runnable {
    int ticket = 30;
    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + " 卖出了一张票,剩余票数为 " + --ticket);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (ticket == 0) {
                System.out.println("票卖完了");
                break;
            }
        }
    }
}

如果多个线程同时访问run()方法时,此时票数为1,当前线程在执行--ticket之前,下一个线程访问了run()方法,再次执行了一次--ticket,ticket最后结果为-1,此时就会造成超卖的问题,那么该如何解决呢?

7.2 Synchronized

7.2.1 线程同步机制

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

7.2.2 同步具体方法-Synchronized

同步代码块

1.synchroniized放在对象前,得到对象的锁,才能操作同步代码。

 格式为:

synchroniized(对象){

        //需要被同步的代码

        }

2.synchroniized还可以放在方法声明中,表示整个方法为同步方法。

格式为:

        public synchroniized void m (String name){

        //需要被同步的代码

        }

8. 分析同步原理

t1,t2,t3线程同时争夺互斥锁,争夺到的执行同步代码块,输出后,将锁放回,重新争夺。

9. 互斥锁

9.1基本介绍

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

9.2 使用互斥锁来解决售票问题

1.代码块加锁         2.方法上加锁

package Thread_;

public class SellTicket {
    public static void main(String[] args) {
//测试
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
//
// //这里我们会出现超卖..
// sellTicket01.start();//启动售票线程
// sellTicket02.start();//启动售票线程
// sellTicket03.start();//启动售票线程
// System.out.println("===使用实现接口方式来售票=====");
// SellTicket02 sellTicket02 = new SellTicket02();
//
// new Thread(sellTicket02).start();//第 1 个线程-窗口
// new Thread(sellTicket02).start();//第 2 个线程-窗口
// new Thread(sellTicket02).start();//第 3 个线程-窗口
//测试一把
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();//第 1 个线程-窗口
        new Thread(sellTicket03).start();//第 2 个线程-窗口
        new Thread(sellTicket03).start();//第 3 个线程-窗口
    }
}
//实现接口方式, 使用 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. 如果在静态方法中,实现一个同步代码块.

    public synchronized static void m1() {
    }
    public static void m2() {
        synchronized (SellTicket03.class) {
            System.out.println("m2");
        }
    }
//1. public synchronized void sell() {} 就是一个同步方法
//2. 这时锁在 this 对象
//3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在 this 对象
    public /*synchronized*/ void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行 sell 方法

        synchronized (/*this*/ object) {
            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));//1 - 0 - -1 - -2
        }
    }
    @Override
    public void run() {
        while (loop) {
            sell();//sell 方法是一共同步方法
        }
    }
}
//使用 Thread 方式
// new SellTicket01().start()
// new SellTicket01().start();
class SellTicket01 extends Thread {
    private static int ticketNum = 100;//让多个线程共享 ticketNum
    // public void m1() {
// synchronized (this) {
// System.out.println("hello");
// }
// }
    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                break;
            }

//休眠 50 毫秒, 模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                    + " 剩余票数=" + (--ticketNum));
        }
    }
}

//实现接口方式
class SellTicket02 implements Runnable {
    private int ticketNum = 100;//让多个线程共享 ticketNum
    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                break;
            }
//休眠 50 毫秒, 模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                    + " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值