Java多线程

本文详细介绍了Java中的线程、进程和多线程概念,包括线程的创建、执行、同步与通信。通过实例展示了线程的实现方式,如继承Thread类、实现Runnable接口和Callable接口。同时,探讨了线程状态、线程同步机制如锁和死锁,以及线程池的使用。此外,还讨论了并发问题和解决方案,如线程安全和并发控制。

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

线程、进程、多线程

  • 进程:进程是程序的一次执行过程,是一个动态的概念,是系统资源分配的单位
  • 线程:通常在一个进程中可以包含若干个线程,一个进程中至少有一个线程,不然没有存在的意义,线程是CPU调度和执行的单位
  • 多线程:真正的多线程是有多个CUP,同时执行,如果在只有一个CPU的情况下,同一时间只能执行一个代码,因为切换速度很快,造成了同时执行的假象
  1. 线程就是独立的执行路径
  2. 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程、gc线程
  3. main()称为主线程,为系统入口,用于执行整个程序
  4. 在一个线程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不可人为干预的
  5. 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  6. 线程会带来额外的开销,如CPU调度时间(排队时间),并发控制开销
  7. 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

线程的实现

方式一:继承Thread类

// 继承Thread类,重写run()方法,在run()方法体内编写业务代码
public class Test extends Thread{

    @Override
    public void run() {
        for (int i=0; i<100; i++) {
            System.out.println("我在吃饭-------------------");
        }
    }

    public static void main(String[] args) throws IOException {
        Test test = new Test();
        // 调用start()开启线程
        test.start();
        // 由执行结果可以知道,多个线程是交替执行的,具体执行先后顺序是靠调度器调度的,无法人为干预
        for (int i=0; i<1000 ;i++) {
            System.out.println("我在睡觉");
        }

    }
}

方式二:继承Runnable接口实现(常用)

public class Test implements Runnable{

    @Override
    public void run() {
        for (int i=0; i<100; i++) {
            System.out.println("我在吃饭-------------------");
        }
    }

    public static void main(String[] args) throws IOException {
        Test test = new Test();

        new Thread(test).start();

        for (int i=0; i<1000 ;i++) {
            System.out.println("我在睡觉");
        }

    }
}

方式三:继承Callable接口实现

// 继承Callable接口,重写call()方法,方法体里面编写业务代码
public class Test implements Callable<Boolean> {

    @Override
    public Boolean call() throws Exception {
        for (int i=0; i<100; i++) {
            System.out.println("我在吃饭-------------------");
        }
        return true;
    }


    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Test test = new Test();

        // 创建服务
        ExecutorService ser = Executors.newFixedThreadPool(1);
        
        // 提交执行(开启线程)
        Future<Boolean> result = ser.submit(test);

        // 用get获取返回值,但是get()会阻塞线程
        //System.out.println(result.get());

        // 关闭服务
        ser.shutdownNow();

        for (int i=0; i<1000 ;i++) {
            System.out.println("我在睡觉");
        }
    }
}

初识并发问题

并发问题:多个线程操作同时操作共享数据所导致的

Demo:抢票

public class Test implements Runnable{
    private int tecikNums = 10;

    @Override
    public void run() {
        while(true) {
            if (tecikNums != 0) {
                try {
                    tecikNums--;
                    System.out.println(Thread.currentThread().getName() + "==>" + "拿到了第" + tecikNums + "张票");
                    // 线程休眠,模拟延时
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                break;
            }
        }
    }

    public static void main(String[] args) throws IOException {
        Test test = new Test();

        new Thread(test,"黄牛").start();
        new Thread(test,"小明").start();
        new Thread(test,"小红").start();
    }

}

Demo:龟兔赛跑

public class Test implements Runnable{
    private static String winner;

