【Java SE】多线程
1.1 线程的生命周期

方法名 | 说明 |
---|---|
yield() | |
stop() | |
sleep() | |
wait() | 阻塞 |
suspend() | 挂起 |
notify()/notifyAll() | 唤醒 |
resume() | 取消挂起 |
1.2 线程的安全问题
1.2.1 通过同步机制解决线程安全问题
方式一:同步代码块
synchronized(同步监视器) { //需要被同步的代码 }
class Window implements Runnable { private int tickets = 100; private Object obj = new Object();//多线程共用同一把锁,即是同步监视器 @Override public void run() { while (true) { synchronized (obj) { if (tickets > 0) { System.out.println("售票,票号为:" + tickets); tickets--; } else { break; } } } } }
说明:操作共享数据的代码,即为需要被同步的代码。
同步监视器,俗称锁。任何类的对象,都可以充当锁。但要求多个线程必须共用同一把锁。在实现Runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器。在继承Thread创建多线程的方式中,可以使用当前类(Bank.class)充当同步监视器,类本身也是一个对象。

方式二:同步方法
1.同步方法解决实现Runnable接口的线程创建方式的线程安全问题
@Override public void run() { while(true) { show(); } } private synchronized void show() { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets); tickets--; }
此时同步方法隐藏使用this作为同步监视器(锁)
2.同步方法解决实现继承Thread的线程创建方式的线程安全问题
@Override public void run() { while(true) { show(); } } private static synchronized void show() { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets); tickets--; } }
此时同步监视器为当前的类
1.2.2 线程同步解决单例模式懒汉式的线程安全问题
方式一:效率稍差
public class Bank { private static Bank bank = null; private Bank() { } public static Bank getBank() { synchronized (Bank.class) { if (bank == null) bank = new Bank(); return bank; } } }
方式二:效率更高
public class Bank { private static Bank bank = null; private Bank() { } public static Bank getBank() { if(bank == null) { synchronized (Bank.class) { if (bank == null) bank = new Bank(); } } return bank; } }
1.3 线程的死锁问题 DeadLock
不同线程分别占用对方需要同步的资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。出现死锁,不会出现异常,不会出现提示,只是所有的线程都处于堵塞状态,无法继续。
public class theadsTest { public static void main(String[] args) { StringBuffer s1 = new StringBuffer(); StringBuffer s2 = new StringBuffer(); new Thread() { @Override public void run() { synchronized (s1) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } s1.append("a"); s2.append("1"); synchronized (s2) { s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); } } } }.start(); new Thread(new Runnable() { @Override public void run() { synchronized (s2) { s1.append("c"); s2.append("3"); synchronized (s1) { s1.append("d"); s2.append("4"); System.out.println(s1); System.out.println(s2); } } } }).start(); } }
解决方法
专门的算法、原则
尽量减少共享资源的使用
尽量避免嵌套同步
1.4 Lock锁解决线程安全问题 jdk5.0新增
private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true) { try { lock.lock(); if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); ticket--; } else { break; } }finally { lock.unlock(); } } }
lock方法默认this为同步监视器,因此线程创建方式只能使用实现Runnable接口的方式,继承的方式需要加static。synchronized在执行完同步代码后会手动释放同步监视器,lock方式需要手动启动同步(lock()),同时结束同步也需要
优先使用顺序:Lock->同步代码块(已经进入方法体,分配了相应资源)->同步方法(在方法体之外)
1.4 线程的通信
@Override public void run() { while(true) { synchronized (this) { notify(); if (num <= 100) { System.out.println(Thread.currentThread().getName() + ":" + num); num++; try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } }
线程一首先获得同步监视器(锁),执行输出后wait()进入了阻塞状态,释放了手中的锁,随后线程二执行notify()唤醒线程一,线程二获得锁进入执行输出(线程一此时没有锁不能够执行),随后线程二wait()进入堵塞状态释放手中的锁,进程一获得锁后执行notify唤醒线程二......以此实现交叉输出。
线程通信方法 | 说明 |
---|---|
wait() | 当前线程进入堵塞状态并释放同步监视器 |
notify() | 唤醒一个wait的线程,若多个线程处于堵塞状态则唤醒优先级高的哪一个。 |
notifyAll() | 唤醒所有的线程。 |
三个方法必须使用在同步代码块或者同步方法中,且三个方法的调用者必须是同步代码块或者同步方法的同步检测器,因此synchronized (this)参数不能为类或者obj。否则会出现IllegalMonitorStateException,或者写为obj.wait()。此外三个方法定义在java,lang.object中。
面试题 sleep() 和 wait()方法的异同
相同点:都能够使线程堵塞。
不同点:①Thread中定义的sleep,Object中定义的wait。
②wait只能使用在同步代码块和同步方法中,由同步检测器调用。
③wait会释放线程的同步检测器,sleep则不会。
生产者消费者问题
package com.hikaru.exer; class Clerk { private static int num = 0; public synchronized void product() { if(num < 20) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num++; System.out.println(Thread.currentThread().getName() + "正在生产第" + num + "产品..."); notify(); }else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void consume() { if(num > 0) { // try { // Thread.sleep(500); // } catch (InterruptedException e) { // e.printStackTrace(); // } System.out.println(Thread.currentThread().getName() + "正在消费第" + num + "产品..."); num--; notify(); }else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Producer extends Thread{ private Clerk clerk; public Producer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println(this.getName() + "开始生产产品..."); while(true) { clerk.product(); } } } class Customer extends Thread{ private Clerk clerk; public Customer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println(this.getName() + "开始消费产品..."); while(true) { clerk.consume(); } } } public class ProductTest { public static void main(String[] args) { Clerk clerk = new Clerk(); Producer p1 = new Producer(clerk); Customer c1 = new Customer(clerk); p1.setName("生产者一"); c1.setName("消费者一"); c1.start(); p1.start(); } }
1.5 通过实现Callable接口新增线程 jdk5.0新增
优点:①call方法相比run()方法,可以有返回值
②方法可以抛出异常
③支持泛型的返回值
④需要借助FutureTask类,比如获取返回结果
FutureTask
FutureTask是Future的唯一实现类,可以对Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。FutureTask同时实现了Runnable、Callable接口,它既可作为Runnable被线程执行,又可作为Future得到Callable的结果。
NumThread numThread = new NumThread();//创建Callable接口实现类 FutureTask futureTask = new FutureTask(numThread); Thread t1 = new Thread(futureTask);//作为Runnable被线程执行 t1.start(); try { System.out.println(futureTask.get());//作为Future得到Callable的结果 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }
1.6 使用线程池创建线程
经常创建和销毁线程、使用量特别大的资源,比如并发情况下的线程,对性能的影响很大。提前创建好多个线程放入线程池中,使用时直接获取,用完放回线程池。能够提高响应速度,降低资源消耗,便于资源管理。
public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(10); service.execute(new NumThread());//适用于Runnable // service.submit();//适用于Callable service.shutdown(); }
ExecutorServic
真正的连接池接口。常见子类ThreadPoolExecutor
execute(Runnable command) | 执行命令,没有返回值,一般用来执行Runnable |
submit(Callable task) | 执行命令,有返回值,一般用来执行Callable |
shutdown | 关闭连接池 |
Executors
工具类、线程池的工厂类,用于创建返回不同类型的线程池
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步