线程概述
什么是进程?什么是线程?它们的区别?
进程是指操作系统中的一段程序,它是一个正在执行中的程序实例,具有独立的内存空间和系统资源,如文件、网络端口等。在计算机程序执行时,先创建进程,再在进程中进行程序的执行。一般来说,一个进程可以包含多个线程。
线程是指进程中的一个执行单元,是进程的一部分,它负责在进程中执行程序代码。每个线程都有自己的栈和程序计数器,并且可以共享进程的资源。多个线程可以在同一时刻执行不同的操作,从而提高了程序的执行效率。
现代的操作系统是支持多进程的,也就是可以启动多个软件,一个软件就是一个进程。称为:多进程并发。
通常一个进程都是可以启动多个线程的。称为:多线程并发。
多线程的作用?
提高处理效率。(多线程的优点之一是能够使 CPU 在处理一个任务时同时处理多个线程,这样可以充分利用 CPU 的资源,提高 CPU 的利用效率。)
JVM规范中规定:
堆内存、方法区 是线程共享的。
虚拟机栈、本地方法栈、程序计数器 是每个线程私有的。
关于Java程序的运行原理
“java HelloWorld”执行后,会启动JVM,JVM的启动表示一个进程启动了。
JVM进程会首先启动一个主线程(main-thread),主线程负责调用main方法。因此main方法是在主线程中运行的。
除了主线程之外,还启动了一个垃圾回收线程。因此启动JVM,至少启动了两个线程。
在main方法的执行过程中,程序员可以手动创建其他线程对象并启动。
并发与并行
并发
使用单核CPU的时候,同一时刻只能有一条指令执行,但多个指令被快速的轮换执行,使得在宏观上具有多个指令同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干端,使多个指令快速交替的执行。
如上图所示,假设只有一个CPU资源,线程之间要竞争得到执行机会。图中的第一个阶段,在A执行的过程中,B、C不会执行,因为这段时间内这个CPU资源被A竞争到了,同理,第二阶段只有B在执行,第三阶段只有C在执行。其实,并发过程中,A、B、C并不是同时进行的(微观角度),但又是同时进行的(宏观角度)。
在同一个时间点上,一个CPU只能支持一个线程在执行。因为CPU运行的速度很快,CPU使用抢占式调度模式在多个线程间进行着高速的切换,因此我们看起来的感觉就像是多线程一样,也就是看上去就是在同一时刻运行。
并行
使用多核CPU的时候,同一时刻,有多条指令在多个CPU上同时执行。
如图所示,在同一时刻,ABC都是同时执行(微观、宏观)。
并发编程与并行编程
在CPU比较繁忙(假设为单核CPU),如果开启了很多个线程,则只能为一个线程分配仅有的CPU资源,
这些线程就会为自己尽量多抢时间片,这就是通过多线程实现并发,线程之间会竞争CPU资源争取执行机会。
在CPU资源比较充足的时候,一个进程内的多个线程,可以被分配到不同的CPU资源,这就是通过多线程实现并行。
至于多线程实现的是并发还是并行?上面所说,所写多线程可能被分配到一个CPU内核中执行,也可能被分配到不同CPU执行,分配过程是操作系统所为,不可人为控制。所以,如果有人问我我所写的多线程是并发还是并行的?我会说,都有可能。
总结:单核CPU上的多线程,只是由操作系统来完成多任务间对CPU的运行切换,并非真正意义上的并发。随着多核CPU的出现,也就意味着不同的线程能被不同的CPU核得到真正意义的并行执行,故而多线程技术得到广泛应用。
不管并发还是并行,都提高了程序对CPU资源的利用率,最大限度地利用CPU资源,而我们使用多线程的目的就是为了提高CPU资源的利用率。
线程的调度策略
线程的调度模型
- 如果多个线程被分配到一个CPU内核中执行,则同一时刻只能允许有一个线程能获得CPU的执行权,那么进程中的多个线程就会抢夺CPU的执行权,这就是涉及到线程调度策略。
- 分时调度模型
- 所有线程轮流使用CPU的执行权,并且平均的分配每个线程占用的CPU的时间。
- 抢占式调度模型
- 让优先级高的线程以较大的概率优先获得CPU的执行权,如果线程的优先级相同,那么就会随机选择一个线程获得CPU的执行权,而Java采用的就是抢占式调用。
实现线程
第一种方式
- 编写一个类继承Thread,重写run方法。
- 创建线程对象:Thread t = new MyThread();
- 启动线程:t.start();
/**
* 在Java语言中,实现线程,有两种方式,第一种方式:
* 第一步:编写一个类继承 java.lang.Thread
* 第二步:重写run方法
* 第三步:new线程对象
* 第四部:调用线程对象的start方法来启动线程。
*/
public class ThreadTest02 {
public static void main(String[] args) {
// 创建线程对象
//MyThread mt = new MyThread();
Thread t = new MyThread();
// 直接调用run方法,不会启动新的线程。
// java中有一个语法规则:对于方法体当中的代码,必须遵循自上而下的顺序依次逐行执行。
// run()方法不结束,main方法是无法继续执行的。
//t.run();
// 调用start()方法,启动线程
// java中有一个语法规则:对于方法体当中的代码,必须遵循自上而下的顺序依次逐行执行。
// start()方法不结束,main方法是无法继续执行的。
// start()瞬间就会结束,原因这个方法的作用是:启动一个新的线程,只要新线程启动成功了,start()就结束了。
t.start();
// 这里编写的代码在main方法中,因此这里的代码属于在主线程中执行。
for (int i = 0; i < 100; i++) {
System.out.println("main--->" + i);
}
}
}
// 自定义一个线程类
// java.lang.Thread本身就是一个线程。
// MyThread继承Thread,因此MyThread本身也是一个线程。
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("MyThread--->" + i);
}
}
}
调用run方法内存图
调用start方法内存图
第二种方式
- 编写一个类实现Runnable接口,实现run方法。
- 创建线程对象:Thread t = new Thread(new MyRunnable());
- 启动线程:t.start();
优先选择第二种方式:因为实现接口的同时,保留了类的继承。
第二种方式也可以使用匿名内部类。
/**
* 在Java语言中,实现线程,有两种方式,第二种方式:
* 第一步:编写一个类实现 java.lang.Runnable接口(可运行的接口)
* 第二步:实现接口中的run方法。
* 第三步:new线程对象
* 第四部:调用线程的start方法启动线程
*
* 总结:实现线程两种方式:
* 第一种:编写类直接继承Thread
* 第二种:编写类实现Runnable接口
*
* 推荐使用第二种,因为实现接口的同时,保留了类的继承。
*/
public class ThreadTest03 {
public static void main(String[] args) {
// 创建Runnable对象
//Runnable r = new MyRunnable();
// 创建线程对象
//Thread t = new Thread(r);
// 创建线程对象
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();
// 主线程中执行的。
for (int i = 0; i < 100; i++) {
System.out.println("main----->" + i);
}
}
}
// 严格来说,这个不是一个线程类
// 它是一个普通的类,只不过实现了一个Runnable接口。
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("t----->" + i);
}
}
}
匿名内部类方式实现
public class ThreadTest04 {
public static void main(String[] args) {
/*// 创建线程对象
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("t ---> " + i);
}
}
});
// 启动线程
t.start();*/
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("t ---> " + i);
}
}
}).start();
for (int i = 0; i < 100; i++) {
System.out.println("main ---> " + i);
}
}
}
第三种方式
实现Callable接口,实现call方法。
/**
* 实现线程的第三种方式:实现Callable接口,实现call方法。
* 这种方式实现的线程,是可以获取到线程返回值的。
*/
public class ThreadTest {
public static void main(String[] args) {
// 创建“未来任务”对象
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// 处理业务......
Thread.sleep(1000 * 5);
return 1;
}
});
// 创建线程对象
Thread t = new Thread(task);
t.setName("t");
// 启动线程
t.start();
try {
// 获取“未来任务”线程的返回值
// 阻塞当前线程,等待“未来任务”结束并返回值。
// 拿到返回值,当前线程的阻塞才会解除。继续执行。
Integer i = task.get();
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
}
}
}
第四种方式
线程池技术
/**
* 创建线程的第四种方式:使用线程池技术。
* 线程池本质上就是一个缓存:cache
* 一般都是服务器在启动的时候,初始化线程池,
* 也就是说服务器在启动的时候,创建N多个线程对象,
* 直接放到线程池中,需要使用线程对象的时候,直接从线程池中获取。
*/
public class ThreadTest {
public static void main(String[] args) {
// 创建一个线程池对象(线程池中有3个线程)
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 将任务交给线程池(你不需要触碰到这个线程对象,你只需要将要处理的任务交给线程池即可。)
executorService.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
});
// 最后记得关闭线程池
executorService.shutdown();
}
}
t.start()和t.run()的本质区别?
- 本质上没有区别,都是普通方法调用。只不过两个方法完成的任务不同。
- t.run()是调用run方法。执行run方法中的业务代码。
- t.start()是启动线程,只要线程启动了,start()方法就执行结束了。
线程常用三个方法
- 实例方法:
-
- String getName(); //获取线程名字
- void setName(String name);//给线程起名字
- 静态方法:static Thread currentThread();//获取当前线程
线程生命周期的七大状态
- 线程生命周期指的是:从线程对象新建,到最终线程死亡的整个过程。
- 线程生命周期包括七个重要阶段:
-
- 新建状态(NEW)
- 就绪状态(RUNNABLE)
- 运行状态(RUNNABLE)
- 超时等待状态(TIMED_WAITING)
- 等待状态(WAITING)
- 阻塞状态(BLOCKED)
- 死亡状态(TERMINATED)
关于sleep
/**
* 关于线程的sleep方法:
* 1. static void sleep(long millis)
* 静态方法,没有返回值,参数是一个毫秒。1秒 = 1000毫秒
* 2. 这个方法作用是:
* 让当前线程进入休眠,也就是让当前线程放弃占有的CPU时间片,让其进入阻塞状态。
* 意思:你别再占用CPU了,让给其他线程吧。
* 阻塞多久呢?参数毫秒为准。在指定的时间范围内,当前线程没有权利抢夺CPU时间片了。
* 3. 怎么理解“当前线程”呢?
* Thread.sleep(1000); 这个代码出现在哪个线程中,当前线程就是这个线程。
* 4. run方法在方法重写的时候,不能在方法声明位置使用 throws 抛出异常。
* 5. sleep方法可以模拟每隔固定的时间调用一次程序。
*/
public class ThreadTest {
public static void main(String[] args) {
try {
// 让当前线程睡眠5秒
// 这段代码出现在主线程中,所以当前线程就是主线程
// 让主线程睡眠5秒
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "===>" + i);
}
// 启动线程
Thread t = new Thread(new MyRunnable());
t.setName("t");
t.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "===>" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
面试题
public class ThreadTest {
public static void main(String[] args) {
MyThread t = new MyThread();
t.setName("t");
t.start();
try {
// 这行代码并不是让t线程睡眠,而是让当前线程睡眠。
// 当前线程是main线程。
t.sleep(1000 * 5); // 等同于:Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "===>" + i);
}
}
}
class MyThread extends Thread {
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "===>" + i);
}
}
}
中断线程睡眠
/**
* 怎么中断一个线程的睡眠。(怎么解除线程因sleep导致的阻塞,让其开始抢夺CPU时间片。)
*/
public class ThreadTest {
public static void main(String[] args) {
// 创建线程对象并启动
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "===> begin");
try {
// 睡眠一年
Thread.sleep(1000 * 60 * 60 * 24 * 365);
} catch (InterruptedException e) {
// 打印异常信息
//e.printStackTrace();
System.out.println("知道了,这就起床!");
}
// 睡眠一年之后,起来干活了
System.out.println(Thread.currentThread().getName() + " do some!");
}
});
// 启动线程
t.start();
// 主线程
// 要求:5秒之后,睡眠的Thread-0线程起来干活
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// Thread-0起来干活了。
// 这行代码的作用是终止 t 线程的睡眠。
// interrupt方法是一个实例方法。
// 以下代码含义:t线程别睡了。
// 底层实现原理是利用了:异常处理机制。
// 当调用这个方法的时候,如果t线程正在睡眠,必然会抛出:InterruptedException,然后捕捉异常,终止睡眠。
t.interrupt();
}
}
线程的终止
/**
* 一个线程 t 一直在正常的运行,如何终止 t 线程的执行!!!!
*/
public class ThreadTest {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.setName("t");
t.start();
// 5秒之后,t线程停止!
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 终止线程t的执行。
// 从java2开始就不建议使用了,因为这种方式是强行终止线程。容易导致数据丢失。
// 没有保存的数据,在内存中的数据一定会因为此方式导致丢失。
t.stop();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "===>" + i);
}
}
}
这种方式会导致数据丢失,不建议使用
停止线程的合适方法
/**
* 如何合理的,正常的方式终止一个线程的执行?
* 一般我们在实际开发中会使用打标记的方式,来终止一个线程的执行。
*/
public class ThreadTest {
public static void main(String[] args) {
// 创建线程
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.setName("t");
// 启动线程
t.start();
// 5秒之后终止线程t的执行
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//终止线程t的执行。
mr.run = false;
}
}
class MyRunnable implements Runnable {
/**
* 是否继续执行的标记。
* true表示:继续执行。
* false表示:停止执行。
*/
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(run){
System.out.println(Thread.currentThread().getName() + "==>" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
return;
}
}
}
}
守护线程
/**
* 1. 在Java语言中,线程被分为两大类:
* 第一类:用户线程(非守护线程)
* 第二类:守护线程(后台线程)
*
* 2. 在JVM当中,有一个隐藏的守护线程一直在守护者,它就是GC线程。
*
* 3. 守护线程的特点:所有的用户线程结束之后,守护线程自动退出/结束。
*
* 4. 如何将一个线程设置为守护线程?
* t.setDaemon(true);
*/
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.setName("t");
// 在启动线程之前,设置线程为守护线程
myThread.setDaemon(true);
myThread.start();
// 10s结束!
// main线程中,main线程是一个用户线程。
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "===>" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class MyThread extends Thread {
@Override
public void run() {
int i = 0;
while(true){
System.out.println(Thread.currentThread().getName() + "===>" + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
定时任务
/**
* 1. JDK中提供的定时任务:
* java.util.Timer 定时器
* java.util.TimerTask 定时任务
* 2. 定时器 + 定时任务:可以帮我们在程序中完成:每间隔多久执行一次某段程序。
* 3. Timer的构造方法:
* Timer()
* Timer(boolean isDaemon) isDaemon是true表示该定时器是一个守护线程。
*/
public class ThreadTest {
public static void main(String[] args) throws Exception{
// 创建定时器对象(本质上就是一个线程)
// 如果这个定时器执行的任务是一个后台任务,是一个守护任务,建议将其定义为守护线程。
Timer timer = new Timer(true);
// 指定定时任务
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = sdf.parse("2024-01-27 10:22:00");
//timer.schedule(new LogTimerTask(), firstTime, 1000);
// 匿名内部类的方式
timer.schedule(new TimerTask() {
int count = 0;
@Override
public void run() {
// 执行任务
Date now = new Date();
String strTime = sdf.format(now);
System.out.println(strTime + ": " + count++);
}
}, firstTime, 1000 * 5);
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
}
}
}
/**
* 定时任务类:专门记录日期的定时任务类。
*/
public class LogTimerTask extends TimerTask {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
int count = 0;
@Override
public void run() {
// 执行任务
Date now = new Date();
String strTime = sdf.format(now);
System.out.println(strTime + ": " + count++);
}
}
join方法
/**
* 线程合并
* 1. 调用join()方法完成线程合并。
*
* 2. join()方法是一个实例方法。(不是静态方法) t.join
*
* 3. 假设在main方法(main线程)中调用了 t.join(),后果是什么?
* t线程合并到主线程中。主线程进入阻塞状态。直到 t 线程执行结束。主线程阻塞解除。
*
* 4. t.join()方法其实是让当前线程进入阻塞状态,直到t线程结束,当前线程阻塞解除。
*
* 5. 和sleep方法有点类似,但不一样:
* 第一:sleep方法是静态方法,join是实例方法。
* 第二:sleep方法可以指定睡眠的时长,join方法不能保证阻塞的时长。
* 第三:sleep和join方法都是让当前线程进入阻塞状态。
* 第四:sleep方法的阻塞解除条件?时间过去了。 join方法的阻塞解除条件?调用join方法的那个线程结束了。
*/
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
t.setName("t");
t.start();
System.out.println("main begin");
// 合并线程
// t合并到main线程中。
// main线程受到阻塞(当前线程受到阻塞)
// t线程继续执行,直到t线程结束。main线程阻塞解除(当前线程阻塞解除)。
//t.join();
// join方法也可以有参数,参数是毫秒。
// 以下代码表示 t 线程合并到 当前线程,合并时长 10 毫秒
// 阻塞当前线程 10 毫秒
//t.join(10);
// 调用这个方法,是想让当前线程受阻10秒
// 但不一定,如果在指定的阻塞时间内,t线程结束了。当前线程阻塞也会解除。
t.join(1000 * 10);
// 当前线程休眠10秒。
//Thread.sleep(1000 * 10);
// 主线程
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "==>" + i);
}
System.out.println("main over");
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "==>" + i);
}
}
}
线程优先级
/**
* 关于线程生命周期中的JVM调度:
* 1. 优先级
* 2. 线程是可以设置优先级的,优先级较高的,获得CPU时间片的总体概率高一些。
* 3. JVM采用的是抢占式调度模型。谁的优先级高,获取CPU时间片的总体概率就高。
* 4. 默认情况下,一个线程的优先级是 5.
* 5. 最低是1,最高是10.
*/
public class ThreadTest {
public static void main(String[] args) {
/*System.out.println("最低优先级:" + Thread.MIN_PRIORITY);
System.out.println("最高优先级:" + Thread.MAX_PRIORITY);
System.out.println("默认优先级:" + Thread.NORM_PRIORITY);
// 获取main线程的优先级
Thread mainThread = Thread.currentThread();
System.out.println("main线程的优先级:" + mainThread.getPriority()); // 5
// 设置优先级
mainThread.setPriority(Thread.MAX_PRIORITY);
System.out.println("main线程的优先级:" + mainThread.getPriority()); // 10*/
// 创建两个线程
Thread t1 = new MyThread();
t1.setName("t1");
Thread t2 = new MyThread();
t2.setName("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "==>" + i);
}
}
}
线程让位(yield方法)
/**
* 关于JVM的调度:
* 1. 让位
* 2. 静态方法:Thread.yield()
* 3. 让当前线程让位。
* 4. 注意:让位不会让其进入阻塞状态。只是放弃目前占有的CPU时间片,进入就绪状态,继续抢夺CPU时间片。
* 5. 只能保证大方向上的,大概率,到了某个点让位一次。
*/
public class ThreadTest {
public static void main(String[] args) {
Thread t1 = new MyThread();
t1.setName("t1");
Thread t2 = new MyThread();
t2.setName("t2");
t1.start();
t2.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
if(Thread.currentThread().getName().equals("t1") && i % 10 == 0){
System.out.println(Thread.currentThread().getName() + "让位了,此时的i下标是:" + i);
// 当前线程让位,这个当前线程一定是t1
// t1会让位一次
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "==>" + i);
}
}
}
线程安全问题
/**
* 线程安全专题:
* 1. 什么情况下需要考虑线程安全问题?
* 条件1:多线程的并发环境下
* 条件2:有共享的数据
* 条件3:共享数据涉及到修改的操作
*
* 2. 一般情况下:
* 局部变量不存在线程安全问题。(尤其是基本数据类型不存在线程安全问题【在栈中,栈不是共享的】,如果是引用数据类型,就另说了!)
* 实例变量可能存在线程安全问题。实例变量在堆中。堆是多线程共享的。
* 静态变量也可能存在线程安全问题。静态变量在堆中。堆是多线程共享的。
*
* 3. 大家找一个现实生活中的例子,来说明一下,线程安全问题:比如同时取钱!
*
* 4. 以上多线程并发对同一个账户进行取款操作的时候,有安全问题?怎么解决?
* 让线程t1和线程t2排队执行。不要并发。要排队。
* 我们把线程排队执行,叫做:线程同步机制。(t1和t2线程,t1线程在执行的时候必须等待t2线程执行到某个位置之后,t1线程才能执行。只要t1和t2之间发生了等待,就认为是同步。)
* 如果不排队,我们将其称为:线程异步机制。(t1和t2各自执行各自的,谁也不需要等对方。并发的,就认为是异步)
* 异步:效率高。但是可能存在安全隐患。
* 同步:效率低。排队了。可以保证数据的安全问题。
*
* 5. 以下程序存在安全问题。t1和t2线程同时对act一个账号进行取款操作。数据是错误的。
*/
线程同步
/**
* 使用线程同步机制,来保证多线程并发环境下的数据安全问题:
* 1. 线程同步的本质是:线程排队执行就是同步机制。
* 2. 语法格式:
* synchronized(必须是需要排队的这几个线程共享的对象){
* // 需要同步的代码
* }
*
* “必须是需要排队的这几个线程共享的对象” 这个必须选对了。
* 这个如果选错了,可能会无故增加同步的线程数量,导致效率降低。
* 3. 原理是什么?
* synchronized(obj){
* // 同步代码块
* }
* 假设obj是t1 t2两个线程共享的。
* t1和t2执行这个代码的时候,一定是有一个先抢到了CPU时间片。一定是有先后顺序的。
* 假设t1先抢到了CPU时间片。t1线程找共享对象obj的对象锁,找到之后,则占有这把锁。只要能够占有obj对象的对象锁,就有权利进入同步代码块执行代码。
* 当t1线程执行完同步代码块之后,会释放之前占有的对象锁(归还锁)。
* 同样,t2线程抢到CPU时间片之后,也开始执行,也会去找共享对象obj的对象锁,但由于t1线程占有这把锁,t2线程只能在同步代码块之外等待。
*
* 4. 注意同步代码块的范围,不要无故扩大同步的范围,同步代码块范围越小,效率越高。
*/
/**
* 在实例方法上也可以添加 synchronized 关键字:
* 1. 在实例方法上添加了synchronized关键字之后,整个方法体是一个同步代码块。
* 2. 在实例方法上添加了synchronized关键字之后,共享对象的对象锁一定是this的。
*
* 这种方式相对于之前所讲的局部同步代码块的方式要差一些:
* synchronized(共享对象){
* // 同步代码块
* }
*
* 这种方式优点:灵活
* 共享对象可以随便调整。
* 同步代码块的范围可以随便调整。
*/
synchronized面试题
/**
* 线程同步机制的面试题:分析以下程序 m2 方法在执行的时候,需要等待 m1 方法的结束吗?
* 不需要。
*/
public class ThreadTest {
public static void main(String[] args) {
MyClass mc = new MyClass();
Thread t1 = new Thread(new MyRunnable(mc));
Thread t2 = new Thread(new MyRunnable(mc));
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t2.start();
}
}
class MyRunnable implements Runnable {
private MyClass mc;
public MyRunnable(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if("t1".equals(Thread.currentThread().getName())){
mc.m1();
}
if("t2".equals(Thread.currentThread().getName())){
mc.m2();
}
}
}
class MyClass {
public synchronized void m1(){
System.out.println("m1 begin");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("m1 over");
}
public void m2(){
System.out.println("m2 begin");
System.out.println("m2 over");
}
}
/**
* 线程同步机制的面试题:分析以下程序 m2 方法在执行的时候,需要等待 m1 方法的结束吗?
* 需要
*/
public class ThreadTest {
public static void main(String[] args) {
MyClass mc = new MyClass();
Thread t1 = new Thread(new MyRunnable(mc));
Thread t2 = new Thread(new MyRunnable(mc));
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t2.start();
}
}
class MyRunnable implements Runnable {
private MyClass mc;
public MyRunnable(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if("t1".equals(Thread.currentThread().getName())){
mc.m1();
}
if("t2".equals(Thread.currentThread().getName())){
mc.m2();
}
}
}
class MyClass {
public synchronized void m1(){
System.out.println("m1 begin");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("m1 over");
}
public synchronized void m2(){
System.out.println("m2 begin");
System.out.println("m2 over");
}
}
/**
* 线程同步机制的面试题:分析以下程序 m2 方法在执行的时候,需要等待 m1 方法的结束吗?
* 不需要
*/
public class ThreadTest {
public static void main(String[] args) {
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
Thread t1 = new Thread(new MyRunnable(mc1));
Thread t2 = new Thread(new MyRunnable(mc2));
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t2.start();
}
}
class MyRunnable implements Runnable {
private MyClass mc;
public MyRunnable(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if("t1".equals(Thread.currentThread().getName())){
mc.m1();
}
if("t2".equals(Thread.currentThread().getName())){
mc.m2();
}
}
}
class MyClass {
public synchronized void m1(){
System.out.println("m1 begin");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("m1 over");
}
public synchronized void m2(){
System.out.println("m2 begin");
System.out.println("m2 over");
}
}
/**
* 线程同步机制的面试题:分析以下程序 m2 方法在执行的时候,需要等待 m1 方法的结束吗?
* 需要等待。
*
* 在静态方法上添加synchronized之后,线程会占有类锁。
* 类锁是,对于一个类来说,只有一把锁。不管创建了多少个对象,类锁只有一把。
*
* 静态方法上添加synchronized,实际上是为了保证静态变量的安全。
* 实例方法上添加synchronized,实际上是为了保证实例变量的安全。
*/
public class ThreadTest {
public static void main(String[] args) {
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
Thread t1 = new Thread(new MyRunnable(mc1));
Thread t2 = new Thread(new MyRunnable(mc2));
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t2.start();
}
}
class MyRunnable implements Runnable {
private MyClass mc;
public MyRunnable(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if("t1".equals(Thread.currentThread().getName())){
mc.m1();
}
if("t2".equals(Thread.currentThread().getName())){
mc.m2();
}
}
}
class MyClass {
public static synchronized void m1(){
System.out.println("m1 begin");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("m1 over");
}
public static synchronized void m2(){
System.out.println("m2 begin");
System.out.println("m2 over");
}
}
死锁
/**
* 写一个死锁。
*/
public class ThreadTest {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new Thread(new MyRunnable(o1, o2));
Thread t2 = new Thread(new MyRunnable(o1, o2));
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class MyRunnable implements Runnable {
private Object o1;
private Object o2;
public MyRunnable(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
if("t1".equals(Thread.currentThread().getName())){
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (o2){
}
}
}else if("t2".equals(Thread.currentThread().getName())){
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (o1){
}
}
}
}
}
模拟多线程卖票
/**
* 模拟三个窗口卖票。
*/
public class SellTicket {
public static void main(String[] args) {
// 创建一个对象,这样让多个线程共享同一个对象
MyRunnable mr = new MyRunnable();
// 创建三个线程,模拟三个窗口
Thread t1 = new Thread(mr);
t1.setName("1");
Thread t2 = new Thread(mr);
t2.setName("2");
Thread t3 = new Thread(mr);
t3.setName("3");
t1.start();
t2.start();
t3.start();
}
}
class MyRunnable implements Runnable {
// 实例变量(多线程共享)
private int ticketTotal = 100;
@Override
public void run() {
// 实现核心业务:卖票
while(true){
// synchronized 是线程同步机制。
// synchronized 又被称为互斥锁!
synchronized (this){
if(ticketTotal <= 0){
System.out.println("票已售完...");
break; // 停止售票
}
// 票还有(ticketTotal > 0)
// 一般出票都需要一个时长,模拟一下
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 出票了
System.out.println("窗口"+ Thread.currentThread().getName() +"售出一张票,还剩" + (--ticketTotal) + "张票");
}
}
}
}
线程通信
线程通信涉及到三个方法:
* wait()、notify()、notifyAll()
- wait(): 线程执行该方法后,进入等待状态,并且释放对象锁。
- notify(): 唤醒优先级最高的那个等待状态的线程。【优先级相同的,随机选一个】。被唤醒的线程从当初wait()的位置继续执行。
- notifyAll(): 唤醒所有wait()的线程
- 需要注意的:
-
- 以上三个方法在使用时,必须在同步代码块中或同步方法中。
- 调用这三个方法的对象必须是共享的锁对象。
- 这三个方法都是Object类的方法。
- wait()和sleep的区别?
-
- 相同点:都会阻塞。
- 不同点:
-
-
- wait是Object类的实例方法。sleep是Thread的静态方法。
- wait只能用在同步代码块或同步方法中。sleep随意。
- wait方法执行会释放对象锁。sleep不会。
- wait结束时机是notify唤醒,或达到指定时间。sleep结束时机是到达指定时间。
-
/**
* 1. 内容是关于:线程通信。
*
* 2. 线程通信涉及到三个方法:
* wait()、notify()、notifyAll()
*
* 3. 以上三个方法都是Object类的方法。
*
* 4. 其中wait()方法重载了三个:
* wait():调用此方法,线程进入“等待状态”
* wait(毫秒):调用此方法,线程进入“超时等待状态”
* wait(毫秒, 纳秒):调用此方法,线程进入“超时等待状态”
*
* 5. 调用wait方法和notify相关方法的,不是通过线程对象去调用,而是通过共享对象去调用。
*
* 6. 例如调用了:obj.wait(),什么效果?
* obj是多线程共享的对象。
* 当调用了obj.wait()之后,在obj对象上活跃的所有线程进入无期限等待。直到调用了该共享对象的 obj.notify() 方法进行了唤醒。
* 而且唤醒后,会接着上一次调用wait()方法的位置继续向下执行。
*
* 7. obj.wait()方法调用之后,会释放之前占用的对象锁。
*
* 8. 关于notify和notifyAll方法:
* 共享对象.notify(); 调用之后效果是什么?唤醒优先级最高的等待线程。如果优先级一样,则随机唤醒一个。
* 共享对象.notifyAll(); 调用之后效果是什么?唤醒所有在该共享对象上等待的线程。
*
class MyRunnable implements Runnable {
// 实例变量,多线程共享的。
private int count = 0;
private Object obj = new Object();
@Override
public void run() {
while(true){
//synchronized (this){
synchronized (obj) {
// 记得唤醒t1线程
// t2线程执行过程中把t1唤醒了。但是由于t2仍然占用对象锁,所以即使t1醒了,也不会往下执行。
//this.notify();
obj.notify();
if(count >= 100) break;
// 模拟延迟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 程序执行到这里count一定是小于100
System.out.println(Thread.currentThread().getName() + "-->" + (++count));
try {
// 让其中一个线程等待,这个等待的线程可能是t1,也可能是t2
// 假设是t1线程等待。
// t1线程进入无期限的等待,并且等待的时候,不占用对象锁。
//this.wait();
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
三个线程交替输出
public class ThreadTest {
// 共享对象(t1 t2 t3线程共享的一个对象,都去争夺这一把锁)
private static final Object lock = new Object();
// 给一个初始值,这个初始值表示第一次输出的时候,t1先输出。
private static boolean t1Output = true;
private static boolean t2Output = false;
private static boolean t3Output = false;
public static void main(String[] args) {
// 创建三个线程
// t1线程:负责输出A
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
for (int i = 0; i < 10; i++) {
while(!t1Output){ // 只要不是t1线程输出
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 程序到这里说明:该t1线程输出了,并且t1线程被唤醒了。
System.out.println(Thread.currentThread().getName() + " ---> A");
// 该布尔标记的值
t1Output = false;
t2Output = true;
t3Output = false;
// 唤醒所有线程
lock.notifyAll();
}
}
}
}).start();
// t2线程:负责输出B
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
for (int i = 0; i < 10; i++) {
while(!t2Output){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + " ---> B");
// 该布尔标记的值
t1Output = false;
t2Output = false;
t3Output = true;
// 唤醒所有线程
lock.notifyAll();
}
}
}
}).start();
// t3线程:负责输出C
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
for (int i = 0; i < 10; i++) {
while(!t3Output){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + " ---> C");
// 该布尔标记的值
t1Output = true;
t2Output = false;
t3Output = false;
// 唤醒所有线程
lock.notifyAll();
}
}
}
}).start();
}
}
懒汉式单例模式
class SingletonTest {
// 静态变量
private static Singleton s1;
private static Singleton s2;
public static void main(String[] args) {
// 获取某个类。这是反射机制中的内容。
/*Class stringClass = String.class;
Class singletonClass = Singleton.class;
Class dateClass = java.util.Date.class;*/
// 创建线程对象t1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
s1 = Singleton.getSingleton();
}
});
// 创建线程对象t2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
s2 = Singleton.getSingleton();
}
});
// 启动线程
t1.start();
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 判断这两个Singleton对象是否一样。
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}
}
/**
* 懒汉式单例模式
*/
public class Singleton {
private static Singleton singleton;
private Singleton() {
System.out.println("构造方法执行了!");
}
// 非线程安全的。
/*public static Singleton getSingleton() {
if (singleton == null) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
singleton = new Singleton();
}
return singleton;
}*/
// 线程安全的:第一种方案(同步方法),找类锁。
/*public static synchronized Singleton getSingleton() {
if (singleton == null) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
singleton = new Singleton();
}
return singleton;
}*/
// 线程安全的:第二种方案(同步代码块),找的类锁
/*public static Singleton getSingleton() {
// 这里有一个知识点是反射机制中的内容。可以获取某个类。
synchronized (Singleton.class){
if (singleton == null) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
singleton = new Singleton();
}
}
return singleton;
}*/
// 线程安全的:这个方案对上一个方案进行优化,提升效率。
/*public static Singleton getSingleton() {
if(singleton == null){
synchronized (Singleton.class){
if (singleton == null) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
singleton = new Singleton();
}
}
}
return singleton;
}*/
// 使用Lock来实现线程安全
// Lock是接口,从JDK5开始引入的。
// Lock接口下有一个实现类:可重入锁(ReentrantLock)
// 注意:要想使用ReentrantLock达到线程安全,假设要让t1 t2 t3线程同步,就需要让t1 t2 t3共享同一个lock。
// Lock 和 synchronized 哪个好?Lock更好。为什么?因为更加灵活。
private static final ReentrantLock lock = new ReentrantLock();
public static Singleton getSingleton() {
if(singleton == null){
try {
// 加锁
lock.lock();
if (singleton == null) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
singleton = new Singleton();
}
} finally {
// 解锁(需要100%保证解锁,怎么办?finally)
lock.unlock();
}
}
return singleton;
}
}