线程
概念
线程是进程中的单个顺序执行流,是一条执行路径。一个进程如果只有一条执行路径,则成为单线程程序;而如果有多条执行路径,则成为多线程程序。
CPU分时调度
时间片,即CPU分配给各个程序的时间,每个进程被分配一个时间段,称作它的时间片。即该进程允许运行的时间,使各个程序从表面上看是同时进行的。
如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程,将当前进程挂起。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换,而不会造成CPU资源浪费。当又切换到之前执行的进程,把现场恢复,继续执行。
在宏观上:我们可以同时打开多个应用程序,每个程序并行,同时运行。
在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。多核提高了并发能力。
单核CPU和多核CPU(了解):
单核CPU同时只能干一件事情,当启动多个程序时,CPU快速切换轮流处理每个程序,但如果CPU不够强劲,同时排队等待处理的任务太多,就会感觉系统会有延时、反应慢、卡顿等,甚至某一个程序在处理时出现错误,无法响应了,会造成后面排队的其他任务只能等待。
多核CPU是在基板上集成多个单核CPU+任务分配系统,两个核心同时处理两份任务,相比单核执行速度更快,有利于同时运行多个程序,不容易造成卡顿,更流畅!
Java程序的运行过程:
-
通过eclipse(java命令)运行一个Java程序,java命令会启动JVM(java虚拟机),等于启动了一个应用程序,也就是启动了一个进程。
-
该进程会自动启动一个“主线程”,然后主线程去调用某个类的 main 方法,所以 main 方法运行在主线程中。在此之前的所有程序代码都是按照顺序依次调用的,都是单线程的。
如果希望程序中实现多段程序代码交替执行的效果,则需要创建多线程程序。
为什么要使用多线程?
- 单线程程序执行时都只有一条顺序执行流,如果程序执行某行代码时遇到了阻塞(比如:抛异常),那么程序的运行将会停滞在这一行,其他代码将会无法执行!
- 这就像去银行办理业务,只有1个业务窗口(单线程),所有的客户都需要在一个窗口排队办理业务,如果业务员在为某一个客户办理业务时,花费了很长时间,那么将会导致后面的客户等待很长时间,这样处理业务的效率也是非常低的。
- 但如果银行为了提高效率,同时开放了5个窗口(多线程),客户可以分布在这5个窗口分别办理业务,即使某一个窗口在为个别客户办理业务时花费了很长时间,但不影响其他窗口办理业务的进度。
多线程理解起来其实非常简单:
- 单线程的程序只有一个顺序执行流。
- 多线程的程序则可以包括多个顺序执行流,多个顺序流之间互不干扰。
[并行]和[并发]的区别: (这是两个概念)
1)并行执行指在同一时刻,有多条指令在多个处理器上同时执行;
2)并发执行指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
多线程的特性
随机性
多线程的程序在执行时,在某一时间点具体执行哪个线程是不确定的,可以确定的是某一个时间点只有一个线程在执行(单核CPU)。
虽然我们感觉这些线程像是在同时运行,实际上是因为CPU在快速切换轮流执行这些线程,由于切换速度是纳秒级别的,所以我们感觉不到。
如何实现多线程
由于线程是依赖进程存在的,因此首先需要创建一个进程,但进程是操作系统创建的,而Java程序是不能直接调用系统功能的。但Java可以去调用C或C++写好的程序去创建进程从而实现多线程程序。
在Java中要想实现多线程操作有两种方式,一种是继承Thread类,另一种是实现Runnable接口。接下来针对这两种创建多线程的方式分别进行讲解。
实现多线程1: 继承Thread类
Thread类概述
Thread类是在java.lang包中定义的类
JavaSE规范中规定,一个类只要继承了Thread类,此类就是多线程的子类。
在Thread子类中,必须重写该类的run()方法,此方法为线程的主体。
通过继承Thread类创建线程
接下来通过案例演示<通过继承Thread类的方式实现多线程>
下面创建线程,并测试主线程和创建的新线程(子线程)交替执行的效果
代码实现如下:
package Part05Thread;
/**
* 多线程
* 多线程可以并发执行多个任务
* 线程的第一种创建方式:
* 1:首先继承Thread线程类
* 2:然后重写run方法
* 3:最后在run方法中定义需要并发执行的任务代码
* 这种线程的创建方式的优点:
* 结构简单,便于匿名内部类的创建
* 这种线程的创建方式的缺点:
* 1:由于java是单继承(一个类只能同一时间继承一个父类),也就导致了我们继承了Thread,
* 就无法再继承别的类了,实际开发中会造成很多不便.
* 2:定义线程时,重写了run方法,直接将任务定义在了线程中,导致线程与任务存在必然的耦合关系,
* 不利于线程的重用
*/
public class ThreadDemo1 {
public static void main(String[] args) {
//启动线程
//1.创建线程对象
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
//2.调用启动线程的方法start
/*
* 注意,启动线程时,需要调用的是start方法,不是调用run方法,
* 当调用start方法时,线程便会纳入线程调度器,统一被管理,
* 一旦它被分配到了CPU的时间片,就会开始自动执行run方法中的代码片段
*/
t1.start();
t2.start();
}
}
//创建线程步骤1:继承Thread线程类
class MyThread1 extends Thread{
//创建线程步骤2:重写run方法
@Override//此注解是标识当前方法是重写方法
public void run() {
//创建线程步骤3:run方法中书写该线程运行时,要执行的代码片段
//fori 快速生成for循环代码模板
for (int i = 0; i < 1000; i++) {
System.out.println("你是谁啊?");
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("开门,我是查水表的!");
}
}
}
从上面的运行结果可以看出,main方法(主线程)和run方法(子线程)中的两个for循环中的输出语句交替执行了,说明通过集成Thread类实现了多线程。
(如果没有测试出主线程和子线程交替执行的效果,可以多测试几次!)
实现多线程2: 实现Runnable接口
Runnable接口概述
通过继承Thread类实现了多线程,但是这种方式有一定的局限性。因为Java中只支持单继承,一个类一旦继承了某个父类就无法再继承Thread类,例如猫类Cat继承了动物类Animal,就无法通过继承Thread类实现多线程。
为了克服这种弊端,在Thread类中提供了两个构造方法:
public Thread(Runnable target)
public Thread(Runnable target,String name)
这两个构造方法都可以接收Runnable的子类实例对象,这样创建的线程将调用实现了Runnable接口类中的run()方法作为运行代码,而不需要调用Thread类的run()方法,所以就可以依靠Runnable接口的实现类启动多线程。
通过实现Runnable接口实现多线程
接下来通过案例演示<通过实现Runnable接口的方式实现多线程>
下面创建线程,并测试主线程和创建的新线程(子线程)交替执行的效果
代码实现如下:
package Part05Thread;
/**
* 第二种创建线程的方式:
* 1:首先实现Runnable接口
* 2:然后实现run方法
* 3:将线程要执行的代码片段写在run方法中
* 优点:解决了第一种方式的缺点
* 缺点:写起来麻烦一点
*/
public class ThreadDemo2 {
public static void main(String[] args) {
//启动线程
//1.实例化线程要执行的任务
MyRunnable1 r1 = new MyRunnable1();
MyRunnable2 r2 = new MyRunnable2();
//2.创建线程对象,来执行线程任务
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
//创建线程的步骤1:实现Runnable接口
class MyRunnable1 implements Runnable{
//创建线程的步骤2:实实现run方法
@Override
public void run() {
//创建线程的步骤3:写入线程执行的代码片段
for (int i = 0; i < 1000; i++) {
System.out.println("hello!姐~~~");
}
}
}
class MyRunnable2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("来了~老弟~~~");
}
}
}
从上面的运行结果可以看出,main方法(主线程)和run方法(子线程)中的两个for循环中的输出语句交替执行了,说明实现Runnable接口同样也实现了多线程。
简化写法
package Part05Thread;
/**
* 使用匿名内部类完成两种线程的创建方式
*/
public class ThreadDemo3 {
public static void main(String[] args) {
//直接继承Thread重写run方法的形式
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("你是谁啊?");
}
}
};
t1.start();
//实现Runnable接口重写run方法单独定义任务的形式
Runnable r1 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("hello!姐~~~");
}
}
};
Thread t2 = new Thread(r1);
t2.start();
//lambda表达式简化写法:适用于接口中只定义了一个方法
Runnable r2 = () -> {
for (int i = 0; i < 1000; i++) {
System.out.println("hello!姐~~~");
}
};
Thread t3 = new Thread(r2);
t3.start();
}
}
Thread的常用方法和总结
CurrentThreadDemo
package Part05Thread;
/**
* java中所有的代码都是靠线程执行的,main方法也是通过虚拟机创建的主线程执行的,
* 这个主线程名字叫做'main',但是这个主线程和我们自己创建的线程并没有任何区别
* Thread提供了一个静态的方法:
* static Thread currentThread()
* 该方法可以获取运行该方法的线程
*/
public class CurrentThreadDemo {
public static void main(String[] args) {
Thread main = Thread.currentThread();
System.out.println("主线程:"+main);//Thread[main,5,main]
Thread t1 = new Thread("无敌霸王机");
System.out.println(t1);
}
}
ThreadInfoDemo
package Part05Thread;
/**
* 获取线程相关信息的一组方法
*/
public class ThreadInfoDemo {
public static void main(String[] args) {
Thread main = Thread.currentThread();//获取主线程
String name = main.getName();//获取线程的名字
System.out.println("线程名是:"+name);
long id = main.getId();
System.out.println("线程的唯一标识:"+id);
int priority = main.getPriority();//获取线程的优先级
System.out.println("线程的优先级:"+priority);
boolean alive = main.isAlive();
System.out.println("线程是否还存活:"+alive);
boolean daemon = main.isDaemon();
System.out.println("是否为守护线程:"+daemon);
boolean interrupted = main.isInterrupted();
System.out.println("线程是否被中断:"+interrupted);
}
}
PriorityDemo
package Part05Thread;
/**
* 线程的优先级
* 线程有10个优先级,分别对应的整数是1-10,其中1是最低的优先级,10是最高的优先级
* 5是默认的优先级
* 线程不能主动的索取时间片,只能被动的等待线程调度器分配给该线程的时间片
* 线程调度器会尽可能的均匀的将时间片分配给每个线程
* 修改线程的优先级会最大程度的改善线程获取时间片的次数,原则上是优先级越高,
* 获取时间片的概率就越大
*/
public class PriorityDemo {
public static void main(String[] args) {
Thread min = new Thread(){
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("我是优先级最小的妃子");
}
}
};
Thread norm = new Thread(){
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("我是优先级普通的妃子");
}
}
};
Thread max = new Thread(){
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("我是优先级最高的妃子");
}
}
};
//min.setPriority(1);//最小值就是1,表示最低优先级
//max.setPriority(10);//最大值就是10,表示最高优先级
max.setPriority(Thread.MAX_PRIORITY);
min.setPriority(Thread.MIN_PRIORITY);
min.start();
norm.start();
max.start();
}
}
进程
什么是进程
进程是操作系统中运行得到一个任务(一个应用程序运行在一个进程中).
进程(process)是一块包含了某些资源的内存区域.操作同利用进程把它的工作划分为一些功能单元.
进程中所包含的一个或者多个执行单元称为线程(thread).进程拥有一个私有的虚拟地址空间,该空间仅能被他所包含的线程访问
线程只能归属于一个进程并且他只能访问该进程所拥有的资源.当操作系统创建一个进程后,该进程会自动申请一个主线程或者首要线程的线程
线程状态
概述
1)新建状态(New):当一个线程对象被创建后,线程就处于新建状态。在新建状态中的线程对象从严格意义上看还只是一个普通的对象,还不是一个独立的线程,不会被线程调度程序调度。新建状态是线程生命周期的第一个状态。
例如: Thread t = new MyThread();
2)就绪状态(Runnable):处于新建状态中的线程被调用start()方法就会进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,但并不是说执行了start()方法线程就会立即执行;
另外,在等待/阻塞状态中的线程,被解除等待和阻塞后将不直接进入运行状态,而是首先进入就绪状态
3)运行状态(Running):处于就绪状态中的线程一旦被系统选中,使线程获取了 CPU 时间,就会进入运行状态。
线程在运行状态下随时都可能被调度程序调度回就绪状态。在运行状态下还可以让线程进入到等待/阻塞状态。
在通常的单核CPU中,在同一时刻只有一个线程处于运行状态。在多核的CPU中,就可能两个线程或更多的线程同时处于运行状态,这也是多核CPU运行速度快的原因。
注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,必须先处于就绪状态中;
4)阻塞状态(Blocked):根据阻塞产生的原因不同,阻塞状态又可以分为三种
a)等待阻塞:运行状态中的线程执行wait()方法,使当前线程进入到等待阻塞状态;
b)锁阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),线程会进入同步阻塞状态;
c)其他阻塞:通过调用线程的sleep(),suspend(), join(), 或发出了I/O请求时等,线程会进入到阻塞状态。当sleep()睡眠结束、调用resume(),?join()等待的线程终止或者超时、或I/O处理完毕时,线程重新转入就绪状态。
5)死亡状态(Dead):当线程中的run方法执行结束后,或者程序发生异常终止运行后,线程会进入死亡状态。处于死亡状态的线程不能再使用 start 方法启动线程。
SleepDemo
package Part05Thread;
import java.util.Scanner;
/**
* 线程提供了一个静态方法:
* static void sleep(long ms);
* 当一个线程调用sleep后就会进入阻塞状态指定的毫秒,
* 超时后会自动回到就绪状态再次开始并发运行
*/
public class SleepDemo {
public static void main(String[] args) {
System.out.println("程序开始了!");
/*
* 倒计时程序,程序启动后,会输入一个数字,
* 从该数字进行每秒递减.到0就输出时间到
*/
try {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个数字!");
int num = scanner.nextInt();
for (int i = num; i >0 ; i--) {
System.out.println(i);
Thread.sleep(1000);
}
System.out.println("时间到!");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("程序结束啦!");
}
}
SleepDemo2
package Part05Thread;
/**
* sleep方法要求必须处理中断
* 当一个线程调用sleep方法处于睡眠阻塞状态时,此时该线程调用interrupt()方法,就会
* 立即中断该睡眠阻塞,并抛出中断异常
*/
public class SleepDemo2 {
public static void main(String[] args) {
Thread lin = new Thread() {
@Override
public void run() {
System.out.println("林:刚美完容,睡一会吧~~~");
try {
Thread.sleep(500000);
System.out.println("林:睡醒了~~~");
} catch (InterruptedException e) {
System.out.println("林:干嘛呢!干嘛呢!干嘛呢!都破了相了!");
}
}
};
Thread huang = new Thread() {
@Override
public void run() {
System.out.println("黄:大锤80,小锤40,开始砸墙!");
for (int i = 0; i < 5; i++) {
System.out.println("黄:80!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println("咣当!");
System.out.println("黄:大哥!搞定!");
lin.interrupt();//强制唤醒lin线程的睡眠阻塞
}
};
lin.start();
huang.start();
}
}
DaemonThreadDemo
package Part05Thread;
/**
* 守护线程
* java将线程归为两类:用户线程和守护线程,也称之为叫做前台线程和后台线程
* 守护线程就是通过普通的用户线程通过调用setDaemon(true)设置为守护线程
* 因此守护线程和用户线程在创建和使用上并没没有区别.但是主要区别在于:
* 进程的结束:当一个java的进程中所有的用户线程结束,进程结束了,此时会将所有的
* 守护线程全部杀死
*/
public class DaemonThreadDemo {
public static void main(String[] args) {
Thread rose = new Thread(){
@Override
public void run() {
for (int i=0;i<5;i++){
System.out.println("rose:let me go!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("rose:啊啊啊啊啊啊啊!!!!");
System.out.println("噗通~噗通~");
}
};
Thread jack = new Thread(){
@Override
public void run() {
while (true){
System.out.println("jack:you jump!i jump!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
rose.start();
//将jack线程设置为守护线程,将进程结束后,jack线程会被杀死
jack.setDaemon(true);
jack.start();
while (true);//当主线程不结束,进程也不会结束
}
}
线程的同步和异步
JoinDemo
package Part05Thread;
/**
* 线程提供的方法:join可以协调线程的同步运行
* 多线程时并发运行的,本身是一种异步的运行状态
* 异步运行:各自执行各自的
* 同步运行:多个线程执行是存在了先后顺序的
*/
public class JoinDemo {
static boolean isFinish = false;//表示图片是否下载完毕,默认是没下载完毕
public static void main(String[] args) {
//创建下载线程
Thread download = new Thread(){
@Override
public void run() {
System.out.println("down:开始下载图片...");
for (int i = 1; i <= 100; i++) {
System.out.println("down:下载了"+i+"%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("down:图片下载完毕!");
isFinish = true;//图片下载完毕
}
};
//创建显示线程
Thread show = new Thread(){
@Override
public void run() {
System.out.println("show:开始显示文字");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("show:显示文字完毕!");
try {
//join是加入的意思,将show线程阻塞,知道download线程执行完毕(图片下载完毕)
//才可以解除show线程的阻塞状态
download.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("show:开始显示图片...");
if (!isFinish){//true=!isFinish isFinish是false
throw new RuntimeException("图片加载失败!");
}
System.out.println("show:图片显示完毕!");
}
};
download.start();
show.start();
}
}
同步锁
同步方法
同步方法概述
除了可以将需要的代码设置成同步代码块以外,也可以使用synchronized关键字将一个方法修饰成同步方法,它能实现和同步代码块同样的功能。
具体语法格式如下:
权限修饰符 synchronized 返回值类型/void 方法名([参数1,...]){
需要同步的代码;
}
被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕后,其他线程才有机会执行该方法。
需要注意的是,同步方法的锁是当前调用该方法的对象,也就是this指向的对象。
同步方法的使用
下面使用同步方法解决上面的多线程售票程序中的线程安全问题
需要注意的是:
-
将有可能发生线程安全问题的方法使用synchronized修饰,同一时间只允许一个线程进入同步方法中
-
synchronized方法的锁对象是当前调用该方法的对象,也就是this指向的对象。
如果当前方法是非静态方法,this表示的是调用当前方法的对象
如果当前方法的静态方法,this表示的是当前类。
示例1: SyncDemo1
package Part05Thread;
/**
* @author 老安
* @data 2022/5/16 19:50
* 多线程并发安全问题
* 当多个线程并发操作同一临界资源时,由于线程的切换不确定,
* 导致操作顺序出现混乱而引发的各种逻辑错误
* 临界资源:操作该资源的完整的过程同一时刻只能由单线程
*/
public class SyncDemo1 {
public static void main(String[] args) {
Table table = new Table();
Thread t1 = new Thread(){
@Override
public void run() {
while (true){//一直抢
int bean = table.getBean();
Thread.yield();
//getName()获取当前执行的线程的名字
//Thread.currentThread.getName()
System.out.println(getName()+"抢了1颗豆子,桌子上还剩:"+bean+"豆子!");
}
}
};
Thread t2 = new Thread(){
@Override
public void run() {
while (true){//一直抢
int bean = table.getBean();
Thread.yield();
System.out.println(getName()+"抢了1颗豆子,桌子上还剩:"+bean+"豆子!");
}
}
};
t1.start();
t2.start();
}
}
//模拟桌子
class Table{
//桌子上有20个豆子
private int beans = 20;
//对外提供取豆子的方法
/*
* 当一个方法使用关键字synchronized后,该方法会被称为同步方法:
* 即多个线程不能同时执行该方法,也就是线程排队执行
* 将多线程并发操作改为同步的状态,有先后顺序的排队执行可以有效解决线程
* 并发安全问题
*/
public synchronized int getBean(){
//如果桌子没有豆子了,就对外抛异常
if (beans <= 0){
throw new RuntimeException("桌子上没有豆子了!");
}
Thread.yield();//礼让线程,让运行的线程直接变为就绪状态
//每次抢一颗豆子,桌子上的豆子数减1
return beans--;
}
}
同步代码块
同步代码块概述
同步是指多个操作在同一个时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。
Java为线程的同步操作提供了synchronized关键字,使用该关键字修饰的代码块被称为同步代码块。
其语法格式如下:
synchronized( 同步对象 ){
需要同步的代码;
}
注意: 在使用同步代码块时必须指定一个需要同步的对象,也称为锁对象,这里的锁对象可以是任意对象。但多个线程必须使用同一个锁对象。
同步代码块的使用
下面使用同步代码块解决上面的多线程售票程序中的线程安全问题
需要注意的是:
-
将有可能发生线程安全问题的代码包含在同步代码块中,同一时间只允许一个线程进入同步代码块中
-
synchronized代码块中的锁对象可以是任意对象,但必须只能是一个锁。
若使用this作为锁对象,需保证多个线程执行时,this指向的是同一个对象
示例1:
package Part05Thread;
/**
* @author 老安
* @data 2022/5/16 21:00
* 同步块的应用
* 有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率
* 语法:
* synchronized(同步监视器对象){
* 需要多个线程同步执行的代码片段
* }
*/
public class SyncDemo2 {
public static void main(String[] args) {
Shop shop = new Shop();
//Shop shop1 = new Shop();
Thread t1 = new Thread("邓启太"){
public void run() {
shop.buy();
}
};
Thread t2 = new Thread("罗泽彬"){
public void run() {
shop.buy();
}
};
t1.start();
t2.start();
}
}
//定义一个商店类
class Shop{
public void buy(){//定义卖衣服的方法
try {
Thread t = Thread.currentThread();//获取执行buy方法的线程对象
System.out.println(t.getName()+"正在挑衣服...");
Thread.sleep(5000);//5秒时间挑衣服
/*
* 同步块在使用时,需要注意:
* 要在"()"中添加指定的同步监视器对象,该对象可以是任何的引用类型,
* 只要保证多个需要同步执行该代码块的线程看到的这个对象是"同一个"即可
* 实际开发中也是结合实际情况选择的
*/
synchronized (this){
//synchronized (new Object()){
System.out.println(t.getName()+"正在试衣服...");
Thread.sleep(5000);//5秒时间试衣服
}
System.out.println(t.getName()+"结账离开!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
静态同步方法
示例1: SyncDemo3
package Part05Thread;
/**
* @author 老安
* @data 2022/5/16 21:36
* 静态方法锁
* 如果我们在静态方法上使用了synchronized关键字,该方法
* 就一定具有同步效果
*/
public class SyncDemo3 {
public static void main(String[] args) {
Boo b1 = new Boo();
Boo b2 = new Boo();
Thread t1 = new Thread() {
@Override
public void run() {
b1.dosome();//静态方法可以类名.方法名
}
};
Thread t2 = new Thread() {
@Override
public void run() {
b2.dosome();//静态方法可以类名.方法名
}
};
t1.start();
t2.start();
}
}
class Boo {
//同步方法其实也有同步监视器,并且固定死了就是this
//this指的是当前静态方法所属的类的类对象,即:Class实例
//JVM加载Boo类,会同时实例化一个Class的实例与当前类对应
//JVM内部加载的每个类都有且仅有一个与之对应的Class类实例
//结论:静态方法加锁,一定同步
//public synchronized static void dosome(){
public static void dosome() {
Thread t = Thread.currentThread();
synchronized (Boo.class){//Boo.class 引用一个类的Class对象 类名.class
System.out.println(t.getName() + ":正在执行dosome方法...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(t.getName() + ":执行dosome方法完毕!");
}
}
互斥锁
示例:SyncDemo4
package Part05Thread;
/**
* 互斥锁
*/
public class SyncDemo4{
public static void main(String[] args) {
Foo foo = new Foo();
Thread t1 = new Thread(){
@Override
public void run() {
foo.methodA();
}
};
Thread t2 = new Thread(){
@Override
public void run() {
foo.methodB();
}
};
t1.start();
t2.start();
}
}
class Foo{
//同步监视器对象是this foo对象
public synchronized void methodA(){
Thread t = Thread.currentThread();//运行A方法的线程
System.out.println(t.getName()+":正在执行A方法...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getName()+":执行A方法完毕!");
}
public synchronized void methodB(){
Thread t = Thread.currentThread();//运行A方法的线程
System.out.println(t.getName()+":正在执行B方法...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getName()+":执行B方法完毕!");
}
}
死锁
示例:DeadLockDemo
package Part05Thread;
/**
* 死锁
* 当多个线程都各自持有一个锁的同时等待对方释放锁,就会形成
* 一种僵持状态,这种状态我们称之为死锁现象
*/
public class DeadLockDemo {
static Object chopsticks = new Object();//筷子
static Object spoon = new Object();//勺
public static void main(String[] args) {
//创建北方人线程,先吃饭,后喝汤
/*Thread np = new Thread(){
public void run() {
try {
System.out.println("北方人开始吃饭了...");
synchronized (chopsticks){//抢筷子
System.out.println("北方人拿起了筷子,开始吃饭...");
Thread.sleep(5000);
System.out.println("北方人吃完了饭,去拿勺...");
synchronized (spoon){//抢勺
System.out.println("北方人拿起了勺,开始喝汤...");
Thread.sleep(5000);
System.out.println("北方人喝完了汤...");
}
System.out.println("北方人放下了勺!");
}
System.out.println("北方人放下了筷子!");
}catch (Exception e){}
}
};*/
//ctrl+alt+l 快速整理代码格式
Thread np = new Thread() {
public void run() {
try {
System.out.println("北方人开始吃饭了...");
synchronized (chopsticks) {//抢筷子
System.out.println("北方人拿起了筷子,开始吃饭...");
Thread.sleep(5000);
System.out.println("北方人吃完了饭");
}
System.out.println("放下了筷子,去拿勺...");
synchronized (spoon) {//抢勺
System.out.println("北方人拿起了勺,开始喝汤...");
Thread.sleep(5000);
System.out.println("北方人喝完了汤...");
}
System.out.println("北方人放下了勺!");
} catch (Exception e) {
}
}
};
//创建南方人线程,先喝汤,后吃饭
Thread sp = new Thread() {
public void run() {
try {
System.out.println("南方人开始吃饭了...");
synchronized (spoon) {//抢勺
System.out.println("南方人拿起了勺,开始喝汤...");
Thread.sleep(5000);
System.out.println("南方人喝完了汤...");
}
System.out.println("南方人放下勺,去拿筷子...");
synchronized (chopsticks) {//抢筷子
System.out.println("南方人拿起了筷子,开始吃饭...");
Thread.sleep(5000);
System.out.println("南方人吃完了饭...");
}
System.out.println("南方人放下了筷子!");
} catch (Exception e) {
}
}
};
np.start();
sp.start();
}
}
线程池
ThreadPoolDemo