基础到进阶Java多线程实战适合初学者

36 阅读6分钟

Java多线程实战难不难?对于初学者来说,这似乎是一座难以攀登的高峰。但其实,从基础到进阶的Java多线程实战,就像一场充满惊喜与挑战的冒险之旅,只要掌握了正确的方法,就能轻松开启。接下来,就带大家一起走进这场精彩的Java多线程实战之旅。 Java多线程基础认知:开启冒险的大门 要进行Java多线程实战,首先得了解什么是多线程。想象一下,你在餐厅当服务员,一个服务员同时要负责给多桌客人上菜、点单、收拾餐具。如果这个服务员一次只能做一件事,效率就会很低,客人可能等很久才能吃上饭。但如果这个服务员可以同时处理多桌客人的需求,比如在给这桌客人上菜的同时,让另一个同事帮忙去给那桌客人点单,这就是多线程的概念。 在Java里,线程就是程序中的执行路径。一个Java程序默认有一个主线程,就像餐厅里默认有一个主要的服务员。而多线程就是让程序可以同时执行多个任务,提高程序的运行效率。 创建线程有两种常见的方式。第一种是继承Thread类。这就好比你要开一家新店,你自己创建了一个全新的服务员团队,这个团队专门为你的新店服务。代码示例如下:

public class MyThread extends Thread { @Override public void run() { System.out.println("这是继承Thread类创建的线程在执行任务"); }

public static void main(String[] args) {
    MyThread myThread = new MyThread();
    myThread.start();
}

}

第二种是实现Runnable接口。这就像是你在已有的餐厅服务员团队里,额外安排了一些人去做一些特殊的任务。代码示例如下:

public class MyRunnable implements Runnable { @Override public void run() { System.out.println("这是实现Runnable接口创建的线程在执行任务"); }

public static void main(String[] args) {
    MyRunnable myRunnable = new MyRunnable();
    Thread thread = new Thread(myRunnable);
    thread.start();
}

}

线程同步:保障冒险的秩序 在多线程环境中,会出现一个问题,就是多个线程可能会同时访问和修改同一个资源。这就好比餐厅里多桌客人同时想要同一瓶红酒,如果不加以控制,就会出现混乱。 为了解决这个问题,就需要用到线程同步。Java提供了www.ysdslt.com/synchronized关键字来实现线程同步。它就像是餐厅里的红酒管理员,只有管理员允许,客人才能拿到红酒。 例如,有一个银行账户类,多个线程可能会同时对这个账户进行取款操作。如果不进行线程同步,就可能出现账户余额为负数的情况。代码示例如下:

class BankAccount { private int balance;

public BankAccount(int balance) {
    this.balance = balance;
}

public synchronized void withdraw(int amount) {
    if (balance >= amount) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        balance -= amount;
        System.out.println("取款成功,当前余额:" + balance);
    } else {
        System.out.println("余额不足");
    }
}

}

public class SynchronizedExample { public static void main(String[] args) { BankAccount account = new BankAccount(1000);

    Thread thread1 = new Thread(() -> account.withdraw(500));
    Thread thread2 = new Thread(() -> account.withdraw(600));

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

}

在这个例子中,withdraw方法使用了synchronized关键字,这样在同一时间只有一个线程可以执行这个方法,避免了多个线程同时修改账户余额导致的问题。 线程通信:协作冒险的关键 在多线程程序中,线程之间有时候需要进行通信。这就好比餐厅里的服务员之间需要相互沟通,才能更好地为客人服务。比如一个服务员负责做菜,另一个服务员负责上菜,做菜的服务员做好菜后要通知上菜的服务员来上菜。 Java提供了wait()、notify()和notifyAll()方法来实现线程通信。wait()方法就像是做菜的服务员做好菜后暂时休息,等待上菜的服务员来拿走菜;notify()方法就像是上菜的服务员拿走菜后通知做菜的服务员可以继续做菜了;notifyAll()方法则是通知所有等待的线程。 下面是一个简单的生产者 - 消费者模型的代码示例:

class SharedResource { private int data; private boolean available = false;

public synchronized void produce(int data) {
    while (available) {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    this.data = data;
    available = true;
    System.out.println("生产了数据:" + data);
    notifyAll();
}

public synchronized int consume() {
    while (!available) {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    available = false;
    System.out.println("消费了数据:" + data);
    notifyAll();
    return data;
}

}

public class ThreadCommunicationExample { public static void main(String[] args) { SharedResource resource = new SharedResource();

    Thread producer = new Thread(() -> {
        for (int i = 1; i <= 5; i++) {
            resource.produce(i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    Thread consumer = new Thread(() -> {
        for (int i = 1; i <= 5; i++) {
            resource.consume();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    producer.start();
    consumer.start();
}

}

在这个例子中,生产者线程调用produce方法生产数据,当数据已经生产出来还未被消费时,生产者线程会调用wait方法进入等待状态;消费者线程调用consume方法消费数据,当没有数据可消费时,消费者线程会调用wait方法进入等待状态。当数据状态改变时,线程会调用notifyAll方法通知其他等待的线程。 线程池:高效冒险的利器 创建和销毁线程是有一定开销的。这就好比餐厅里每次有客人来都临时招聘服务员,客人走了又辞退服务员,这样会增加成本。为了避免这种开销,就可以使用线程池。线程池就像是餐厅里固定的服务员团队,客人来了就直接安排服务员服务,不需要每次都招聘和辞退。 Java提供了ExecutorService接口和Executors类来创建线程池。常见的线程池类型有FixedThreadPool、CachedThreadPool、ScheduledThreadPool等。 下面是一个使用FixedThreadPool的代码示例:

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;

public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(3);

    for (int i = 0; i < 5; i++) {
        final int taskId = i;
        executorService.submit(() -> {
            System.out.println("任务 " + taskId + " 正在被执行,执行线程:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务 " + taskId + " 执行完毕");
        });
    }

    executorService.shutdown();
}

}

在这个例子中,使用Executors.newFixedThreadPool(3)创建了一个固定大小为3的线程池。然后提交了5个任务,线程池会安排线程来执行这些任务。最后调用shutdown方法关闭线程池。 进阶实战:挑战更高难度的冒险 掌握了以上基础知识后,就可以进行一些进阶的实战了。比如使用Callable和Future接口实现有返回值的线程。这就好比餐厅里的服务员不仅要完成服务任务,还要带回客人的反馈信息。 下面是一个使用Callable和Future的代码示例:

import java.util.concurrent.*;

class CallableTask implements Callable { @Override public Integer call() throws Exception { System.out.println("Callable任务正在执行"); Thread.sleep(2000); return 100; } }

public class CallableFutureExample { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newSingleThreadExecutor();

    CallableTask task = new CallableTask();
    Future future = executorService.submit(task);

    System.out.println("等待任务执行结果...");
    Integer result = future.get();
    System.out.println("任务执行结果:" + result);

    executorService.shutdown();
}

}

在这个例子中,CallableTask类实现了Callable接口,重写了call方法,该方法有返回值。通过ExecutorService的submit方法提交任务,返回一个Future对象,通过Future对象的get方法可以获取任务的执行结果。 从基础到进阶的Java多线程实战,就像一场从新手到高手的冒险之旅。通过对线程的创建、同步、通信、线程池等知识的学习和实践,你会逐渐掌握Java多线程编程的技巧,在编程的世界里开启更多的可能性。