一、什么是多线程
在计算机科学中,多线程是指一个进程内部的并发执行,也就是在一个进程中同时执行多个线程。在Java中,线程是Java程序执行的基本单位,允许程序同时运行多个部分。Java多线程编程的主要目的是提高程序的执行效率和资源利用率,提高程序的响应速度,确保程序并发执行,也就是线程安全。
多线程可以单独执行各自的任务,在执行多任务时可以充分利用CPU资源。Java多线程技术可以通过创建Thread类对象、实现Runnable接口或使用线程池等方式实现,并且支持同步(Synchronized)和异步(Asynchronized)操作。
二、Java多线程的基本操作
Java多线程的基本操作包括线程的创建、启动、休眠、中断、等待、唤醒等。下面我们来一个个介绍。
1. 线程的创建和启动
为了创建和启动一个线程,需要以下步骤:
// 继承Thread类 class MyThread extends Thread { @Override public void run() { System.out.println("Thread " + this.getName() + " is running"); } } public class Main { public static void main(String[] args) { // 创建Thread类的实例对象 MyThread thread1 = new MyThread(); MyThread thread2 = new MyThread(); // 启动线程 thread1.start(); thread2.start(); } }
在上面的例子中,我们创建了两个线程MyThread,并启动它们。当调用start()方法时,程序会自动调用run()方法,从而执行线程体中的代码。
2. 线程的休眠和中断
在有些情况下,我们需要线程休眠一段时间,或者中断线程的执行。这时可以使用Thread类的sleep()和interrupt()方法来实现。例如:
class MyThread extends Thread { @Override public void run() { try { // 线程休眠2秒 Thread.sleep(2000); System.out.println("Thread " + this.getName() + " is running"); } catch (InterruptedException e) { System.out.println("Thread " + this.getName() + " is interrupted"); } } } public class Main { public static void main(String[] args) { MyThread thread1 = new MyThread(); MyThread thread2 = new MyThread(); thread1.start(); // 线程休眠1秒 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 中断线程 thread2.interrupt(); } }
在上面的例子中,我们创建了两个线程MyThread,并启动它们。其中一个线程休眠2秒后输出信息,另一个线程等待1秒后中断它的执行。
3. 线程的等待和唤醒
有些线程可能需要等待其他线程的执行完成后再继续执行,此时就可以使用Thread类的wait()和notify()方法。下面是一个简单的例子:
class MyThread extends Thread { private int count = 0; private final Object lock; public MyThread(Object lock) { this.lock = lock; } public void increment() { count++; } public int getCount() { return count; } @Override public void run() { synchronized (lock) { while (count < 5) { try { System.out.println("Thread " + this.getName() + " is waiting"); // 等待唤醒 lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Thread " + this.getName() + " is running"); } } } public class Main { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); MyThread thread1 = new MyThread(lock); MyThread thread2 = new MyThread(lock); thread1.start(); thread2.start(); for (int i = 0; i < 5; i++) { synchronized (lock) { thread1.increment(); thread2.increment(); lock.notifyAll(); } Thread.sleep(1000); } } }
在上面的例子中,我们创建了两个线程MyThread,并启动它们。这两个线程都等待lock对象的唤醒。当每次循环时,我们通过synchronized块和notifyAll()方法来唤醒两个线程的执行。
三、Java多线程的问题
在多线程编程中,可能会出现一些问题,例如死锁、竞争条件、线程安全等等。下面我们来逐个介绍。
1. 死锁
死锁是指两个或多个线程互相等待其它线程完成执行然后无限期地等待,导致程序不能终止。这种情况下,所有线程都被阻塞,导致程序陷入停滞。下面是一个简单的死锁例子:
public class DeadLockDemo { static class Waiter implements Runnable { private Object lock1; private Object lock2; public Waiter(Object lock1, Object lock2) { this.lock1 = lock1; this.lock2 = lock2; } @Override public void run() { synchronized (lock1) { System.out.println(Thread.currentThread().getName() + " acquired lock1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println(Thread.currentThread().getName() + " acquired lock2"); } } } } static class Notifier implements Runnable { private Object lock1; private Object lock2; public Notifier(Object lock1, Object lock2) { this.lock1 = lock1; this.lock2 = lock2; } @Override public void run() { synchronized (lock2) { System.out.println(Thread.currentThread().getName() + " acquired lock2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1) { System.out.println(Thread.currentThread().getName() + " acquired lock1"); } } } } public static void main(String[] args) { Object lock1 = new Object(); Object lock2 = new Object(); new Thread(new Waiter(lock1, lock2), "Thread1").start(); new Thread(new Notifier(lock1, lock2), "Thread2").start(); } }
在上面的例子中,我们创建了两个线程Waiter和Notifier,并启动它们。这两个线程互相等待对方释放其锁对象,产生死锁。
2. 竞争条件
在多线程程序中,可能会出现竞争条件,即多个线程同时访问共享资源。竞争条件可能导致数据一致性问题,例如线程间数据覆盖、数据错乱等。下面是一段简单的竞争条件示例代码:
public class RaceConditionDemo { static class IncrementThread extends Thread { private int count = 0; @Override public void run() { while (count < 5) { System.out.println(Thread.currentThread().getName() + ": " + count++); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { IncrementThread thread1 = new IncrementThread(); IncrementThread thread2 = new IncrementThread(); thread1.start(); thread2.start(); } }
在上面的例子中,我们创建了两个线程,每个线程自增一个计数器。由于线程的执行顺序不确定,所以每次程序输出的结果可能不同,产生数据一致性问题。
3. 线程安全
线程安全是指在多线程环境下,共享资源能够正确地被多个线程并发访问。Java中有多种方式来实现线程安全,例如Synchronized关键字、Lock接口、原子变量等。下面是一个使用Synchronized关键字实现线程安全的示例:
public class ThreadSafeDemo { private int count = 0; public synchronized void increment() { count++; } public int getCount() { return count; } static class IncrementThread extends Thread { private ThreadSafeDemo demo; public IncrementThread(ThreadSafeDemo demo) { this.demo = demo; } @Override public void run() { for (int i = 0; i < 10000; i++) { demo.increment(); } } } public static void main(String[] args) throws InterruptedException { ThreadSafeDemo demo = new ThreadSafeDemo(); IncrementThread thread1 = new IncrementThread(demo); IncrementThread thread2 = new IncrementThread(demo); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("Count: " + demo.getCount()); } }
在上面的例子中,我们创建了两个线程IncrementThread,并启动它们,每个线程增加一个计数器。由于increment()方法加上了Synchronized关键字,所以线程安全。