    @Override
    public void run() {

        for (int i=0; i<=100; i++) {
            if (gameOver(i)) {
                break;
            }

            // 通过线程名选择对应操作
            if (Thread.currentThread().getName().equals("乌龟")) {
                // 乌龟每一步都比兔子慢10毫秒
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("乌龟走了第" + i + "步");
            }else if (Thread.currentThread().getName().equals("兔子")) {
                // 兔子走到第50步的时候,模拟兔子睡觉
                if (i==50) {
                    try {
                        Thread.sleep(1700);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("兔子走了第" + i + "步");
            }
        }
    }

    // 开启乌龟和兔子两个线程
    public static void main(String[] args) {
        Test test = new Test();

        new Thread(test,"乌龟").start();
        new Thread(test,"兔子").start();
    }

    // 判断比赛是否继续
    public boolean gameOver(int step) {
        if (winner != null) {
            return true;
        }else if (step == 100) {
            winner = Thread.currentThread().getName();
            System.out.println("胜利者:" + winner);
            return true;
        }
        return false;
    }
}

线程方法

  • 获取当前线程名字
Thread.currentThread().getName()
  • 线程休眠
// 每个对象都有一个锁,sleep不会释放锁
// 休眠1秒
Thread.Sleep(1000);
  • 线程礼让
// 线程礼让是让当前进程暂停,转为就绪状态,让CUP重新调度,所以礼让不一定成功,主要看CPU怎么调度
Thread.yield();
  • 线程强制执行
// 调用join方法会让主线程处于阻塞状态,先将线程内的内容执行完毕,再次开始执行主线程
Demo demo = new Demo();
Thread thread = new Thread(demo);
thread.join();
  • 线程优先级:优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,还是得看调度器的调度
// 线程的优先级用数字表示,范围1~10
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
// 获取线程优先级
Demo demo = new Demo();
Thread thread = new Thread(demo);
thread.getPriority();
// 设置线程优先级
thread.setPriority(xxx);

// 线程优先级的实现原理,类似于买彩票,买一张中奖概率小,那就买100张,其实这里设置优先级就是增加提供给调度器的线程数量,数量越大,就越容易被调度

Lamda表达式

函数式接口:只包含一个方法的接口就是函数式接口,也叫功能性接口

Lamda简化了匿名内部类,方法引用简化了lamda

基本语法:接口 对象 = (参数表) -> {代码实现};

// 定义一个函数式接口
interface Demo {
    void test(int i);
}

// Lamda表达式用法
public class Lamda {
    public static void main(String[] args) {
        // 基本写法
        Demo demo01 = (int i) -> {
            System.out.println("Hello World" + i);
        };
        demo01.test(10);

        // 简化一:参数类型可省略
        Demo demo02 = (i) -> {
            System.out.println("Hello World" + i);
        };
        demo02.test(10);

        // 简化二:括号可省略
        Demo demo03 = i -> {
            System.out.println("Hello World" + i);
        };
        demo03.test(10);

        // 简化三:如果只有一行业务代码,那么花括号可省略
        Demo demo04 = i -> System.out.println("Hello World" + i);
        demo04.test(10);
    }
}

Lamda表达式在多线程中的运用

public class Test {
    public static void main(String[] args) throws IOException {
        // 用Lamda表达式+匿名类的方式实现了Runnable接口
        new Thread(() -> {
            for (int i=0; i<100; i++) {
                System.out.println("我在吃饭-------------------");
            };
        }).start();

        // 由执行结果可以知道,多个线程是交替执行的,具体是靠CUP调度,无法人为干预
        for (int i=0; i<1000 ;i++) {
            System.out.println("我在睡觉");
        }
    }
}

线程状态

线程中断后,进入死亡状态,就不可再次启动了

public class Test implements Runnable{

    @Override
    public void run() {
        for (int i=0; i<5; i++) {
            try {
                Thread.sleep(1000); //TIMED_WAITING
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("线程终止了");
    }

    public static void main(String[] args) throws InterruptedException {

        Test test = new Test();
        Thread thread = new Thread(test);

        Thread.State state = thread.getState();
        System.out.println(state); //NEW

        thread.start();
        state = thread.getState();
        System.out.println(state); //RUNNABLE

        // 只要线程不终止,就一直打印线程状态
        while (Thread.State.TERMINATED != state) {
            Thread.sleep(200);
            state = thread.getState();
            System.out.println(state);
        }
    }
}

线程停止

JDK提供了stop()和destroy()方法来停止线程,但是这两个方法都已经废除,不推荐使用

最好的做法是,自己创建一个标志位来控制线程的停止,达到某一条件,就自动停止

public class Test extends Thread{
    private static boolean flag = false;


    @Override
    public void run() {
        for (int i=0; i<100; i++) {
            if (flag) {
                break;
            }
            System.out.println("我在吃饭-------------------");
        }
    }

    public static void main(String[] args) throws IOException {
        Test test = new Test();
        // 调用start()开启线程
        test.start();
        // 由执行结果可以知道,多个线程是交替执行的,具体是靠CUP调度,无法人为干预
        for (int i=0; i<1000 ;i++) {
            if (i==50) {
                flag = true;
                System.out.println("线程停止了");
            }
            System.out.println("我在睡觉");
        }

    }
}

线程休眠

  • sleep时间制定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁
try {
    Thread.sleep(5000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

线程礼让

  • 线程礼让,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态重新转为就绪状态,等待调度器调度
  • 礼让不一定成功,主要还是看调度器的调度
public class Test implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开始执行");
        // 线程礼让
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "线程停止执行");
    }

    public static void main(String[] args) {
        Test test = new Test();

        new Thread(test,"A").start();
        new Thread(test,"B").start();
    }
}

线程强制执行

join合并线程,待此线程执行完成后,再执行其他线程,其他线程会阻塞

public class Test implements Runnable{

    @Override
    public void run() {
       for (int i=0; i<=500; i++) {
           System.out.println("插队线程正在执行" + i);
       }

    }

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        Thread thread = new Thread(test);
        thread.start();

        for (int i=0; i<=100; i++) {
            if (i == 50) {
                // 主线程执行到50的时候,插队线程开始执行,因为阻塞,一直要到插队线程执行完毕,主线程才会接着执行
                thread.join();
            }
            System.out.println("主线程在执行" + i);
        }
    }
}

线程优先级

Java提供一个线程调度器来监控处于就绪状态的所有线程,线程调度器按照优先级觉得线程执行顺序的先后,优先级低也不代表一定后执行,主要还是调度器控制

// 最小优先级
public final static int MIN_PRIORITY = 1;
// 默认优先级
public final static int NORM_PRIORITY = 5;
// 最大优先级
public final static int MAX_PRIORITY = 10;

获取、设置优先级,最好在线程开启之前进行设置

Test test = new Test();
Thread thread = new Thread(test);
// 获取
thread.getPriority();

// 设置
thread.setPriority(4);

// 线程开启
thread.start();

守护线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
Test test = new Test();
Thread thread = new Thread(test);

thread.setDaemon(true); //默认为false,表示用户线程,一般创建的都为用户线程,true为守护线程

线程同步

  • 形成条件:队列+锁
  • 线程同步是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池,形成队列,前面的线程使用完毕,下一个线程再使用
  • 由于同一进程的多个线程共享同一块存储空间,为了避免访问冲突,加入了锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后再释放锁

使用锁存在一些问题:

  1. 一个线程持有锁会导致其他所有需要此锁的线程挂起
  2. 在多线程竞争下,加锁、释放锁会导致较多的上下文切换和调度延时,引起性能问题
  3. 一个优先级高的线程等待一个优先级低的线程时,会导致优先级倒置,引起性能问题

线程锁

  • 线程同步是依靠锁实现的,锁又分为同步方法和同步代码块两种

  • 对于普通同步方法,锁的是当前实例对象。 如果有多个实例 那么锁的对象必然不同,就无法实现同步。

  • 对于静态同步方法,锁的是当前类的Class对象。有多个实例 但是锁对象是相同的 可以实现同步。

  • 对于同步代码块,锁的是Synchonized括号里的对象。对象最好是线程操作的公共资源

// 同步方法:多个线程时,为保证一个方法被一个线程执行时不被影响,需要锁住此方法,一般对于增删改操作才上锁,默认锁住的是当前方法的所在类的实例对象
private synchronized void buy(){

}

// 同步块:写在方法中,多个对象使用同一共享资源时,为了不被影响,需要锁住此资源
synchronized(Obj){

}

Lock与Synchonized功能相似,显式定义了锁,配合异常使用,一般在finally里面关闭

public class Lock implements Runnable{
    private int ticks = 10;

    ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                if (ticks>0) {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + "拿到了第" + ticks + "张票");
                    ticks--;
                }else {
                    break;
                }
            } catch (Exception e) {

            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        Lock lock = new Lock();
        
        new Thread(lock,"线程一").start();
        new Thread(lock,"线程二").start();
        new Thread(lock,"线程三").start();
    }
}

Lock与Synchonized的区别:

