Java多线程
线程的状态
public enum State{
NEW RUNNABLE,//新生态,分为ready(准备状态),running(运行状态)
BLOCKED,//阻塞 线程在运行过程中始终在占用CPU的时间 只有当被阻塞了 才不会占用CPU的时间
WAITING TIMED_WAITING,//一直等 有时限的等待
TERMINATED//终止态
}
线程的等待状态与阻塞状态
//等待状态
Object.wait()// 使当前线程进入等待状态,等待其他线程调用 Object.notify() 或 Objec.notifyAll() 方法唤醒它;
// 传入的参数为timeOut 以ms为单位
Thread.join()// 等待指定线程执行完毕,进入等待状态。
// 在其他线程中调用该线程的join()方法则其他线程会等待该线程执行完毕后再继续执行线程
//唤醒等待状态
lock.notifyAll()// 与wait()方法对应,能够唤醒两条以上的线程
lock.notify()// 唤醒两条线程
//阻塞状态
thread.sleep()// 休眠指定的时间
多线程并发
并发指的是多个任务在时间上重叠进行,可以是线程交替执行任务,也可以是同时执行任务。具体取决于操作系统和硬件的支持。
在单核处理器上,实际上只有一个线程能够被执行,但多个线程可以通过快速的切换(时间片轮转)来模拟同时执行的效果。这种情况下,多个线程会交替地在处理器上执行各自的任务,使得任务在时间上重叠进行,实现并发。
在多核处理器或多处理器系统上,多个线程可以被同时执行,每个线程可以在不同的处理器核心上执行自己的任务,从而实现真正的并行执行。然而,当多个线程同时访问共享的资源(如变量、对象、文件等)时,可能会导致数据不一致或竞态条件等问题。为了确保线程安全,可以使用同步机制来控制对共享资源的访问。
多线程并发并不一定意味着多个线程在同一时刻同时进行计算操作,而有可能是指多个线程在同一时间段内交替执行。
并行指的是在不同的处理单元上面。
class Counter {
private int count;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Count: " + counter.getCount()); // 输出结果应为2000
}
}
而是通过join()
方法的阻塞机制,确保在主线程输出结果之前,thread1
和thread2
线程完成了各自的计数操作。join()
方法可以用于在多线程环境中协调线程的执行顺序:
// 等待线程执行完毕
try {
thread.join();
} catch (InterruptedException e) {
// 处理中断异常
}
并发类
concurrentHashMap的原理:分段锁,每一段都给它开一扇门上一把锁
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 创建多个线程,同时对 ConcurrentHashMap 进行操作
Thread thread1 = new Thread(() -> {
for (int i = 1; i <= 100; i++) {
map.put("Key" + i, i);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 101; i <= 200; i++) {
map.put("Key" + i, i);
}
});
Thread thread3 = new Thread(() -> {
for (int i = 201; i <= 300; i++) {
map.put("Key" + i, i);
}
});
// 启动线程
thread1.start();
thread2.start();
thread3.start();
// 等待所有线程执行完毕
try {
thread1.join();
thread2.join();
thread3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出 ConcurrentHashMap 中的数据
System.out.println("ConcurrentHashMap: " + map);
}
}
注意:因为每个线程的执行速度和竞争情况可能不同,因此无法确定不同线程之间操作的先后顺序。主线程调用join()方法并不能决定线程之间的执行顺序。concunrrentHashMap中的数据是三个线程共同完成之后的结果。
高阶线程
共享锁与排他锁的关系
- 多个线程可以同时持有共享锁,但只有一个线程可以持有排它锁(Exclusive Lock);
- 当一个线程持有共享锁时,其他线程可以继续持有共享锁,但不能持有排它锁;
- 当一个线程持有排它锁时,其他线程不能持有共享锁或排它锁;
- 共享锁不会阻塞其他线程持有共享锁,但会阻塞其他线程持有排它锁;
- 共享锁可以降级为排它锁,但排它锁不能升级为共享锁。
线程安全
尽管线程之间的栈内存独立为线程提供了一定的隔离,但仍需要额外的线程安全机制来处理共享数据的访问。(对堆内存中的数据而言,显然没有办法保证线程安全。)
线程安全(Thread safety)是指在多线程环境下,对共享资源的访问和操作能够正确地执行而不会导致数据不一致、并发错误或其他不可预期的问题。
具体来说,线程安全的定义包括以下几个方面:
原子性(Atomicity):对于多个线程同时执行的操作,要么完全执行成功,要么完全不执行,不会出现中间或部分执行的情况。多个线程对共享资源的操作应该是原子的,不会相互干扰。
atomic
可见性(Visibility):一个线程对共享资源的修改对其他线程应该是可见的。当一个线程修改了共享资源的值后,其他线程应该能够立即看到这个修改。
volatile
有序性(Ordering):多线程环境下的操作应该按照一定的顺序进行,以确保正确的执行。线程之间的操作可能存在并发冲突,需要通过合适的同步机制来保证操作的有序性。
通过在各线程之间调用join()方法来调整线程的执行顺序
atomicxxx 原子类变量
“要么成功要么失败”,在多线程环境下是线程安全的,不会被其他线程中断或者影响,不需要其他的同步措施。
在多线程环境下的lambda表达式下
.map(list->{
// 变量是无法传到lambda表达式里面 通过原子计算
AtomicInteger directSaleCount = new AtomicInteger(0);
AtomicInteger indirectSaleCount = new AtomicInteger(0);
AtomicInteger totalDays = new AtomicInteger(0);
SortedMap<Integer,String> map = new TreeMap<>();// 行为模式--时间 选用treeMap的原因是自动根据key排序,并且firstKey()能够获取最小键值对应的值
list
.stream()
.sorted(Comparator.comparing(UserAct::getTime))
.forEach(act -> {
if(act.getBehaviourType()==4){
if(map.isEmpty()){
directSaleCount.getAndIncrement();
}else {
final String startTime = map.get(map.firstKey());
final String stopTime = act.getTime();
indirectSaleCount.getAndIncrement();
try {
final long start = sdf.parse(startTime).getTime();
final long stop = sdf.parse(stopTime).getTime();
totalDays.getAndAdd((int)((stop-start)/DAY_IN_MILLIS));
} catch (ParseException ex) {
ex.printStackTrace();
} finally{
map.clear();
}
}
}
map.put(act.getBehaviourType(),act.getTime());
});
// 没有自动拆装箱的机制,需要通过对象去get到值
return new int[]{
directSaleCount.get(),indirectSaleCount.get(),totalDays.get()};
})
普通变量并不是线程安全的,所以此处应用原子类变量。
volatile xxx 非线程安全型可见性变量
volatile
变量适用于只有一个线程写入,多个线程读取的场景,例如线程间的通信标志位。而 Atomic
类适用于多个线程对同一个变量进行复合操作的场景,例如计数器、累加器等。
volatile适合只需要读的或者只有一个线程改的,而atomic适合有多个线程改的。
一个卖票/造票,多人买票
static volatile int ticket = 20;// 被多个客户共享的票。
jdk写的比我们重构的线程安全,但并不是绝对安全。所谓的线程安全指的是数据一致性,即我在修改的时候,你是不能修改的,你只能看到我修改前或后的数据,达成线程安全需要加锁。对于要求绝对线程安全的,我们有替代品。
//The number of times this HashMap has been structurally modified 记录增删运行次数
transient int modCount;
//在查询数据的时候,我先取modCount,当迭代这条数据的时候,有人做了一个新增操作,这个时候的modCount和前面的modCount不同,这个时候就会报异常。
//ConcurrentModificationException 实时修改异常
生产者消费者模式
public class ProducerAndConsumer {
// 共享的数据都需要设置为静态的。
final static Object lock = new Object();// 共享数据->共享同一把锁。
static volatile int ticket = 20;// 被多个客户共享的票。
// 生产者:1.在造票后进入阻塞状态 2.造票后通知全部消费者
static void produce(){
final Thread thread = new Thread(() -> {
final String name = Thread.currentThread().getName();
while (true) {
// 上锁方式1:同步代码块
synchronized (lock) {
if (ticket > 0) {
try {
lock.wait(5000);// 阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
ticket += 20;
System.out.printf("%s 造票 %d 张\n",name,20);
lock.notifyAll();// 两条以上的线程 lock.notify():两条线程之间
}
}
}
});
thread.setName("车站");
thread.start();
}
static void consume(int i){
final Thread thread = new Thread(() -> {
final String name = Thread.currentThread().getName();
while(true){
synchronized (lock){
if(ticket>0){
ticket--;
System.out.printf("%s 抢到一张票,剩余 %d 张\n",name,ticket);
try {
Thread.sleep(1000);// 抢票输出过程的阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
thread.setName("黄牛"+i);
thread.start();
}
public static void main(String[] args) {
produce();
for (int i = 1; i <= 5; i++) {
consume(i);
}
}
}
java中任意对象都作为锁,因为Object是锁。
在这其中锁和票都是共享的,都应该定义为静态变量
共享锁:多个线程能够共享读取其中的数据,但是不能进行写操作。(所有买家只能获取票数,但无法修改票数)
熟悉Thread.sleep()以及Thread.currentThread.getName()的应用