Java多线程(Multithreading)
  y9EYnC7aLifI 2023年12月05日 16 0

什么是线程?

  • 线程是程序运行的基本执行单元,是进程内的一个子任务
  • 进程是一个具有独立功能的程序在一个数据集合上的一次动态执行过程,是操作系统分配资源和调度的基本单位
  • 一个进程可以包含一个或多个线程,每个线程都有自己的程序计数器、栈和局部变量等属性,但是共享进程的内存空间、文件描述符和其他资源
  • 线程之间可以并发或并行执行,可以提高程序的效率和响应性,但也会带来同步和通信等问题

学过计算机操作系统的同学应该都没什么问题

进程和线程的区别

  • 进程之间是独立的地址空间和资源,切换开销大,资源管理和保护较好;线程之间是共享地址空间和资源,切换开销小,资源管理和保护较差
  • 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮,而多线程运行效率更高,但是线程不能独立执行,必须依存在应用程序中,操作系统不会把线程看作多个独立应用

线程状态

线程状态

线程的创建

  1. 通过实现 Runnable 接口

    • 定义一个类实现Runnable接口,并重写run()方法,该方法是线程的执行体;
    • 创建该类的实例对象,并用一个Thread对象来包装它;
    • 调用Thread对象的start()方法来启动线程。
  2. 通过继承 Thread 类本身

    • 定义一个类继承Thread类,并重写run()方法,该方法是线程的执行体;
    • 创建该类的实例对象,即创建了一个线程对象;
    • 调用线程对象的start()方法来启动线程

    示例

    package com.ThreadTest;
    //1.通过实现 Runnable 接口
    public class RunnableDemo implements Runnable{
        @Override
        public void run() {
            System.out.println("Testing Runnable......");
        }
    }
    
    package com.ThreadTest;
    //2.通过继承 Thread 类本身
    public class ThreadDemo extends Thread{
        @Override
        public void run() {
            System.out.println("Testing Thread......");
        }
    }
    
    package com.ThreadTest;
    public class MultiThreadingTest {
        public static void main(String[] args) {
            //通过实现 Runnable 接口
            RunnableDemo runnableDemo = new RunnableDemo();
            Thread thread = new Thread(runnableDemo);
            thread.start();
    
            //通过继承 Thread 类本身
            ThreadDemo threadDemo = new ThreadDemo();
            threadDemo.start();
            
        }
    }
    
    

    输出结果:

    Testing Runnable......
    Testing Thread......
    

    两种方法都需要重写.run(),通过.start()来启动线程,也可通过调用run()来执行

    run()start()的区别

    • run()方法是线程的*执行体*,定义了线程要执行的任务;

    • start()方法是线程的**启动**方法,用来异步地启动一个新线程,并让新线程执行run()方法;

    • 直接调用run()方法,相当于在当前线程中执行run()方法,并不会创建新线程;

    • 调用start()方法,会创建新线程,并让新线程进入就绪状态,等待CPU调度,当新线程获得CPU资源时,会自动调用run()方法;

    • start()方法只能被调用一次,否则会抛出IllegalThreadStateException异常

    多线程的执行过程

    • 一般的程序是从main出发,直线向下进行,只有一条主线

    • 多线程在main主线程序遇到线程程序时会转到线程程序,并返回到主线程序中,这样main程序和线程程序同时执行

多线程案例---抢鞋

package com.ThreadTest;
public class DemoThread implements Runnable{
    private int nike = 10;
    @Override
    public void run() {
        while(true) {
            if(nike > 0) {
                System.out.println(Thread.currentThread().getName() + "抢到了第" + nike-- + "双鞋");
            }
        }

    }
}
package com.ThreadTest;
public class MultiThreadingTest {
    public static void main(String[] args) {
        DemoThread demoThread = new DemoThread();
        new Thread(demoThread,"Lowell").start();
        new Thread(demoThread,"Frank").start();
        new Thread(demoThread,"Tom").start();
    }
}

输出结果:

Lowell抢到了第10双鞋
Tom抢到了第8双鞋
Tom抢到了第6双鞋
Frank抢到了第9双鞋
Tom抢到了第5双鞋
Lowell抢到了第7双鞋
Tom抢到了第3双鞋
Frank抢到了第4双鞋
Tom抢到了第1双鞋
Lowell抢到了第2双鞋

synchronized同步方法

我们将上面第一个代码更改如下:

package com.ThreadTest;
public class DemoThread implements Runnable{
    private int nike = 10;
    @Override
    public void run() {
        while(true) {
            if(nike > 0) {
                try {
                    //进程睡眠1000ms
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "抢到了第" + nike-- + "双鞋");
            }
        }
    }
}

输出结果:

Lowell抢到了第10双鞋
Frank抢到了第10双鞋
Tom抢到了第9双鞋
Frank抢到了第8双鞋
Lowell抢到了第7双鞋
Tom抢到了第8双鞋
Lowell抢到了第5双鞋
Tom抢到了第4双鞋
Frank抢到了第6双鞋
Frank抢到了第3双鞋
Lowell抢到了第1双鞋
Tom抢到了第2双鞋

