JavaEE初阶——从入门到掌握线程安全
—JAVAEE— ⬅(click)
Java多线程编程初阶:从入门到掌握线程安全
1. 认识线程(Thread)
线程是什么
线程是程序中的执行流,多个线程可以并发执行多个任务。例如,一家公司办理银行业务,多个员工分别处理转账、发福利、缴社保,这就是多线程的典型场景。
进程和线程的区别
- 进程是包含线程的.每个进程⾄少有⼀个线程存在,即主线程。
- 进程和进程之间不共享内存空间.同⼀个进程的线程之间共享同⼀个内存空间.
- 进程是系统分配资源的最⼩单位,线程是系统调度的最小单位。
- ⼀个进程挂了⼀般不会影响到其他进程.但是⼀个线程挂了,可能把同进程内的其他线程⼀起带走(整个进程崩溃).
创建线程的几种方式
方式 | 示例代码 |
---|---|
继承Thread类 | class MyThread extends Thread { public void run() { ... } } |
实现Runnable接口 | class MyRunnable implements Runnable { public void run() { ... } } |
匿名内部类 | new Thread(() -> { ... }).start(); |
Lambda表达式 | new Thread(() -> System.out.println("Hello")).start(); |
使用重写Thread类创建一个多线程程序
import java.util.Random;public class ThreadDemo {private static class MyThread extends Thread {@Overridepublic void run() {Random random = new Random();while (true) {System.out.println(Thread.currentThread().getName());try {Thread.sleep(random.nextInt(10));} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {MyThread t1 = new MyThread();t1.start();Random random = new Random();while (true) {System.out.println(Thread.currentThread().getName());try {Thread.sleep(random.nextInt(10));} catch (InterruptedException e) {e.printStackTrace();}}}
}
使用lambda表达式创建一个多线程程序
package Thread;public class Demo5 {public static void main(String[] args) {// 使用lambda表达式Thread thread1 = new Thread(() -> {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}, "thread-1");thread1.start();}
}
使用 jconsole 命令观察线程
多线程的优势
多线程能充分利用多核CPU,提高程序运行效率。以下是一个对比串行与并发执行的示例:
public class ThreadAdvantage {private static final long count = 10_0000_0000;public static void main(String[] args) throws InterruptedException {concurrency();serial();}private static void concurrency() throws InterruptedException {long begin = System.nanoTime();Thread thread = new Thread(() -> {int a = 0;for (long i = 0; i < count; i++) a--;});thread.start();int b = 0;for (long i = 0; i < count; i++) b--;thread.join();long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("并发: %f 毫秒%n", ms);}private static void serial() {long begin = System.nanoTime();int a = 0;for (long i = 0; i < count; i++) a--;int b = 0;for (long i = 0; i < count; i++) b--;long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("串行: %f 毫秒%n", ms);}
}
2. Thread类及常见方法
构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程 |
Thread(String name) | 创建命名线程 |
Thread(Runnable target, String name) | 使用Runnable创建命名线程 |
常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
2.3 启动线程:start()
Thread t = new Thread(() -> System.out.println("线程运行"));
t.start(); // 真正启动线程
中断线程
方式一:自定义标志位
public class ThreadDemo {private static class MyRunnable implements Runnable {public volatile boolean isQuit = false;@Overridepublic void run() {while (!isQuit) {System.out.println("转账中...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("停止转账");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread t = new Thread(target, "李四");t.start();Thread.sleep(5000);target.isQuit = true;}
}
方式二:使用interrupt()
package Thread;//中断线程
//通过调用线程的interrupt()方法,来中断线程
public class Demo8 {public static void main(String[] args) throws InterruptedException {// 注意,此处针对lambda的定义其实是在new Thread之前的!// 所以如果在lambda中使用thread,是会报错的!Thread thread = new Thread(() -> {// currentThread()是Thread提供的一个静态方法// 它的功能是哪个线程调用这个方法,就返回哪个线程对象的引用while (!Thread.currentThread().isInterrupted()) {// 由于这个 currentThread()方法,是在后续thread.start 之后,才执行的.// 并且是在thread线程中执行的。返回的结果就是指向thread线程对象的引用了System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// 如果线程在sleep状态时,被中断了,会触发InterruptedException异常// 当线程thread正在sleep时,主线程(main)调用thread的interrupt方法,可以提前唤醒sleep状态的线程,此时会触发InterruptedException异常。通过这个异常,能区分线程是正常休眠结束还是被提前中断唤醒。并且,当sleep因提前唤醒触发异常后,会将线程的中断标志位(isInterrupted)重置为false。因此会无限循环!// 这个printStackTrace()方法,只是用来打印异常的栈轨迹的。e.printStackTrace();// 当线程在sleep状态时被中断,会抛出InterruptedException异常。我们可以在catch块中处理这个异常,例如记录日志、清理资源等。同时,我们可以根据业务需求,选择是否继续循环或退出线程或稍等一会儿在退出循环。这里我们选择退出线程// 添加break之后,触发异常时就会结束循环,让线程结束// 在break之前也可以添加一些其他善后逻辑,相当于稍后再结束// doSomething();break;// 使用IDEA自动生成catch语句,此时默认给的代码就是再次抛出一个其他异常,如RuntimeException,但是这个做法太粗暴了,它不只是让thread线程结束,也会使整个进程结束,因为没有人catch这个RuntimeException异常。不推荐// throw new RuntimeException(e);}}});thread.start();// 在main线程中尝试终止thread线程Thread.sleep(3000);thread.interrupt();}
}
等待线程:join()
public class JoinDemo {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("t1运行中");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();t1.join(); // 等待t1结束System.out.println("t1已结束");}
}
获取当前线程引用
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName());
休眠线程:sleep()
Thread.sleep(1000); // 休眠1秒
3. 线程的状态和转移
线程状态枚举
public class ThreadState {public static void main(String[] args) {for (Thread.State state : Thread.State.values()) {System.out.println(state);}}
}
状态包括:NEW
, RUNNABLE
, BLOCKED
, WAITING
, TIMED_WAITING
, TERMINATED
。
状态转移图
[NEW]|| start()v
[RUNNABLE] <----------------+| || 获取CPU | yield()/时间片用完v |
[RUNNING] ------------------+|| sleep()/wait()/join()v
[TIMED_WAITING/WAITING]|| 时间到/notify()/interrupt()v
[RUNNABLE] <----------------+^ || | 获取锁| 等待锁 |+------------------------+| |v |
[BLOCKED] ------------------+[RUNNING]|| 执行完毕v
[TERMINATED]
4. 线程安全与同步机制
线程不安全示例
public class ThreadUnsafe {private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) count++;});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) count++;});t1.start();t2.start();t1.join();t2.join();System.out.println("count: " + count); // 可能小于100000}
}
线程不安全的原因
原因 | 说明 |
---|---|
原子性 | 操作被中断导致数据不一致 |
可见性 | 线程间数据更新不可见 |
指令重排序 | 编译器/CPU优化导致执行顺序变化 |
synchronized关键字
修饰代码块
public class SynchronizedDemo {private final Object locker = new Object();private int count = 0;public void increment() {synchronized (locker) {count++;}}
}
修饰方法
public synchronized void increment() {count++;
}
修饰静态方法
public static synchronized void increment() {count++;
}
volatile关键字
public class VolatileDemo {static class Counter {public volatile int flag = 0;}public static void main(String[] args) {Counter counter = new Counter();Thread t1 = new Thread(() -> {while (counter.flag == 0) {// 空循环}System.out.println("循环结束");});Thread t2 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("输入整数:");counter.flag = scanner.nextInt();});t1.start();t2.start();}
}
5. 线程间协作:wait与notify
public class WaitNotifyDemo {static class WaitTask implements Runnable {private final Object locker;public WaitTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker) {try {System.out.println("wait开始");locker.wait();System.out.println("wait结束");} catch (InterruptedException e) {e.printStackTrace();}}}}static class NotifyTask implements Runnable {private final Object locker;public NotifyTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker) {System.out.println("notify开始");locker.notify();System.out.println("notify结束");}}}public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(new WaitTask(locker));Thread t2 = new Thread(new NotifyTask(locker));t1.start();Thread.sleep(1000);t2.start();}
}
6. 多线程案例实战
单例模式
懒汉式(线程安全)
public class Singleton {private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
阻塞队列
public class BlockingQueue {private final int[] items;private int head = 0;private int tail = 0;private int size = 0;public BlockingQueue(int capacity) {items = new int[capacity];}public synchronized void put(int value) throws InterruptedException {while (size == items.length) {wait();}items[tail] = value;tail = (tail + 1) % items.length;size++;notifyAll();}public synchronized int take() throws InterruptedException {while (size == 0) {wait();}int value = items[head];head = (head + 1) % items.length;size--;notifyAll();return value;}
}
定时器
定时器是什么
定时器也是软件开发中的⼀个重要组件.类似于⼀个"闹钟".达到⼀个设定的时间之后,就执行某个指定好的代码
public class MyTimer {private final PriorityQueue<MyTask> queue = new PriorityQueue<>();private final Object locker = new Object();public void schedule(Runnable task, long delay) {synchronized (locker) {queue.offer(new MyTask(task, System.currentTimeMillis() + delay));locker.notify();}}public MyTimer() {Thread worker = new Thread(() -> {while (true) {synchronized (locker) {while (queue.isEmpty()) {try {locker.wait();} catch (InterruptedException e) {e.printStackTrace();}}MyTask task = queue.peek();long currentTime = System.currentTimeMillis();if (currentTime >= task.time) {queue.poll();task.runnable.run();} else {try {locker.wait(task.time - currentTime);} catch (InterruptedException e) {e.printStackTrace();}}}}});worker.start();}static class MyTask implements Comparable<MyTask> {Runnable runnable;long time;public MyTask(Runnable runnable, long time) {this.runnable = runnable;this.time = time;}@Overridepublic int compareTo(MyTask o) {return Long.compare(time, o.time);}}
}
线程池
public class MyThreadPool {private final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();public MyThreadPool(int nThreads) {for (int i = 0; i < nThreads; i++) {new Thread(() -> {while (true) {try {Runnable task = queue.take();task.run();} catch (InterruptedException e) {e.printStackTrace();}}}).start();}}public void submit(Runnable task) {try {queue.put(task);} catch (InterruptedException e) {e.printStackTrace();}}
}
7. 总结与对比
进程 vs 线程
对比项 | 进程 | 线程 |
---|---|---|
资源分配 | 独立内存空间 | 共享进程资源 |
创建开销 | 大 | 小 |
切换开销 | 大 | 小 |
通信方式 | 复杂(IPC) | 简单(共享内存) |
线程安全保证思路
- 无共享资源:避免共享数据
- 只读共享:使用不可变对象
- 同步机制:
- 原子性:synchronized
- 可见性:volatile
- 有序性:避免指令重排序
本文详细介绍了Java多线程编程的基础知识、常见问题及解决方案,并提供了丰富的代码示例。掌握多线程编程是Java工程师的必备技能,希望本文能帮助你更好地理解和应用多线程技术。
欢迎在评论区交流讨论!