  • Lock是显式锁(手动开启与关闭),Synchonized是隐式锁,出作用域自动关闭
  • Lock只有代码块锁,Synchonized有代码块锁和方法锁
  • Lock性能好
  • 使用顺序:Lock > 同步代码块 > 同步方法

死锁

简单死锁现象

public class Test extends Thread{
    // 玩具刀和玩具枪都只有一份
    static Knife knife = new Knife();
    static Gun gun = new Gun();

    @Override
    public void run() {

        if (Thread.currentThread().getName().equals("小明")) {
            synchronized (knife) {
                // 小明在获得玩具枪的同时,还想去获取玩具刀,但是玩具刀在小黄那里,无法获取,于是小明就等待,小黄也在等待小明使用完玩具枪这个资源,两个线程互相等待,就形成了死锁现象
                System.out.println("小明得到了玩具枪");
                synchronized (gun) {
                    System.out.println("小明得到了玩具刀");
                }
            }
        }else {
            synchronized (gun) {
                System.out.println("小黄得到了玩具刀");
                synchronized (knife) {
                    System.out.println("小明得到了玩具刀");
                }
            }
        }
    }

    public static void main(String[] args) {
        Test test = new Test();

        Thread thread1 = new Thread(test,"小明");
        Thread thread2 = new Thread(test,"小黄");

        thread1.start();
        thread2.start();

    }
}

// 刀
class Knife  {

}

// 枪
class Gun {

}

解决:在抱有资源的情况下,尽量不要去抢夺资源

public class Test extends Thread{
    // 玩具刀和玩具枪都只有一份
    static Knife knife = new Knife();
    static Gun gun = new Gun();

