《线程(二)》
目录
一、线程同步
并发访问问题
在处理多任务时,多线程无疑是高效的,但由于默认情况下线程之间是异步的,就会产生并发访问问题。
案例一(存钱业务模拟)
- 账户类
package com.hpr.test;
public class Account {
private int money = 0;
public void saveMoney(int save) {
money = money + save;
}
public int getMoney() {
return money;
}
}
- 测试类
package com.hpr.test;
public class Test {
static class SaveThread extends Thread {
private Account account;
public SaveThread(Account account) {
this.account = account;
}
@Override
public void run() {
//每个线程存款5次,每次存100
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(10);
account.saveMoney(100);
System.out.println(Thread.currentThread().getName() + "存钱成功,当前余额:" + account.getMoney());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
//1.创建同步资源:Account账户
Account account = new Account();
//2.创建三个线程
SaveThread th1 = new SaveThread(account);
th1.setName("th1");
SaveThread th2 = new SaveThread(account);
th2.setName("th2");
SaveThread th3 = new SaveThread(account);
th3.setName("th3");
//3.启动线程
th1.start();
th2.start();
th3.start();
//4.加入主线程,优先执行
try {
th1.join();
th2.join();
th3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//5.主线程调用方法查看账户余额
System.out.println("账户余额:" + account.getMoney());
}
}
执行结果
变量及方法是线程共享的,即每个线程都可以访问,但不会排队访问,因此大概率下会造成结果不一致情况。
同步处理(三种方式)
- 同步方法(synchronized):同一时刻只能有一个线程进入该方法,其他线程需"排队"访问。
...
public class Account {
...
public synchronized void saveMoney(int save) {
money = money + save;
}
...
}
- 同步代码块(给对象上锁,即同一时刻只能有一个线程访问该对象该部分代码块)
...
public class Account {
...
public void saveMoney(int save) {
synchronized (this) {
money = money + save;
}
}
...
}
- 重入锁(重入锁是以对象形式使用的,要保证一个对象一个锁,可将其声明为全局变量)
...
public class Account {
..
private Lock lock = new ReentrantLock();
public void saveMoney(int save) {
//上锁
lock.lock();
//执行业务
money = money + save;
//解锁
lock.unlock();
}
...
}
执行结果
二、Java锁
Java中每一个对象都有一个lock,当访问一个对象某个synchronized方法的时候,该对象就会被上锁,同一时刻只能有一个线程访问该对象synchronized资源,其他线程需排队等待。
注意:被上锁的是对象,而不是方法,非synchronized资源不受影响。
同步锁(对象锁)
对象中有多个synchronized方法,当多线程访问其中任意一个,保证有且仅有一个线程在同一时刻能够访问到synchronized资源,剩余线程进行排队等待。
public synchronized void saveMoney(int save) {
money = money + save;
}
静态锁(类锁)
在静态方法中使用同步锁(static synchronized),该类将被上锁。即同步锁针对一个对象,而静态锁针对该类的所有对象。
public static synchronized void saveMoney(int save) {
money = money + save;
}
死锁
当两个线程循环依赖于一对同步对象时将发生死锁,例如:
三、线程池
当线程较少时我们可手动创建,但当所需线程非常多的时候,使用线程池将会事半功倍,Java通过Executors提供了四种线程池,并具有以下优势:
- 提高资源利用率
线程池可以重复利用已经创建了的线程; - 提高响应速度
因为当线程池中的线程没有超过线程池的最大上限时,有的线程处于等待分配任务状态,当任务到来时,无需创建线程就能被执行; - 具有可管理性
线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销。
案例二(单一线程池、固定线程池、缓存线程池)
//单一线程池(只有一个线程)
ExecutorService es = Executors.newSingleThreadExecutor();
//固定线程池(固定数量线程)
//ExecutorService es = Executors.newFixedThreadPool(2);
//缓存线程池(不定数量线程)
//ExecutorService es = Executors.newCachedThreadPool();
//线程任务
Runnable runnable = () -> {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
};
//提交任务至线程池处理
es.submit(runnable);
es.submit(runnable);
es.submit(runnable);
//关闭线程池
es.shutdown();
- 单一线程池执行结果
- 固定线程池执行结果
- 缓存线程池执行结果
定时线程池
//Runtime.getRuntime().availableProcessors() 获取JVM的可用的处理器数量
ScheduledExecutorService ses = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
Runnable task = () -> System.out.println("----------------------");
//scheduleAtFixedRate(定时任务, 延迟时长, 间隔时长, 时长单位);
ses.scheduleAtFixedRate(task, 3, 1, TimeUnit.SECONDS);
执行结果
延迟三秒之后,每隔一秒执行一次打印。
总结
重点
- 线程同步实现手段;
- Java锁机制相关概念;
- Java四种线程池的特点及使用方式。
难点
- 线程同步实现手段;
- Java锁机制相关概念。