一.线程和进程的区别
为什么要进行并发编程:CPU多核心
通过多进程可以实现并发编程,但是进程太重量了,因此引入了多线程.
线程是轻量级的进程,创建和销毁的开销更小,进程是包含线程的,同一进程的若干线程公用同一份系统资源(内存,硬盘等).
二.Thread类重要的属性
三.线程等待
无法控制两个线程的调度执行顺序但是可以确定两个线程的结束顺序,让后结束的线程等待先结束的线程;
此后结束的线程就会进入阻塞,一直到另一个线程结束,阻塞才结束;
注意:
(1)join()是确保被等待的线程先结束,如果已经结束了,join就不必再等待了;
(2)不一定是两个线程,可以同时等待若干个线程,也可以相互等待
join()函数的参数:
currentThread()函数:
获取当前线程的引用:
public class demo12 {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Thread t = new Thread(() -> {
System.out.println("t waiting");
try {
mainThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t ok");
});
t.start();
Thread.sleep(2000);
System.out.println("main线程结束");
}
}
注意:Thread.sleep()让调用的线程阻塞等待,是有一定时间的;
线程执行sleep这样的操作称为"放权",放弃使用CPU的权利;
若CPU占用率过高,就可以通过sleep来改善;
四.进程状态
1.NEW
当前Thread对象虽然有了,但是内核的线程还没有(还没调用过start);
2.TERMINATED
当前Thread对象虽然还在,但是内核的线程已经销毁(结束)了;
3.RUNNABLE
就绪状态:正在CPU上运行 + 随时可以去CPU上运行;
4.BLOCKED
因为锁竞争而引起的阻塞;
5.TIMED_WAITING
有超时间的等待:比如sleep或者join的带参数版本;
6.WAITING
没有超时间的阻塞等待:比如join/wait;
五.线程安全
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
// 对 count 变量进⾏⾃增 5w 次
for (int i = 0; i < 50000; i++) {
count++;
}
});
Thread t2 = new Thread(() -> {
// 对 count 变量进⾏⾃增 5w 次
for (int i = 0; i < 50000; i++) {
count++;
}
});
t1.start();
t2.start();
// 如果没有这俩 join, 肯定不⾏的. 线程还没⾃增完, 就开始打印了. 很可能打印出来的 cou
t1.join();
t2.join();
// 预期结果应该是 10w
System.out.println("count: " + count);
}
错误原因:
count++在CPU视角里其实是三步:
(1)把内存中的数据读取到CPU寄存器里;(load)
(2)把CPU寄存器的数据+1;(add)
(3)把寄存器的数值写回内存;(save)
CPU在调度执行的时候,说不上啥时候就会把线程给切换(抢占式执行,随机调度)
指令是CPU执行的最基本单位,至少把当前指令执行完,才会被调度走
但是由于count++是三个指令,可能执行过程中会被调度走;
注意:有可能出现小于5w的情况:比如线程1加一次的过程中线程2加了四次,实际相当于加一次的结果;