进程与线程:
什么是进程?
进程的定义,一直以来没有完美的标准。
进程是程序的一次执行。应用程序以进程的形式,运行于操作系统之上,享受操作系统提供的服务。
什么是Java程序的进程?
Java编写的程序都运行在Java虚拟机(JvM)中,
每当使用java命令启动一个Java应用程序时,就会启动一个JVM进程。在这个JVM进程内部,所有Java程序代码的运行都是以线程来运行的。
什么是线程?
线程是指"进程代码段”的一次的顺序执行流程。线程演进完成后,线程是cpu调度的最小单位。一条进程可以有一个或多个线程,各个线程之间共享进程的内存空间、系统资源,进程仍然是操作系统资源分配的最小的单位。
Java线程和os线程的关系:一对一模型
线程的四种实现方式
- 继承Thread类,重写run方法;
- 实现Runnable接口类,实现run方法;以及变形写法,使用lambda表达式实现
- 使用callable和FutureTask创建异步任务,然后创建线程实例
- 通过线程池实现
继承Thread类及实现Runnable接口类示例:
public class T01 {
public static class MyThread extends Thread {
public void run() {
System.out.println("MyThread");
}
}
public static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable");
}
}
public static void main(String[] args) {
new MyThread().start();
new Thread(new MyRunnable()).start();
new Thread(() -> {
System.out.println("线程测试");
}).start();
}
}
继承Thread类或实现Runnable接口 实现异步任务的问题:
- 不能获取异步执行目标的结果
- 不能取消异步执行的任务
解决方法:
使用"可以进行管理的异步任务"相关类:Future接口和FutureTask类型
总结:三种写法最终都是实现Runnable类,实现run方法。
线程的调度模型
目前主要分为两种调度模型:分时调度模型、抢占式调度模型。
- 分时调度模型
平均分配cpu时间片,每个线程占有的cpu时间片长度一样,平均分配,一切平等
- 抢占式调度模型
哪个线程的优先级比较高,抢到的cpu时间片的概率就大。java采用的就是抢占式调度模型。
线程的常用方法
-
sleep方法,线程睡眠;当前线程结束一段时间将cpu让给其他线程运行;
-
yield方法,线程返回就绪状态,进入等待队列;
-
join方法,调用其他线程,将其他线程加入的当前线程中运行;
static void sleep() {
new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
static void yield() {
new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("AA" + i);
if (i % 10 ==0) {
Thread.yield();
}
}
}).start();
}
static void join() {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println("B" + i);
}
});
t2.start();
}
public static void main(String[] args) {
// sleep();
// yield();
join();
}
synchronized
-
锁的是对象,而不是代码;
-
尽量不使用String常量, Integer, Long
-
当synchronized不指定对象时锁定的是this对象,静态锁定时锁的xx.class;
-
锁定方法和非锁定方法可同时执行;
-
当加锁程序发生异常时,应捕获异常;若未捕获异常,synchronized失效,其他线程可立即执行当前程序
-
锁升级
-
偏向锁(记录最初运行的线程id,当存在线程争用时,升级为自旋锁)
-
自旋锁(在cpu上循环等待,当循环等待一定次数时(默认10次),升级为重量级锁)
-
重量级锁(就绪队列)
加锁代码执行时间短,线程数少,用自旋;执行时间长,线程数多,用重量级
volatile
-
线程自己的可见性
-
禁止指令重排序
线程自己的可见性验证
/**
* 当变量run不加volatile修饰时,main中改变run变量为false,线程t1得到的run为true,while循环不结束
* 当变量run加volatile修饰时,main中改变run变量为false,线程t1得到的run为false,while循环结束
*/
public class T11_volatile {
// boolean run = true;
volatile boolean run = true;
public void m() {
System.out.println("m start");
while (run) {
}
System.out.println("m end");
}
public static void main(String[] args) throws InterruptedException {
T11_volatile t11 = new T11_volatile();
new Thread(t11::m, "t1").start();
TimeUnit.SECONDS.sleep(1);
t11.run = false;
}
}
测试结果:当变量run不加volatile修饰时,main中改变run变量为false,线程t1得到的run为true,while循环不结束;当变量run加volatile修饰时,main中改变run变量为false,线程t1得到的run为false,while循环结束
原因:不加volatile时,main在自己工作区改变run变量,立即返回非堆;而t1未立即从堆中获取最新的run变量,仍使用自己工作区的run变量值。
禁止指令重排序:
在编译器编译之后的指令会分成三步 1.给指令申请内存 2.给成员变量初始化 3.是把这块内存的内容赋值给变量。而在极端高并发的情况下有可能照成1、3、2的指令顺序,所以需要volatile禁止指令重排序。