一、进程与线程
1)进程
程序:静态的,就是存放在磁盘里的可执行文件,是一系列指令的集合
进程:进入到内存中执行的应用程序,是系统运行的基本单位,有独立的运行空间
具体点说:一个程序如果被CPU多次读取到内存中,变成多个独立的进程。将程序运行起来,就称之为进程。进程是执行程序的一次性过程,他是动态的概念。进程存在生命周期,也就是说程序随着程序的终止而销毁。进程之间是通过TCP/IP端口实现交互的。
一句话总结:
一个应用程序(一个进程就是一个软件),一个程序至少包含一个进程,一个进程中至少包含一条线程
【进程的三个状态】
进程在运行的过程中不断的改变其运行状态。通常一个运行的进程必须有三个状态,就绪态 、 运行态 、 阻塞态 。就绪态:当进程获取出 CPU 外所有的资源后,只要再获得 CPU 就能执行程序,这时的状态叫做就绪态。在一个系统中处于就绪态的进程会有多个,通常把这些排成一个队列,这个就叫就绪队列。运行态:当进程已获得 CPU 操作权限,正在运行,这个时间就是运行态。在单核系统中,同一个时间只能有一个运行态,多核系统中,会有多个运行态。阻塞态:正在执行的进程,等待某个事件而无法继续运行时,便被系统剥夺了 CPU 的操作权限,这时就 是阻塞态。引起阻塞的原因有很多,比如:等待 I/O 操作、被更高的优先级的进程剥夺了 CPU 权限等。
2)线程
是系统分配资源的基本单位,一个进程可以包含多个线程,堆空间和方法区共享,但是每个线程的栈空间和程序计数器是独立的,同样的线程消耗的资源也是小于进程的。线程是CPU 调度和执行的最小单位。也可以讲,就是说,是进程中的执行单元,在进程下进行,作用是负责当前进程中的程序运行,一个进程中是可以有多个线程的,这样的应用程序称之为多线程,很多线程都是模拟出来的,真正的多线程是 指有多个 CPU 即,多核,如服务器,如果是模拟出来的多线程,即一个 CPU 的情况下,在同一个时间 点, CPU 只能执行一个代码,因为切换很快,所以就有同时执行的错觉
对于java程序来说,当在DOS命令窗口中输入:
java HelloWorld 回车之后。会先启动 JVM ,而 JVM 就是一个进程。JVM 再启动一个主线程调用 main 方法( main 方法就是主线程)。同时再启动一个垃圾回收线程负责看护,回收垃圾。注意 :使用多线程机制之后, main 方法结束只是主线程结束了,其他线程还没结束,但没有主线程也不能运行。最起码,现在的 java 程序中至少有两个线程并发,一个是 垃圾回收线程,一个是 执行main 方法的主线程
总结:
1) 线程在进程下进行2) 进程之间不会相互影响,主线程结束将会导致整个进程结束3) 不同的进程数据很难共享,两个不同进程的内存也是不共享的,也是独立的4) 同线程下的不同线程之间数据很容易共享5) 进程使用内存地址可以限定使用量6) 可以把进程看成是现实生活当中的公司,线程可以看作是公司当中的某个员工
二、并发与并行与串行
1)并发
同一个时刻多个线程同时操作了同一个数据。(这是一种假并行。即一个 CPU 的情况下,在同一个时间点, CPU 只能执行一个代码,因为切换的很快,所以就有同时执行的错觉)。特点:同时安排若干个任务,这些任务可以彼此穿插着进行;有些任务可能是并行的,比如买菜、发邮 件和去洗脚的某些路是重叠的,这是确实是在做三件事;但进菜市场和发邮件和接娃三者是互斥的,每 个时刻只能完成一件(并发允许两个任务之间彼此干扰)
2)并行
同一个时刻,多个线程同时执行了不同的程序多个任务同时进行。并行必须有多核才能实现,否则只能是并发。你(线程)做你的事,我(线程)做我的事,咱们互不干扰并同时进行。
3)串行
三、CPU调度与主线程介绍
1)CPU调度
1 ):分时调度指的是,让所有的线程轮流获取CPU 的使用权,并且平均分配每个线程占用的 CPU 的时间片2 ):抢占式调度指的是,多个线程轮流抢占CPU 的使用权,随机性比较高,哪个线程的优先级越高,先执行的几率就比较大,不是每次都是优先级高的线程先抢到 ------->Java 程序为抢占式
2)主线程介绍
/**专门运行main方法的线程叫做主线程**/
public class Demo01Thread {
/**单线程程序**/
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println("我是一个for循环");
}
System.out.println(Math.abs(-1));
}
}
四、创建线程的方式
1)第一种方式_extends Thread
步骤:
1)创建一个类,继承Thread类
2)重写Thread中的run方法,设置线程任务(该线程要执行啥代码)
3)创建自定义的线程类对象
4)调用thread中的start方法(开启线程,JVM自动调用重写的run方法执行线程任务)
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("MyThread...执行了............."+i);
}
}
}
public class Demo02Thread {
public static void main(String[] args) {
//创建线程对象
MyThread myThread = new MyThread();
//调用Thread中的start方法,开启线程,jvm自动执行run方法
myThread.start();
myThread.start();//不能多次调用
for (int i = 0; i < 10; i++) {
System.out.println("main方法.............执行了"+i);
}
}
}
2)Thread类中的方法
public void run() : 此线程要执行的任务在此处定义代码。
public String getName() : 获取当前线程名称。
public void setName(线程名字): 给线程设置名字
public static Thread currentThread() :返回对当前正在执行的线程对象的引用。此方法在哪个线程中用获取的就是哪个线程对象
static void sleep(long millis) : 线程睡眠,设置的是毫秒值,如果超时,线程会自动醒来,继续执行 void start() 使该线程开始执行; Java 虚拟机调用该线程的 run 方法
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程开始执行了........"+i);
}
}
}
public class Test01 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.setName("曼曼");
myThread.start();//开启时才抢占,
for (int i = 0; i < 5; i++) {
Thread.sleep(1000L);//降低for循环的执行速度,线程睡眠
System.out.println(Thread.currentThread().getName()+"线程开始执行............"+i);
}
}
}
Thread.yield()方法:暂停当前正在执行的线程对象,把执行机会让給相同或者更高优先级的线程。
/**
* 线程让步
*/
public class Text05 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 15; i++) {
Thread.yield();
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
});
t.setName("t");
t.start();
// 主线程
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
// 等待子线程执行完毕再输出
if (i == 7) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
/**
运行结果:让主线程走完了才运行
**/
1.sleep() 使当前线程进入停滞状态,所以执行 sleep() 的线程在指定的时间内肯定不会被执行; yield() 只是使当前线程重新回到可执行状态,所以执行 yield() 的线程有可能在进入到可执行状态后马上又被执 行。2.sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的, yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。3. 实际上, yield() 方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以 yield() 方法称为 “ 退让 ” ,它把运 行机会让给了同等优先级的其他线程4. 另外, sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优 先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先 级的线程运行结束,才有机会运行。补充:
Thread.join() 方法:用于等待其他线程终止
如果线程A中调用了线程B的join方法,那么线程A阻塞,直到线程B执行完后,线程A从阻塞状态转为就绪状态,等待获取CPU的使用权。join方法要在start方法调用后调用才有效,线程必须启动,再加入。
package com.thread;
public class Text06 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new JoinThread("t1"));
Thread t2 = new Thread(new JoinThread("t2"));
t1.start();
t1.join();
t2.start();
}
}
class JoinThread implements Runnable {
private String name;
// 构造函数
public JoinThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println(name + "-->" + i);
}
}
}
主线程一定会等子线程都结束了才结束
3)第二种方式_实现Runnable接口
1.创建一个类,实现Runnable接口
2.重写run方法,设置线程任务
3.创建线程类对象
4.创建Thread对象,将线程类对象封装到Thread中 -> Thread(Runnable target)
5.调用Thread中的start方法
4)两种实现多线程方式的区别
1. 继承 Thread, 有局限性 , 因为继承支持单继承2. 实现 Runnable, 解决了单继承的局限性如果一个类继承Thread,则不适合资源共享。但是如果实现了Runnable接口的话,则很容易的实现资源贡共享。
【总结】:
实现Runnable接口比继承Thread类具有的有优势,适合多个相同的程序代码的线程去处理统一资源,可以避免java中的单继承的限制增加程序的健壮性,代码可以被多个线程共享,代码和数据独立,线程池只能放入实现Runnable或callable类线程,不能直接放入继承Therad的类。
main方法其也是一个线程。在java中所有的线程都是同时启动的,至于什么时候、那哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实就是在操作系统中启动了一个进程。
5)匿名内部类创建多线程
1.匿名内部类回顾:
a.匿名内部类代表的是子类对象或者实现类对象
b.格式1:
new 接口/抽象类(){
重写方法
}.重写的方法();c.格式2:
接口/抽象类 对象名 = new 接口/抽象类(){
重写方法
}
对象名.重写的方法();
javaJava
public class Test02 {
/**
* 匿名内部类创建多线程的方式
**/
public static void main(String[] args) {
// 创建并启动第一个线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
// 线程休眠100毫秒
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出线程名和执行次数
System.out.println(Thread.currentThread().getName() + "...执行了" + i);
}
}
}, "杨童").start();
// 创建并启动第二个线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
// 线程休眠100毫秒
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出线程名和执行次数
System.out.println(Thread.currentThread().getName() + "...执行了" + i);
}
}
}, "曼曼").start();
}
}
五、线程的生命周期
等待阻塞:运行的线程执行 wait ()方法, JVM 会把该线程放入等待池中。( wait 方法会释放持有的锁)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池中。其他阻塞:运行的线程执行 sleep ()或 join ()方法,或者发出了 I/O 请求时, JVM 会把该线程程置为阻塞状态。当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪 状。( sleep 是不会释放持有的锁)死亡状态( Dead ):线程执行完了或者因异常退出了 run ()方法,该线程结束生命周期。
六、线程同步
1)概念
即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多种。
2)为啥要创建多线程
在一般情况下,创建一个线程是不能提高程序执行效率的,所以要创建多个线程。
3-为什么要线程同步
多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。
4-什么叫线程同步
同步就是协同步调,按预定的先后次序进行运行。如:你做完,我再做。
错误理解 :
“同”字从字面上容易理解为一起动作,其实不是。“同”字应是指协同、协助、互相配合。
正确理解 :
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其他线程也不能调用这个方法。
线程同步的作用 :
线程有可能和其他线程共享一些资源,比如:内存、文件、数据库等
当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。
线程同步的真是意思其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
5-类锁和对象锁
(1)基本概念:
对象锁:在java中每个对象都有一个唯一的锁,对象锁用于对象实例方法或者一个对象实例上面的
—— 一个对象一把锁,100个对象100把锁。
类锁:是用于一个类静态方法或者class对象的,一个类的实例对象可以有多个,但是只有一个class对象
—— 100个对象,也只是1把锁。
注意上述第三种方式synchronized同步代码块实现:
在静态方法添加synchronized这把锁属于类了,所有这个类的对象都共享这把锁,这就叫类锁。
(2)释放锁的时机
如果一个方法或者代码块被synchronized关键字修饰,当线程获取到该方法或代码块的锁,其他线程是不能继续访问该方法或代码块的。而其他线程想访问该方法或代码块,就必须要等待获取到锁的线程释放这个锁,而在这里释放锁只有两种情况 :
线程执行完代码块,自动释放锁;
程序报错,JVM让线程自动释放锁;
如果一个方法或者代码块被synchronized关键字修饰,当线程获取到该方法或代码块的锁,其他线程是不能继续访问该方法或代码块的。而其他线程想访问该方法或代码块,就必须要等待获取到锁的线程释放这个锁,而在这里释放锁只有两种情况 :
线程执行完代码块,自动释放锁;
程序报错,JVM让线程自动释放锁;
public static void main(String[] args) {
new Text07().myThread1(new Thread());
new Text07().myThread2(new Thread());
}
七、 线程安全问题
1)什么时候会发生线程安全问题
2)问题
public class MyTicket implements Runnable{
int ticket = 100;
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket>0){//也有可能出现负数的情况
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
}
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket,"曼曼");
Thread t2 = new Thread(myTicket,"童童");
Thread t3 = new Thread(myTicket,"雪雪");
t1.start();
t2.start();
t3.start();
}
}
3)解决方案1(同步代码块)
同步互斥访问(synchronized)
基本上所有解决线程安全问题的方式都是采用“序列化临界资源访问”的方式,即在同一时刻只有一个线程操作临界资源,操作完了才能让其他线程进行操作,也称同步互斥访问。
在Java中一般采用synchronized和Lock来实现同步互斥访问。
Synchronized关键字
首先了解一下互斥锁 :就是能达到互斥访问目的的锁
1)线程有可能和其他线程共享一些资源,比如:内存、文件、数据库等
2)当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。
3) 线程同步的真是意思其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
synchronized同步代码块实现
1. 第一种:synchronized代码块方式(灵活)
2. 第二种:在实例方法上使用synchronized
3. 第三种 : 在静态方法上使用synchroniz
1.
1.关键字:synchronized
2.格式:
synchronized(任意对象){
可能出现线程不安全的代码
}
3.线程进了synchronized代码块相当于上锁了,其他线程只能在代码块外面等待,等到执行的线程执行完毕,出了synchronized,相当于将锁释放了,此时其他等待的线程才有机会拿到锁,上锁,进去执行
4.注意:想要实现线程安全,线程同步,多个线程之间用的锁对象需要是同一个锁对象
/**
* 实现 Runnable 接口的 MyTicket 类,用于模拟多线程售票系统
*/
public class MyTicket implements Runnable {
// 剩余票数
int ticket = 100;
// 创建对象锁
Object obj = new Object();
@Override
public void run() {
while (true) {
try {
// 线程休眠
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 同步代码块
synchronized (new Object()) {
if (ticket > 0) {
// 打印当前买票信息
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
// 票数减一
ticket--;
}
}
}
}
}
/**
* 测试类 Test01,用于测试多线程售票系统
*/
public class Test01 {
public static void main(String[] args) {
// 创建 MyTicket 实例
MyTicket myTicket = new MyTicket();
// 创建线程并指定线程名
Thread t1 = new Thread(myTicket, "曼曼");
Thread t2 = new Thread(myTicket, "童童");
Thread t3 = new Thread(myTicket, "雪雪");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
4)解决方案2(同步方法)
1.普通同步方法
1.格式:
public synchronized 返回值类型 方法名(参数){
可能出现线程不安全的代码
}2.默认锁:
this
/**
* 实现 Runnable 接口的 MyTicket 类,用于模拟多线程售票系统
*/
public class MyTicket implements Runnable {
// 剩余票数
int ticket = 100;
@Override
public void run() {
while (true) {
try {
// 线程休眠
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
method();
}
}
/**
* 同步方法
*/
public synchronized void method() {//可能出现线程不安全的代码
/*// 注释掉原有的同步方法
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}*/
if (ticket > 0) {
// 打印当前买票信息
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
// 票数减一
ticket--;
}
}
}
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket,"曼曼");
Thread t2 = new Thread(myTicket,"童童");
Thread t3 = new Thread(myTicket,"雪雪");
t1.start();
t2.start();
t3.start();
}
}
2.静态同步方法
1.格式:
public static synchronized 返回值类型 方法名(参数){
可能出现线程不安全的代码
}2.默认锁:
当前类.class
public class MyTicket implements Runnable {
static int ticket = 100;
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
method();
}
}
/* public static synchronized void method() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
}*/
public static void method(){
synchronized (MyTicket.class){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
}
}
}
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket,"曼曼");
Thread t2 = new Thread(myTicket,"童童");
Thread t3 = new Thread(myTicket,"雪雪");
t1.start();
t2.start();
t3.start();
}
}
八、死锁
1.死锁(锁嵌套就有可能产生死锁)
指的是两个或者两个以上的线程在执行的过程中,由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况就称之为死锁.
根据上图所示:线程T1正在持有R1锁,但是T1线程必须再拿到R2锁,才能继续执行
而线程T2正在持有R2锁,但是T2线程需要再拿到R1锁,才能继续执行
此时两个线程处于互相等待的状态,就是死锁,在程序中的死锁将出现在同步代码块的嵌套中
public class LockA {
public static LockA lockA = new LockA();
}
public class LockB {
public static LockB lockB = new LockB();
}
/**
* 循环死锁类 DieLock,实现了 Runnable 接口
*/
public class DieLock implements Runnable {
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
// 如果 flag 为 true,获取 lockA 锁
synchronized (LockA.lockA) {
System.out.println("if...lockA");
// 获取 lockB 锁
synchronized (LockB.lockB) {
System.out.println("if...lockB");
}
}
} else {
// 如果 flag 为 false,获取 lockB 锁
synchronized (LockB.lockB) {
System.out.println("else...lockB");
// 获取 lockA 锁
synchronized (LockA.lockA) {
System.out.println("else...lockA");
}
}
}
}
}
public class Test01 {
public static void main(String[] args) {
DieLock dieLock1 = new DieLock(true);
DieLock dieLock2 = new DieLock(false);
new Thread(dieLock1).start();
new Thread(dieLock2).start();
}
}
直接卡死
九、线程状态
1)线程状态介绍
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态:
这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析。
2)线程状态图
可能不详细
再贴一张
十、等待唤醒
练习1:
public class BaoZiPu {
// 定义一个count 计数
// 生产包子 count++ 消费包子:直接输出count
private int count;
private boolean flag;
// 定义一个boolean的flag
// flag = true----->证明有包子 flag = false----->证明没有包子
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
/**
* getCount专门给消费线程用
*/
public void getCount() {
System.out.println("消费了第......"+count+"个包子");
}
/**
* setCount专门给生产线程生产包子用
*/
public void setCount() {
count++;
System.out.println("生成了第..."+count+"个包子");
}
public boolean isFlag() {//看看是不是有包子
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class Product implements Runnable {
private BaoZiPu baoZiPu;
public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
synchronized (baoZiPu) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//1.判断flag如果是true,证明有包子,生产线程等待
if (baoZiPu.isFlag() == true) {
try {
baoZiPu.wait();//必须要锁对象调用
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2.如果flag为false,证明没有包子,需要生产包子
baoZiPu.setCount();
//3.改变flag状态,为true,证明有包子了
baoZiPu.setFlag(true);
//4.唤醒消费线程
baoZiPu.notify();//必须要锁对象调用
}
}
}
}
public class Consumer implements Runnable {
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
synchronized (baoZiPu) {
try {
// 线程休眠
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 1.判断标志 flag,如果为 false,表示没有包子,消费线程等待
if (!baoZiPu.isFlag()) {
try {
baoZiPu.wait(); // 等待并释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 2.如果 flag 为 true,表示有包子,需要消费包子
baoZiPu.getCount();
// 3.改变标志 flag 状态为 false,表示没有包子了
baoZiPu.setFlag(false);
// 4.唤醒生产线程
baoZiPu.notify(); // 唤醒在此对象监视器上等待的单个线程
}
}
}
}
public class Test01 {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();
Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);
new Thread(product).start();
new Thread(consumer).start();
}
}
同步方法改造写法
public class BaoZiPu {
private int count;
private boolean flag;
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
/**
* getCount专门给消费线程用
* @return
*/
public synchronized void getCount() {
//1.判断flag如果是false,证明没有包子,消费线程等待
if (flag == false) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2.如果flag为true,证明有包子,需要消费包子
System.out.println("消费了第......" + count + "个包子");
//3.改变flag状态,为false,证明没有包子了
flag = false;
//4.唤醒生产线程
this.notify();
}
/**
* setCount专门给生产线程生产包子用
*
* @param
*/
public synchronized void setCount() {
//1.判断flag如果是true,证明有包子,生产线程等待
if (flag == true) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2.如果flag为false,证明没有包子,需要生产包子
count++;
System.out.println("生成了第..." + count + "个包子");
//3.改变flag状态,为true,证明有包子了
flag = true;
//4.唤醒消费线程
this.notify();
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
/**
* 生产线程
*/
public class Product implements Runnable {
private BaoZiPu baoZiPu;
public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
baoZiPu.setCount();
}
}
}
public class Consumer implements Runnable {
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
baoZiPu.getCount();
}
}
}
public class Test01 {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();
Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);
new Thread(product).start();
new Thread(consumer).start();
}
}
多等待多唤醒
public class BaoZiPu {
private int count;
private boolean flag;
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
/**
* getCount专门给消费线程用
*/
public synchronized void getCount() {
//1.判断flag如果是false,证明没有包子,消费线程等待
while (flag == false) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2.如果flag为true,证明有包子,需要消费包子
System.out.println("消费了第......" + count + "个包子");
//3.改变flag状态,为false,证明没有包子了
flag = false;
//4.唤醒生产线程
this.notifyAll();
}
/**
* setCount专门给生产线程生产包子用
*
* @param
*/
public synchronized void setCount() {
//1.判断flag如果是true,证明有包子,生产线程等待
while (flag == true) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2.如果flag为false,证明没有包子,需要生产包子
count++;
System.out.println("生成了第..." + count + "个包子");
//3.改变flag状态,为true,证明有包子了
flag = true;
//4.唤醒消费线程
this.notifyAll();
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class Consumer implements Runnable {
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
baoZiPu.getCount();
}
}
}
public class Product implements Runnable {
private BaoZiPu baoZiPu;
public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
baoZiPu.setCount();
}
}
}
public class Test01 {
public static void main(String[] args) {
/**
* 两个生产者,两个消费者
* **/
BaoZiPu baoZiPu = new BaoZiPu();
Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);
new Thread(product).start();
new Thread(product).start();
/*********************************/
new Thread(consumer).start();
new Thread(consumer).start();
/**
* 会出现连续消费,连续生产的情况
*notify 如果多条线程在等待唤醒,就会随机唤醒一个
*假如第一个生产者线程抢到了锁,生产完第一个包子,然后随机唤醒一个锁,然后释放锁
* 然后第二个生产线程抢到了锁,flag还是为true,所以生产线程等待,再次释放锁
* 然后第一个消费者线程抢到锁,然后消费一个包子,同时改flag为false 随机唤醒一个生产者线程,再次释放拿到的锁
*同时刚刚等待的第二个生产者线程再次拿到锁,生产一个包子,改变flag值,再次随机唤醒一个生产者线程,同时释放手里的锁
*这时第一个生产线程也可能再次拿到锁,再次生产一个包子,前一个已经生产了,会造成同时生产2个包子的情况
*反之,也可能出现同时消费2个包子的情况
* 解决办法:
*直接唤醒所有线程,但是需要保证第二个自己的线程能再次拿到锁,所以必须循环回去,
* 将所有if改成while
*这样就保证了第一次在生产完包子后,改了flag值,即使第二个生产者拿到锁,flag不符合也不会继续生产
* **/
}
}
十一、lock锁
1)简单实现案例:
1.概述:是一个接口,作为锁对象使用
2.实现类:ReentrantLock
3.方法:
void lock() :获取锁
void unlock() :释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 实现 Runnable 接口的 MyTicket 类,用于模拟多线程售票系统
*/
public class MyTicket implements Runnable {
int ticket = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取锁
lock.lock();
try {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
} finally {
// 释放锁
lock.unlock();
}
}
}
}
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket,"曼曼");
Thread t2 = new Thread(myTicket,"童童");
Thread t3 = new Thread(myTicket,"雪雪");
t1.start();
t2.start();
t3.start();
}
}
2)lock与synchronized区别
1. 在java.util.concurrent.atomic 包下有很多"原子类",这些类都是对某一个值进行修改,进行操作的,底层实现原理都是"乐观锁"
AtomicXXX比如:AtomicInteger -> 以原子形式,修改int的值
2.构造:
a.AtomicInteger()创建具有初始值 0 的新 AtomicInteger -> int i = 0
b.AtomicInteger(int initialValue)创建具有给定初始值的新 AtomicInteger -> int i = 10
3.方法:
int addAndGet(int delta) 以原子方式将给定值与当前值相加
int getAndIncrement()-> 加1
int getAndDecrement()-> 减1
public class Test02 {
public static void main(String[] args) {
AtomicInteger i = new AtomicInteger(10);
System.out.println(i);
int sum = i.addAndGet(10);
System.out.println("sum = " + sum);
}
}
十二、线程池
1.问题描述:
我们将来在操作多线程的时候,我们为了执行线程任务会频繁创建线程对象,销毁线程对象,这样的话比较耗费内存资源
所以,我们想,能不能创建几个线程对象,让这几个线程对象循环利用
1.概述:Executors线程池对象
2.创建:
static ExecutorService newFixedThreadPool(int nThreads) -> 创建线程池,指定最多创建多少条线程对象
3.ExecutorService:管理线程的
Future<?> submit(Runnable task) -> 提交线程任务
void shutdown() -> 启动一次顺序关闭,执行以前提交的任务,但不接受新任务
4.Future接口:
用来接收执行run方法后的返回值的值,但是run方法没有返回值,所以不需要Future接收
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行了");
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(2);
Future<String> future = es.submit(new MyCallable());
System.out.println("future = " + future.get());
}
}
十三、Callable接口
1)基本概念
1.概述:Callable<V>接口,类似于Runnable
2.方法:V call()
3.<V>:叫做泛型
a.泛型作用:规定类型的,统一数据类型的
b.泛型只能传递引用数据类型,如果操作基本类型数据,需要传递包装类,如果不写泛型,默认类型为Object类型
4.call方法 ->设置线程任务的,类似于run方法
5.call方法和run方法区别
run:设置线程任务,没有返回值,不能throws异常
call:设置线程任务,有返回值,能直接throws异常,
返回值类型是啥类型?
实现Callable时泛型写什么类型,重写的call方法返回值类型就是啥类型
6.提交线程任务:ExecutorService中的方法:
Future<T> submit(Callable<T> task)
返回值:Future->用于接收call方法的返回值
7.获取call的返回值:
需要用到Future接口中的方法:V get()
public class Test01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(2);
Future<String> future = es.submit(new MyCallable());
System.out.println("future = " + future.get());
}
}
练习1
需求:创建两个线程任务,一个线程任务完成1-100的和,一个线程任务返回一个字符串
public class MyString implements Callable<String> {
@Override
public String call() throws Exception {
int[] arr = {1,2,3,4,5};
String str = "[";
for (int i = 0; i < arr.length; i++) {
if (i== arr.length-1){
str+=arr[i]+"]";
}else {
str+=arr[i]+", ";
}
}
return str;
}
}
public class MySum implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum+=i;
}
return sum;
}
}
public class Test01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建一个固定大小为 2 的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
// 提交 MyString 任务,并获取其执行结果
Future<String> f1 = es.submit(new MyString());
// 提交 MySum 任务,并获取其执行结果
Future<Integer> f2 = es.submit(new MySum());
// 输出 MyString 任务的执行结果
System.out.println(f1.get());
// 输出 MySum 任务的执行结果
System.out.println(f2.get());
// 关闭线程池
es.shutdown();
}
}
十四、定时器
1.作用:用来定时执行线程任务的
2.构造:Timer()
3.方法:
void schedule(TimerTask task, Date firstTime, long period)
task:设置线程任务
firstTime:从什么时间开始
period:每隔多长时间执行一次线程任务
public class Test01 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("柳岩对涛哥说:涛哥,该起床了!");
}
},new Date(),2000L);//当前系统时间开始算,每隔两秒
}
}
十五、枚举
1)枚举介绍
1.引用数据类型:
类 数组 接口 枚举 注解
2.所有的枚举类的父类->Enum
public enum 枚举类类名{
}3.枚举中的成员:
a.所有成员默认都是static final修饰的常量,但是不用写static final,被static final修饰的变量名习惯上大写
b.每一个枚举成员都是所在枚举的对象
c.问题:枚举类中的成员都是什么类型? -> 本类类型
4.枚举的使用场景:一般都是表示状态的
5.注意:枚举类中的构造都是private的,不写private,默认也是private
package com.atguigu.enumtest;
public enum Status {
//WEIFUKUAN,// Status WEIFAHUO = new Status()
//YIFUKUAN,// Status YIFUKUAN = new Status()
//WEIFAHUO,// Status WEIFAHUO = new Status()
//YIFAHUO;// Status YIFAHUO = new Status()
WEIFUKUAI("未付款"),// Status WEIFAHUO = new Status("未付款")
YIFUKUAN("已付款"), //Status YIFUKUAN = new Status("已付款")
WEIFAHUO("未发货"), //Status WEIFAHUO = new Status("未发货")
YIFAHUO("已发货");//Status YIFAHUO = new Status("已发货")
//枚举类中的构造要求是private的,不写也是private
String name;
Status(){
}
Status(String name){
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Test01 {
public static void main(String[] args) {
Status weifahuo = Status.WEIFAHUO;
System.out.println("weifahuo = " + weifahuo);//默认调用toString方法
System.out.println("weifahuo = " + weifahuo.toString());//默认调用toString方法
Status weifahuo1 = Status.WEIFAHUO;
System.out.println(weifahuo1.getName());
}
}
2)枚举的方法
public enum Status {
//WEIFUKUAN,// Status WEIFAHUO = new Status()
//YIFUKUAN,// Status YIFUKUAN = new Status()
//WEIFAHUO,// Status WEIFAHUO = new Status()
//YIFAHUO;// Status YIFAHUO = new Status()
WEIFUKUAI("未付款"),// Status WEIFAHUO = new Status("未付款")
YIFUKUAN("已付款"), //Status YIFUKUAN = new Status("已付款")
WEIFAHUO("未发货"), //Status WEIFAHUO = new Status("未发货")
YIFAHUO("已发货");//Status YIFAHUO = new Status("已发货")
//枚举类中的构造要求是private的,不写也是private
String name;
Status(){
}
Status(String name){
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Test02 {
public static void main(String[] args) {
Status weifahuo = Status.WEIFAHUO;
System.out.println(weifahuo);
System.out.println(weifahuo.toString());
System.out.println("=======================");
Status[] values = Status.values();
for (Status value : values) {
System.out.println(value.getName());
}
System.out.println("=======================");
Status yifahuo = Status.valueOf("YIFAHUO");
System.out.println(yifahuo);
}
}
写的不好,请你给我指出来,谢谢