    @Override
    public void run() {

        if (Thread.currentThread().getName().equals("小明")) {
            // 这一次,小黄和小明都在资源使用完成后再访问别的资源,所以避免了死锁
            synchronized (knife) {
                System.out.println("小明得到了玩具枪");
            }
            synchronized (gun) {
                System.out.println("小明得到了玩具刀");
            }
        }else {
            synchronized (gun) {
                System.out.println("小黄得到了玩具刀");
            }
            synchronized (knife) {
                System.out.println("小明得到了玩具刀");
            }
        }
    }

    public static void main(String[] args) {
        Test test = new Test();

        Thread thread1 = new Thread(test,"小明");
        Thread thread2 = new Thread(test,"小黄");

        thread1.start();
        thread2.start();

    }
}

// 刀
class Knife  {

}

// 枪
class Gun {

}

线程通信——生成者与消费者关系问题

管程法

利用一个缓冲区来解决问题

public class Test {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();

        new Productor(container).start();
        new Consumer(container).start();
    }

}

// 生产者
class Productor extends Thread {
    // 获取容器
    SynContainer container;

    // 构造方法传入容器
    public Productor(SynContainer container) {
        this.container = container;
    }

    // 生产者的线程操作
    @Override
    public void run() {
        try {
            for (int i=0; i<20; i++) {
                System.out.println("生产了第" + (i+1) + "只鸡");
                container.push(new Chicken(i+1));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

// 消费者
class  Consumer extends Thread {
    SynContainer container;

    public Consumer(SynContainer container) {
        this.container = container;
    }

    @Override
    public void run() {
        try {
            for (int i=0; i<20; i++) {
                System.out.println("消费了第" + container.pop().getId() + "只鸡");
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

// 产品
class Chicken {
    // 产品编号
    private int id;

    public Chicken(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

// 缓冲区
class SynContainer {
    // 容器大小
    static Chicken[] chickens = new Chicken[10];
    // 计数器
    int count = 0;

    // 生产者放入商品
    public synchronized void push(Chicken chicken) throws InterruptedException {
        if (count == chickens.length) {
            // 容器满了,生产者停止生产,等待消费者消费
            this.wait();
        }

        // 容器没满,放入产品到容器
        chickens[count] = chicken;
        count++;

        //可以通知消费者消费
        this.notify();
    }

    public synchronized Chicken pop() throws InterruptedException {
        // 判断是否能消费
        if (count == 0) {
            // 容器为空,停止消费,等待生产者生产
            this.wait();
        }

        // 容器不为空,从容器拿出产品
        count--;
        Chicken chicken = chickens[count];

        //可以通知消费者消费
        this.notify();

        return chicken;
    }
}

信号灯法

利用一个标识符来解决问题

public class Test02 {
    public static void main(String[] args) {
        Process process = new Process();
        new Productor(process).start();
        new Consumer(process).start();
    }
}

// 生产者
class Productor extends Thread {
    Process process;

    // 构造方法传入容器
    public Productor(Process process) {
        this.process = process;
    }

    // 生产者的线程操作
    @Override
    public void run() {
        for (int i=0; i<20; i++) {
            try {
                this.process.push(new Chicken(i+1));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 消费者
class  Consumer extends Thread {
    Process process;

    // 构造方法传入容器
    public Consumer(Process process) {
        this.process = process;
    }

    // 消费者的线程操作
    @Override
    public void run() {
        for (int i=0; i<20; i++) {
            try {
                process.pop();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 产品
class Chicken {
    // 产品编号
    private int id;

    public Chicken() {
    }

    public Chicken(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "鸡,id为" + id;
    }
}

// 过程
class Process {
    Chicken chicken;

    // 标识符  true时消费者等待  false时生产者等待
    private boolean flag = true;

    // 生产过程
    public synchronized void push(Chicken chicken) throws InterruptedException {
        if (!flag) {
            this.wait();
        }

        System.out.println("生产了" + chicken);
        this.notifyAll();
        this.chicken = chicken;
        this.flag = !this.flag;
    }

    // 消费过程
    public  synchronized void pop() throws InterruptedException {
        if (flag) {
            this.wait();
        }

        System.out.println("消费了" + chicken);
        this.notifyAll();
        this.flag = !this.flag;

    }
}

线程池

// 1.创建服务,创建线程池,参数为线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);

// 2.执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());

// 3.关闭连接
service.shutdown();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值