此时我们发现,出现了两个用户抢同一双鞋的情况,并且我们抢鞋的顺序是随机的。

那我们该如何解决此问题呢?我们引入synchronized

  • synchronized是Java中的一个关键字,它可以保证在同一时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时它还可以保证共享变量的内存可见性
  • synchronized的原理是它使用了对象内部的一个叫做监视器锁(monitor)来实现的,但是监视器锁本质又是依赖于底层的操作系统的互斥锁(Mutex Lock)来实现的,因此它也被称为“重量级锁
  • synchronized可以修饰实例方法、静态方法和代码块,它们的作用范围和作用对象不同:
    • 修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
    • 修饰静态方法:也就是给当前类加锁,会作用于类的所有对象实例,进入同步代码前要获得当前类的锁
    • 修饰代码块:指定加锁对象,对给定对象或类加锁,进入同步代码前要获得给定对象或类的锁
  • synchronized还具有可重入性,即一个线程可以多次获取同一把锁,不会发生死锁

也就是操作系统中的互斥访问,即访问前先加一把锁,结束后再解锁

代码修改如下

package com.ThreadTest;

public class DemoThread implements Runnable{
    private int nike = 10;
    Object lock = new Object();
    @Override
    public void run() {
        while(true) {
            synchronized (lock) {
                if (nike > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "抢到了第" + nike-- + "双鞋");
                }
            }
        }
    }
}

输出结果:

Lowell抢到了第10双鞋
Lowell抢到了第9双鞋
Lowell抢到了第8双鞋
Lowell抢到了第7双鞋
Lowell抢到了第6双鞋
Lowell抢到了第5双鞋
Lowell抢到了第4双鞋
Lowell抢到了第3双鞋
Lowell抢到了第2双鞋
Lowell抢到了第1双鞋

也可以将synchronized创建成一个同步方法,将同步代码块抽离出来,再调用

public synchronized void shoeCatch() {
        if (nike > 0) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "抢到了第" + nike-- + "双鞋");
        }
    }

ReentrantLock同步锁

package com.ThreadTest;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DemoThread implements Runnable {
    private int nike = 10;
    Lock reetrantLock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            reetrantLock.lock();
            try {
                if (nike > 0) {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + "抢到了第" + nike-- + "双鞋");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reetrantLock.unlock();
            }
        }
    }
}

synchronized和Lock的区别

  • synchronizedJava的关键字,而Lock是Java的接口。
  • synchronized是非公平锁,也就是说,等待的线程不一定按照先来后到的顺序获得锁,而Lock可以选择是公平锁还是非公平锁。
  • synchronized是不可中断锁,当一个线程持有锁时,其他线程只能等待,不能响应中断,而Lock是可中断锁,当一个线程持有锁时,其他线程可以选择放弃等待或者响应中断。
  • synchronized是隐式锁,不需要手动加锁和解锁,而Lock是显式锁,需要手动调用lock()和unlock()方法来加锁和解锁。
  • synchronized不支持多个条件变量(Condition),而Lock支持多个条件变量(Condition),这样可以实现更细粒度的线程控制
  • synchronized在发生异常时候会自动释放占有的锁,而Lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。
  • synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度。
  • 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized

守护进程

  • 守护进程(Daemon)是一种在系统后台执行的程序,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件
  • 守护进程也称为服务线程,它的作用是为其他线程(用户线程)提供服务
  • 在Java中,可以通过setDaemon(true)方法将一个线程设置为守护线程,但必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常
  • 在守护线程中产生的新线程也是守护线程
  • 当所有用户线程都执行完毕后,守护线程会随着JVM一同退出,不管守护线程是否执行完毕
// 定义一个普通线程类
class NormalThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("普通线程第" + i + "次执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 定义一个守护线程类
class DaemonThread extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("守护线程正在运行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class DaemonDemo {
    public static void main(String[] args) {
        // 创建一个普通线程对象
        NormalThread normalThread = new NormalThread();
        // 创建一个守护线程对象
        DaemonThread daemonThread = new DaemonThread();
        // 设置守护线程为true
        daemonThread.setDaemon(true);
        // 启动两个线程
        normalThread.start();
        daemonThread.start();
    }
}

输出结果:

普通线程第0次执行
守护线程正在运行
普通线程第1次执行
守护线程正在运行
普通线程第2次执行
守护线程正在运行
普通线程第3次执行
守护线程正在运行
普通线程第4次执行
守护线程正在运行
普通线程第5次执行
守护线程正在运行 

匿名内部类

// 使用匿名内部类实现Thread类
new Thread() {
    @Override
    public void run() {
        // 线程要执行的任务
        System.out.println("使用匿名内部类实现Thread类");
    }
}.start(); // 启动线程

// 使用匿名内部类实现Runnable接口
new Thread(new Runnable() {
    @Override
    public void run() {
        // 线程要执行的任务
        System.out.println("使用匿名内部类实现Runnable接口");
    }
}).start(); // 启动线程


线程优先级

每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。

Java 线程的优先级是一个整数,其取值范围是1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )

默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5),但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

