• Java回顾-多线程


    一、线程创建

    方式一、继承Thread类

            1.定义新类继承Thread类

            2.重写run()方法

            3.创建新类对象,调用 .start()方法

    1. //方式1.1 继承Thread类
    2. class thread1 extends Thread{
    3. @Override
    4. public void run() {
    5. System.out.println("线程1的操作...");
    6. }
    7. }
    8. @Test
    9. public void test1(){
    10. thread1 t1=new thread1();
    11. t1.start();
    12. }
    13. //方式1.2 继承Thread类,但用匿名内部类实现
    14. @Test
    15. public void test2(){
    16. //1.2 采用匿名子类的方式
    17. new Thread(){
    18. @Override
    19. public void run() {
    20. System.out.println("线程1的操作...");
    21. }
    22. }.start();
    23. }

    方式二、实现Runable接口

            1.定义新类实现Runable接口

            2.重写run()方法

            3.创建Thread类的实例构造器参数为新类

            4.Thread类的实例调用 .start()方法

            源码解读:Thread类的构造器有个Runnable类型参数target,调用Threadrun()时实际上执行的是targetrun()target就是我们自定义的类里面的run()

    1. class thread2 implements Runnable{
    2. @Override
    3. public void run() {
    4. System.out.println("线程2的操作...");
    5. }
    6. }
    7. @Test
    8. public void test3(){
    9. Thread t2=new Thread(new thread2());
    10. t2.start();
    11. }

    两种方式的比较:

        -开发中一般用第二种方式

        原因:1.实现的方式没有类的单继承性的局限性

                  2.实现的方式更适合来处理多个线程有共享数据的情况

        联系:Thread 类也继承于 Runnable( )接口

        相同点:两种方式都需要重写run(),将线程要执行的操作声明在run()中

    方式三、实现 Callable 接口

              与Runnable相比,Callable功能更强大:

                    -相比run()可以有返回值

                    -方法可以抛出异常

                    -支持泛型的返回值

                    -需要借助FutureTask类,比如获取返回结果

      Future接口

                    -可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。

                    -FutureTask是Future接口的唯一实现类,同时实现了Runnable、Future接口

    实现步骤:

            1.创建 Callable实现类

            2.实现 call方法,将此线程需要执行的操作声明在call()

            3.创建Callable 接口实现类的对象

            4.将Callable 接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象

            5.将FutureTask的对象作为参数传递到Thread类构造器中,创建Thread对象,并调用start()

            6.(可选)获取Callable中call方法的返回值  - .get

    1. class Thread3 implements Callable{
    2. @Override
    3. public Object call() throws Exception {
    4. System.out.println("线程3的操作");
    5. return 521;//可以有返回值
    6. }
    7. }
    8. public static void main(String[] args) throws ExecutionException, InterruptedException {
    9. Thread3 thread3=new Thread3();//新建线程的对象
    10. FutureTask futureTask=new FutureTask(thread3);//FutureTask 对象
    11. new Thread(futureTask).start();// Thread 对象.start()
    12. futureTask.get();// 获取该线程的返回值
    13. }

    方式四、使用线程池

            思路:提前创建好多个线程,放入线程池中,使用时直接获取,是用完放回池中

            好处:提高响应速度、降低资源消耗、便于线程管理

            相关API:

            ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

                    -void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行                 Runnable

                    -Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable

                    -void shutdown() :关闭连接池

             Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池                                  Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池                              Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池                                  Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池                                  Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

    1. class Thread4 implements Runnable{
    2. @Override
    3. public void run() {
    4. System.out.println("线程4");
    5. }
    6. }
    7. class Thread5 implements Callable{
    8. @Override
    9. public Object call() throws Exception {
    10. System.out.println("线程5");
    11. return 521;
    12. }
    13. }
    14. public static void main(String[] args) throws ExecutionException, InterruptedException {
    15. ExecutorService service= Executors.newFixedThreadPool(10);
    16. service.execute(new Thread4());//执行实现Runnable接口的线程
    17. service.submit(new Thread5());//执行实现Callable接口的线程
    18. service.shutdown();//关闭所有线程
    19. }
    20. }
    21. 线程4
    22. 线程5

     二、线程类的相关方法

        1. start() :启动当前线程:调用当前线程的run()

        2. run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中

        3. currentThread():静态方法,返回执行当前代码的线程

        4. getName():获取当前线程的名字

        5. setName():设置当前线程的名字     可以在重写的Thread类的构造器中重命名

        6. yield():释放当前cpu的执行权 

        7. join():在线程a中调用线程b的join(),此时线程a进入阻塞状态,直到线程b完全执行完,a才结束阻塞状态

        8. sleep():让当前线程睡眠指定毫秒的时间,使得当前线程进入阻塞状态,在Thread中声明

    ,不会释放锁(同步监视器)

    三、线程的优先级

                MAX_PRIORITY:10

                NORM_PRIORITY:5  (默认值)

                MIN_PRIORITY:1

          高优先级的线程会抢占低优先级线程的cpu执行权,但这只是从概念上讲的,不是高优先级执行完才执行低优先级。

           涉及的方法

            getPriority() :返回线程优先值

            setPriority(int newPriority) :改变线程的优先级

    四、线程的生命周期

     

        -每个线程用有独立的运行程序计数器pc

        -一个进程中的多个线程共享相同的内存单元,他们从同一个中分配对象,可以访问相同的变量和对象

        -java应用程序至少有三个线程:main() gc() 垃圾回收线程、异常处理线程

    五、线程的同步

    1.线程安全问题

             下面是火车售票的例子,3个线程再判断是否有余票的时候都阻塞了,则恢复执行后数据就出错了:

    解决方法:一般通过同步机制来解决线程的安全问题

            -方式一、同步代码块                        

    1. Synchronized(this/newthread.class){
    2.           //需要被同步的代码块
    3.         }

            说明:1.操作共享数据(多个线程共同操作的变量)的代码即为需要被同步的代码

                      2.同步监视器:俗称,可以是任一个类的对象

            要求:多个线程必须共用一把锁

                      3.在实现Runnable接口创建多线程的方式中,可以使用 this 充当同步监视器,但继承Thread方式时不要用 this 作为锁,因为 this 会分别指代多个线程的对象,可以用 类.class 代替 this

            

       -方式二、同步方法

                        -把需要同步的代码块拿出来单独作为一个方法,声明时加上synchronized.                

    1. private synchronized void test(){
    2.           // 需要被同步的代码块
    3.         }

          总结

    1、同步方式的不足之处:操作同步代码时只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低

    2、同步方法仍然涉及到同步监视器,只是不需要显式的声明

    3、非静态的同步方法,同步监视器是this

         静态的同步方法,同步监视器是当前class本身

    2.线程的死锁问题 

            死锁 :不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

            出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续

            解决方法 :

                    专门的算法、原则

                    尽量减少同步资源的定义

                    尽量避免嵌套同步


         所以引入了方式三:

            -方式三、Lock锁 (-jdk5.0+)

    private final ReentrantLock lock = new ReenTrantLock();
    
    1. try{
    2. lock.lock();
    3. //有隐患的代码块
    4. }finally{
    5. lock.unlock();
    6. }

            Lock与Synchronized 区别

            1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是 隐式锁,出了作用域自动释放

            2. Lock只有代码块锁,synchronized有代码块锁方法锁

            3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

    六、线程的通信

    1.常用方法

            wait()一旦执行,当前线程就进入阻塞状态,并释放同步监视器

            notify()一旦执行,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的

            notifyAll()一旦执行,就会唤醒被wait的所有线程

            说明:

                    1.wait()、notify()、notifyAll()三个方法必须使用在同步代码块同步方法中(synchronized)

                    2.三者的调用者必须是同步代码块同步方法中的同步监视器,否则出现异常

                    3.三者定义在object类

            wait()与 sleep()的对比

                    1.sleep()再Thread类中声明,wait()在object类中声明。

                    2.sleep()可在任何场景下调用,wait()只能在同步代码块同步方法中用

                    3.sleep()不释放同步锁,wait()会释放。

    2.举例-生产者消费者模式

            生产者负责生产产品,消费者负责消费产品,店员负责转交产品。店员取货和送货的速度都是随机的,全程不间断进行。

    1. public class test0 {
    2. public static void main(String[] args) {
    3. Clerk clerk=new Clerk();
    4. Thread p1=new Thread(new Producer(clerk));
    5. Thread c1=new Thread(new Customer(clerk));
    6. p1.setName("1.生产者");
    7. c1.setName("2.消费者");
    8. p1.start();
    9. c1.start();
    10. }
    11. }
    12. class Clerk{
    13. private int productCount=0;
    14. Random random=new Random();
    15. public synchronized void produceProduct() throws InterruptedException {
    16. if(productCount<20){
    17. productCount++;
    18. System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
    19. Thread.sleep(random.nextInt(250));
    20. notify();
    21. }else {
    22. wait();
    23. }
    24. }
    25. public synchronized void consumeProduct() throws InterruptedException {
    26. if(productCount>0){
    27. System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
    28. productCount--;
    29. Thread.sleep(random.nextInt(250));
    30. notify();
    31. }else {
    32. wait();
    33. }
    34. }
    35. }
    36. class Producer implements Runnable{
    37. private Clerk clerk;
    38. public Producer(Clerk clerk) {
    39. this.clerk = clerk;
    40. }
    41. @Override
    42. public void run() {
    43. System.out.println("开始生产产品...");
    44. while (true){
    45. try {
    46. clerk.produceProduct();
    47. } catch (InterruptedException e) {
    48. e.printStackTrace();
    49. }
    50. }
    51. }
    52. }
    53. class Customer implements Runnable{
    54. private Clerk clerk;
    55. public Customer(Clerk clerk) {
    56. this.clerk = clerk;
    57. }
    58. @Override
    59. public void run() {
    60. System.out.println("开始消费产品...");
    61. while (true){
    62. try {
    63. clerk.consumeProduct();
    64. } catch (InterruptedException e) {
    65. e.printStackTrace();
    66. }
    67. }
    68. }
    69. }

  • 相关阅读:
    Docker的奇幻漂流
    VR航天航空巡展VR科技馆航天主题科普设备沉浸遨游太空
    31. 下一个排列
    ASP.NET Core依赖注入系统学习教程:关于服务注册使用到的方法
    2.0SpringMVC中报文信息转换器HttpMessageConverter
    精度不够,滑动时间来凑「限流算法第二把法器:滑动时间窗口算法」
    MyBatis resultMap元素
    qq视频录制教程,让你的视频更加精彩
    amber14自由能计算及增强采样方法
    Java基于微信小程序的电影交流平台
  • 原文地址:https://blog.csdn.net/weixin_62427168/article/details/125443962