创建线程的其他方式
Runnable接口
Runnable接口是Java中用于定义线程任务的接口,实现该接口需要实现run()方法,在run()方法中定义要执行的任务。创建线程时,可以将实现了Runnable接口的对象作为参数传递给Thread类的构造函数,然后调用start()方法启动线程。
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程要执行的任务
System.out.println("Runnable Thread is running.");
}
}
// 创建线程并启动
Thread thread = new Thread(new MyRunnable());
thread.start();
Callable接口
Callable接口也是用于定义线程任务的接口,与Runnable接口不同的是,Callable接口的任务可以返回结果,并且可以抛出异常。实现Callable接口的类需要实现call()方法,该方法中定义了线程要执行的任务,并返回一个结果。创建线程时,可以将实现了Callable接口的对象作为参数传递给Thread类的构造函数,然后调用start()方法启动线程。调用线程的get()方法可以获取线程执行完任务后的返回结果。
注意:Callable接口使用了泛型
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
// 线程要执行的任务,并返回结果
return "Callable Thread is running.";
}
}
public class CallableTest {
public static void main(String[] args) {
// 创建线程并启动
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
// 获取线程执行的结果
String result = null;
try {
result = futureTask.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(result);
}
}
FutureTask接口
FutureTask类实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable和Future接口。FutureTask类可以将Callable对象转换为Future和Runnable对象,可以用作线程执行的任务。创建线程时,可以将实现了FutureTask接口的对象作为参数传递给Thread类的构造函数,然后调用start()方法启动线程。
调用FutureTask的get()方法可以获取线程执行完任务后的返回结果。
线程池
ThreadPoolExecutor类
- ThreadPoolExecutor类是Java中用于管理和控制线程池的类。
- 线程池是一种重用线程的机制,可以避免频繁创建和销毁线程的开销。
- ThreadPoolExecutor类提供了创建和管理线程池的方法,可以设置线程池的大小、任务队列、拒绝策略等。
- 可以通过调用ThreadPoolExecutor的execute()方法提交任务给线程池执行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务给线程池执行
for (int i = 0; i < 10; i++) {
final int taskNum = i;
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("Task " + taskNum + " is running.");
}
});
}
// 关闭线程池
executor.shutdown();
}
}
线程安全
当多个线程共享一个对象中的数据或者类信息的时候容易引发读写错误的问题。
同步代码块
- 同步代码块是一种线程同步的机制,用于控制多个线程对共享资源的访问。
- 同步代码块使用synchronized关键字来修饰,指定一个对象作为锁。
- 在同步代码块中,只有获取到锁的线程才能执行代码块中的内容,其他线程需要等待锁的释放。
- 同步代码块可以避免多个线程同时访问共享资源而导致的数据不一致或错误。
class SynchronizedExample {
private int count = 0;
private Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
return count;
}
}
public class SynchronizedTest {
public static void main(String[] args) {
// 创建多个线程对共享资源进行操作
SynchronizedExample example = new SynchronizedExample();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
example.increment();
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
example.increment();
}
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Count: " + example.getCount());
}
}
同步方法
- 同步方法是一种简化线程同步的机制,与同步代码块类似,它也使用synchronized关键字来修饰方法。
- 在同步方法中,整个方法体都被视为同步代码块,使用方法所属对象作为锁。
- 同步方法的锁是隐式的,即无需显式地编写锁对象,方法所属对象即为锁。
- 同步方法可以确保同一时间只有一个线程执行该方法,从而避免并发访问共享资源的问题。
public class SynchronizedTest2 {
public static void main(String[] args) {
SynchronizedExample2 se = new SynchronizedExample2();
Thread t1 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 1000; i++) {
se.increment();
}
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 1000; i++) {
se.increment();
}
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("count = " + se.getCount());
}
}
class SynchronizedExample2 {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
同步锁和互斥锁
同步锁(Synchronization Lock)和互斥锁(Mutex Lock)是用于实现线程同步的两种机制,用于控制多个线程对共享资源的访问。
同步锁
同步锁是一种机制,用于保护共享资源在同一时间只能被一个线程访问。在Java中,synchronized关键字用于实现同步锁。
- 当一个线程获取到同步锁时,其他线程将被阻塞,直到锁被释放。
- 同步锁可以用于同步代码块或同步方法中,通过锁定对象或方法来实现线程同步。
- 同步锁的机制可以确保共享资源的访问不会出现数据不一致或错误的情况。
以下是一个使用同步锁的示例:
public class SynchronizedExample {
private int count = 0;
private Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
return count;
}
}
在上面的示例中,通过使用同步锁(lock对象),在increment()
方法中对count
进行自增操作。只有一个线程可以获取到锁并执行自增操作,其他线程需要等待锁的释放。
互斥锁
互斥锁是一种特殊的同步锁,用于确保在同一时间只有一个线程能够访问共享资源。互斥锁保证了临界区(Critical Section)的互斥访问,避免了多个线程同时执行临界区代码的情况。
互斥锁通常有两种状态:锁定(Locked)和未锁定(Unlocked)。
- 当一个线程获取到互斥锁时,将锁定临界区,其他线程将被阻塞。
- 当线程执行完临界区代码后,释放互斥锁,其他线程可以获取锁并进入临界区执行代码。
互斥锁的实现可以使用底层的硬件指令或操作系统提供的原子操作。
以下是一个使用互斥锁的示例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MutexLockExample {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
在上面的示例中,使用了ReentrantLock
类来实现互斥锁。在increment()
方法中,首先调用lock()
方法获取锁,然后执行自增操作,最后通过unlock()
方法释放锁。
小练习:抢票窗口(3个窗口抢100张票)
/*
自定义一个线程类,实现窗口抢票功能。总票数为100,共有3个抢票窗口,当票数为0时,停止抢票。
*/
class TicketWindow implements Runnable {
private int totalTickets = 100;
private static Object lock = new Object();
@Override
public void run() {
while (true) {
synchronized (lock) {
if (totalTickets > 0) {
System.out.println(Thread.currentThread().getName() + ":抢到第 " + (101-totalTickets) + " 张票");
totalTickets--;
} else {
System.out.println(Thread.currentThread().getName() + ":票已售罄");
break;
}
}
try {
Thread.sleep(100); // 模拟抢票过程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow();
new Thread(ticketWindow, "窗口1").start();
new Thread(ticketWindow, "窗口2").start();
new Thread(ticketWindow, "窗口3").start();
}
}
在上面的示例中,创建了一个TicketWindow类来模拟售票窗口,使用同步代码块来确保每个窗口在售票时的线程安全。然后创建了三个窗口线程,它们不断地从TicketCounter对象中售出票,直到票卖完为止。