public class MaxPriorityThread implements Runnable {
    @Override
    public void run() {
        for(int i = 0 ; i < 3 ; i++) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}
public class MinPriorityThread implements Runnable {
    @Override
    public void run() {
        for(int i = 0 ; i < 3 ; i++) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}
public class MultiThreadingTest {
    public static void main(String[] args) {
Thread maxThread = new Thread(new MaxPriorityThread() , "Max Thread......");
        Thread minThread = new Thread(new MinPriorityThread(), "Min Thread......");
        //设置进程优先级
        maxThread.setPriority(Thread.MAX_PRIORITY);
        minThread.setPriority(Thread.MIN_PRIORITY);
        
        maxThread.start();
        minThread.start();
    }
}

输出结果:

Max Thread......
Max Thread......
Max Thread......
Min Thread......
Min Thread......
Min Thread......

但有时会发现优先级没有调换过来,是操作系统的原因,程序执行太快了没有反应过来,还没调度程序就结束了

Min Thread......
Min Thread......
Min Thread......
Max Thread......
Max Thread......
Max Thread......

join线程插队、yield线程让步

public class MultiThreadingTest {
    public static void main(String[] args) {
        MaxPriorityThread maxPriorityThread_1 = new MaxPriorityThread();
        Thread thread = new Thread(maxPriorityThread_1,"1");
        thread.start();
        for (int i = 0 ; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName());
            if(i == 3) {
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

输出结果:

main
main
main
main
1
1
1
main
main
main
main
main
main
public class LowellThread  extends Thread{
    @Override
    public void run() {
        for (int i = 0 ; i < 10 ; i++) {
            if(i % 3 == 0) {
                System.out.println("------现在" + Thread.currentThread().getName() + "号进程开始让步------");
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName() + "号进程");
        }
    }
}
public class MultiThreadingTest {
    public static void main(String[] args) {
        LowellThread lowellThread = new LowellThread();

        Thread thread_1 = new Thread(lowellThread, "1");
        Thread thread_2 = new Thread(lowellThread, "2");
        Thread thread_3 = new Thread(lowellThread, "3");

        thread_1.start();
        thread_2.start();
        thread_3.start();

输出结果:

------现在3号进程开始让步------
------现在2号进程开始让步------
------现在1号进程开始让步------
2号进程
3号进程
2号进程
1号进程
3号进程
1号进程
2号进程
1号进程
3号进程
------现在1号进程开始让步------
------现在2号进程开始让步------
1号进程
------现在3号进程开始让步------
1号进程
1号进程
2号进程
------现在1号进程开始让步------
3号进程
1号进程
1号进程
1号进程
------现在1号进程开始让步------
1号进程
2号进程
2号进程
------现在2号进程开始让步------
2号进程
3号进程
2号进程
3号进程
2号进程
------现在2号进程开始让步------
2号进程
------现在3号进程开始让步------
3号进程
3号进程
3号进程
------现在3号进程开始让步------
3号进程

线程状态

线程通信

用生产者与消费者的简化问题来举例子:

示例

public class Producer extends Thread{
    private Product product;
    public Producer (Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        while(true) {
            synchronized (product) {
                //判断是否还有产品
                if(product.flag == true) {
                    try {
                        //还有产品,生产者等待
                        product.wait();
                    } catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                product.flag = true;
                System.out.println(Thread.currentThread().getName() + "生产完了,唤醒消费者!");
                product.notify();
            }
        }
    }
}

public class Consumer extends Thread{
    private Product product;
    public Consumer(Product product) {
        this.product = product;
    }
    @Override
    public void run() {
        while(true) {
            synchronized (product) {
                //判断是否还有产品
                if(product.flag == false) {
                    try {
                        //产品买完了,消费者等待
                        product.wait();
                    } catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                product.flag = false;
                System.out.println(Thread.currentThread().getName() + "消费结束,唤醒生产者!");
                product.notify();
            }
        }
    }
}

public class Product {
    public boolean flag = false;
}
public class MultiThreadingTest {
    public static void main(String[] args) {
        Product product = new Product();
        new Producer(product).start();
        new Consumer(product).start();
    }
}

输出结果:

Thread-0生产完了,唤醒消费者!
Thread-1消费结束,唤醒生产者!
Thread-0生产完了,唤醒消费者!
Thread-1消费结束,唤醒生产者!
Thread-0生产完了,唤醒消费者!
Thread-1消费结束,唤醒生产者!
Thread-0生产完了,唤醒消费者!

.notifyAll()方法用于唤醒在该对象上等待的所有线程

参考资料:

[Multithreading-Java API 实战]((27条消息) 7:Multithreading-Java API 实战_Yeats_Liao的博客-CSDN博客)

[Java 多线程编程 | 菜鸟教程](Java 多线程编程 | 菜鸟教程 (runoob.com))

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年12月05日 0

暂无评论

推荐阅读
  2Vtxr3XfwhHq   2024年05月17日   45   0   0 Java
  8s1LUHPryisj   2024年05月17日   42   0   0 Java
  aRSRdgycpgWt   2024年05月17日   44   0   0 Java
y9EYnC7aLifI