一、多线程基础
多线程是指一个程序中有两个或多个线程同时运行,每个线程执行不同的任务。Java中的多线程技术是通过Thread类和Runnable接口来实现的。Thread类代表了一个线程,而Runnable接口是一个任务的执行状态。以下是Java实现多线程的示例:
class MyThread extends Thread { public void run() { System.out.println("Thread is running."); } } public class Main { public static void main(String[] args) { MyThread t = new MyThread(); t.start(); } }
在上面的代码中,我们创建了一个MyThread类,该类继承自Thread类。然后我们覆盖了run()方法并在其中打印一条消息。在main()方法中,我们创建了一个MyThread类并开始运行它。
二、线程同步
在Java中,线程同步确保多个线程访问共享资源是有序的。线程同步可以通过synchronized关键字来实现。以下是线程同步的基本示例代码:
class SharedResource { int value; synchronized void setValue(int value){ this.value = value; } synchronized int getValue(){ return value; } } class MyThread1 extends Thread { SharedResource s; MyThread1(SharedResource s){ this.s = s; } public void run(){ s.setValue(10); } } class MyThread2 extends Thread { SharedResource s; MyThread2(SharedResource s){ this.s = s; } public void run(){ int value = s.getValue(); System.out.println(value); } } public class Main { public static void main(String[] args) { SharedResource s = new SharedResource(); MyThread1 t1 = new MyThread1(s); MyThread2 t2 = new MyThread2(s); t1.start(); t2.start(); } }
在上述代码中,我们定义了一个名为SharedResource的类,该类包含两个方法,setValue()和getValue()。在setValue()方法中,我们使用synchronized关键字来确保线程同步,以避免线程之间发生错误。在MyThread1类和MyThread2类中,我们分别使用了SharedResource类中的setValue()方法和getValue()方法。
三、死锁
当两个或多个线程试图锁定资源时,它们可能会定位到不同的资源,进而导致死锁。死锁是非常严重的问题,因为它会导致应用程序停止响应。以下是死锁的示例代码:
class Resource1 { synchronized void execute(Resource2 res2){ System.out.println("Resource1 - executing."); res2.execute(); } synchronized void execute(){ System.out.println("Resource1 - executing."); } } class Resource2 { synchronized void execute(Resource1 res1){ System.out.println("Resource2 - executing."); res1.execute(); } synchronized void execute(){ System.out.println("Resource2 - executing."); } } public class Main { public static void main(String[] args) { Resource1 res1 = new Resource1(); Resource2 res2 = new Resource2(); Thread t1 = new Thread(new Runnable() { @Override public void run() { res1.execute(res2); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { res2.execute(res1); } }); t1.start(); t2.start(); } }
在上面的代码中,我们定义了两个资源,Resource1和Resource2。在每个资源中,我们实现了两个不同的execute()方法。我们创建了两个线程,并让它们同时运行execute()方法。当线程t1运行Resource1中的execute()方法时,它会尝试获取Resource2的锁。但是,Resource2的execute()方法已经在执行中,使得t1无法获取Resource2的锁,从而发生了死锁。同样的情况也发生在t2中。
四、线程池
线程池是一种可以重用线程执行多个任务的机制,它可以在需要时调用线程,而不是一直创建新线程。使用线程池可以提高应用程序的性能、可伸缩性和吞吐量。以下是使用Java线程池的示例代码:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class MyTask implements Runnable{ private int taskId; public MyTask(int taskId) { this.taskId = taskId; } public void run() { System.out.println("Task ID : " + this.taskId + " performed by " + Thread.currentThread().getName()); } } public class Main{ public static void main(String[] args) { int numOfTasks = 10; ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 1; i <= numOfTasks; i++) { executor.submit(new MyTask(i)); } executor.shutdown(); } }
在上面的代码中,我们创建了一个名为MyTask的类,该类实现了Runnable接口。在main()方法中,我们创建了一个执行任务的线程池,并将MyTask分配给它。在for循环中,我们创建了10个任务,并将它们添加到线程池的任务队列中。当我们运行代码时,我们可以看到5个线程在执行任务,并且很快便完成了所有任务,因为它们不断地重用了线程。
五、死锁的避免
为了避免死锁,我们需要写出良好的代码,确保它们正确地并发工作。以下是一些减轻死锁的方法:
1. 获取锁的顺序:线程应该按照固定的顺序获取锁。这样可以避免出现循环依赖,最终导致死锁。
2. 限制锁持有时间:线程应该尽快释放它们所持有的锁,以避免其他线程需要等待很长时间才能获取它们。这样可以减少持有锁的风险,从而减少死锁的可能性。
3. 使用tryLock()方法:除了synchronized关键字外,Java还提供了一个tryLock()方法,该方法尝试获取锁,但不会阻塞线程。如果锁可用,则线程会立即获取锁,并继续执行任务;否则线程将继续执行其他任务。
4. 使用锁的等待和通知机制:Java中的wait()和notify()方法就是为了避免死锁而设计的。wait()方法告诉线程等待并释放锁,而notify()方法通知其他线程可以竞争锁。
六、总结
在Java中使用并发编程技术可以提高应用程序的性能和效率,但也会带来许多问题,如死锁、并发访问和线程同步。理解这些问题并使用正确的技术来解决它们是极为重要的。本文介绍了Java多线程的基础知识、线程同步、死锁、线程池和死锁的避免方法。