Java 多线程是指在一个程序中同时执行多个线程。线程是操作系统能够进行运算调度的最小单位,它是进程中的一个执行路径。Java 提供了内建的多线程支持,使得程序可以并行处理任务,从而提高效率。以下是一些关键点:
-
线程创建:Java 中可以通过继承
Thread
类或实现Runnable
接口来创建线程。继承Thread
类时,需要重写run()
方法;实现Runnable
接口时,需要实现run()
方法,并将其作为参数传递给Thread
类。
继承Thread
类:
class MyThread extends Thread { public void run() { System.out.println("Thread is running"); } } MyThread thread = new MyThread(); thread.start(); // 启动线程
实现
Runnable
接口:
class MyRunnable implements Runnable { public void run() { System.out.println("Runnable is running"); } } Thread thread = new Thread(new MyRunnable()); thread.start(); // 启动线程
-
线程管理:可以使用
Thread
类中的方法来管理线程,如start()
启动线程,sleep()
使线程暂停,join()
等待线程完成,interrupt()
中断线程等。 -
线程同步:为了避免多个线程同时访问共享资源时出现数据不一致的问题,Java 提供了同步机制。使用
synchronized
关键字可以在方法或代码块上实现同步,确保同一时刻只有一个线程可以执行被同步的代码。
使用synchronized
:
class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } }
使用
ReentrantLock
:
import java.util.concurrent.locks.ReentrantLock; class Counter { private int count = 0; private final ReentrantLock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } public int getCount() { return count; } }
-
线程池:Java 提供了
java.util.concurrent
包中的线程池类,如ExecutorService
,可以更有效地管理和复用线程,避免频繁创建和销毁线程。 -
线程安全:除了使用同步机制,Java 还提供了线程安全的集合类(如
ConcurrentHashMap
)和原子类(如AtomicInteger
),可以在多线程环境中安全地操作共享数据。
还有一个可能在开发过程中遇到的小问题,有关于锁的问题
观察下面两份代码
package com.cn1;
class TicketsThread3 implements Runnable {
private static int tickets = 10; // 共享的票数
private static final Object lock = new Object(); // 锁对象
@Override
public void run() {
while (true) {
synchronized (lock) { // 确保对票务的访问是线程安全的
if (tickets > 0) {
Thread thread = Thread.currentThread();
String name = thread.getName();
System.out.println(name + " 正在发售第 " + tickets-- + " 张票");
} else {
break; // 票卖完后退出循环
}
}
// 可以选择在每次发售票后让线程休息一小会,以便观察输出
try {
Thread.sleep(100); // 休眠100毫秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重置中断状态
}
}
}
}
public class Demo6 {
public static void main(String[] args) {
TicketsThread3 ticketsThread3 = new TicketsThread3();
Thread window1 = new Thread(ticketsThread3, "窗口1");
Thread window2 = new Thread(ticketsThread3, "窗口2");
Thread window3 = new Thread(ticketsThread3, "窗口3");
Thread window4 = new Thread(ticketsThread3, "窗口4");
window1.start();
window2.start();
window3.start();
window4.start();
}
}
运行结果:
第二份代码:
package com.cn2;
class TicketWin implements Runnable {
private static int ticket = 10;
private static final Object lock = new Object(); // 锁对象
@Override
public void run() {
while (true) {
synchronized (lock) {//锁机制,synchronized(参数)表示同步,参数表示同步锁
if (ticket > 0) {
Thread thread = Thread.currentThread();
String name = thread.getName();
System.out.println(name + " 卖出的票 " + ticket--);
} else {
break;
}
// 可以选择在每次发售票后让线程休息一小会,以便观察输出
try {
Thread.sleep(100); // 休眠100毫秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重置中断状态
}
}
}
}
}
public class Demo5 {
public static void main(String[] args) {
TicketWin t1 = new TicketWin();
Thread thread1= new Thread(t1,"窗口1");
Thread thread2= new Thread(t1,"窗口2");
Thread thread3= new Thread(t1,"窗口3");
Thread thread4= new Thread(t1,"窗口4");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
运行结果
通过观察我们可以看到第一份代码的运行结果比较正常,几乎每个线程都能执行,而第二份代码多次运行结果都是几乎只有一个线程或两个线程在执行,这两份代码唯一的区别点在于睡眠sleep()在锁synchronized的外面和内部
当睡眠在锁内部时:
- 效果:线程在持有锁时进行睡眠,这意味着其他线程无法访问被同步的方法或代码块,直到当前线程完成睡眠并释放锁。这样会导致性能下降,因为锁在睡眠期间被占用,其他线程只能等待锁释放才能执行。
- 问题:可能导致线程阻塞时间过长,影响程序的响应性和吞吐量。
当睡眠在锁外部时:
- 效果:线程在执行完锁相关的操作后再进行睡眠,这样不会持有锁期间睡眠,允许其他线程在睡眠期间访问锁保护的资源。能提高系统的并发性和响应性。
- 好处:避免了线程在持有锁时的不必要阻塞,提高了系统的整体性能。
所以我们写代码时要注意大括号位置,不要把睡眠写到锁内。
t通过合理地设计和管理线程,可以显著提高程序的性能和响应速度,但也需要注意线程安全和资源竞争问题。