多线程

我们在之前,学习的程序在没有跳转语句的前提下,都是由上至下依次执行,那现在想要设计一个程序,边打游戏边听歌,怎么设计?
要解决上述问题,咱们得使用多进程或者多线程来解决.

并发与并行

  • 并发:指两个或多个事件在同一个时间段内发生。
  • 并行:指两个或多个事件在同一时刻发生(同时发生)。

在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的效率。

注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

线程与进程

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
    简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
    我们可以再电脑底部任务栏,右键----->打开任务管理器,可以查看当前任务的进程:

线程调度:

  • 分时调度
    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
  • 抢占式调度
    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

主线程:

主线程:执行主(main)方法的线程

单线程程序:java程序中只有一个线程
执行从main方法开始,从上到下依次执行

JVM执行main方法,main方法会进入到栈内存
JVM会找操作系统开辟一条main方法通向cpu的执行路径
cpu就可以通过这个路径来执行main方法
而这个路径有一个名字,叫main(主)线程

public class Person {private String name;//定义一个方法runpublic void run(){//定义循环,执行20次for(int i=0; i<20; i++){System.out.println(name+"-->"+i);//有this就是使用成员变量//没有this就是就近原则。}}注意:还要有get,set,有参无参数构造方法public class Demo01MainThread {public static void main(String[] args) {Person p1 = new Person("小强");p1.run();
//        System.out.println(0/0);//0不能做分母,抛出数学计算异常ArithmeticException: / by zeroPerson p2 = new Person("旺财");p2.run();}
}
小结:上面单线程代码在上述图中有解释,单线弊端:一条线程出错后面的代码不能被执行

创建线程类

创建多线程程序的第一种方式:创建Thread类的子类

java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类

实现步骤:
1.创建一个Thread类的子类
2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
3.创建Thread类的子类对象
4.调用Thread类中的方法start方法,开启新的线程,执行run方法
void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
结果是两个线程并发地运行;当前线程(main线程)和另一个线程(创建的新线程,执行其 run 方法)。

多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
java程序属于抢占式调度,那个线程的优先级高,那个线程优先执行;同一个优先级,随机选择一个执行

//1.创建一个Thread类的子类
public class MyThread extends Thread{//2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)@Overridepublic void run() {for (int i = 0; i <20 ; i++) {System.out.println("run:"+i);}}
}
public class Demo01Thread {public static void main(String[] args) {//3.创建Thread类的子类对象MyThread mt = new MyThread();//4.调用Thread类中的方法start方法,开启新的线程,执行run方法mt.start();for (int i = 0; i <20 ; i++) {System.out.println("main:"+i);}}
}

有通向cpu的两条路径,一起抢夺cpu的执行时间,导致随机打印结果。

main方法走到栈的下面,也就是压栈执行,然后执行main里面的代码,创建了一个对象,在堆中,并且有地址值,赋值给变量mt。后面代码mt.run()方法之前都是一个单线程,结果是先执行run方法,在执行其他代码。多线程就是在调用start()方法,会开辟新的栈空间执行run方法。如果还有其他对象调用start()方法会在开辟一个新的栈空间。就是三线程。

Thread类

方法

获取线程的名称:
1.使用Thread类中的方法getName()
String getName() 返回该线程的名称。
2.可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
static Thread currentThread() 返回对当前正在执行的线程对象的引用。

// 定义一个Thread类的子类
public class MyThread extends Thread{//重写Thread类中的run方法,设置线程任务@Overridepublic void run() {//获取线程名称//String name = getName();//System.out.println(name);//Thread t = Thread.currentThread();//System.out.println(t);//Thread[Thread-0,5,main]//String name = t.getName();//System.out.println(name);//链式编程System.out.println(Thread.currentThread().getName());}
}线程的名称:主线程: main新线程: Thread-0,Thread-1,Thread-2public class Demo01GetThreadName {public static void main(String[] args) {//创建Thread类的子类对象MyThread mt = new MyThread();//调用start方法,开启新线程,执行run方法mt.start();new MyThread().start();new MyThread().start();//链式编程System.out.println(Thread.currentThread().getName());//注意:获取当前正在执行的线程名称的时候,一定要先获取当前正在执行的线程//(Thread.currentThread(),因为测试类中没有继承Thread类没有getname方法,必须先获取线程在获取名字}
}

设置线程的名称:(了解)
1.使用Thread类中的方法setName(名字)
void setName(String name) 改变线程名称,使之与参数 name 相同。
2.创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
Thread(String name) 分配新的 Thread 对象。

public class MyThread extends Thread{public MyThread(){}public MyThread(String name){super(name);//把线程名称传递给父类,让父类(Thread)给子线程起一个名字}@Overridepublic void run() {//获取线程的名称System.out.println(Thread.currentThread().getName());}
}
public class Demo01SetThreadName {public static void main(String[] args) {//开启多线程MyThread mt = new MyThread();mt.setName("小强");mt.start();//开启多线程new MyThread("旺财").start();}
}

public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
毫秒数结束之后,线程继续执行
注意是静态方法,通过Thread可以直接调用

public class Demo01Sleep {public static void main(String[] args) {//模拟秒表for (int i = 1; i <=60 ; i++) {System.out.println(i);//使用Thread类的sleep方法让程序睡眠1秒钟try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

创建多线程程序的第二种方式:实现Runnable接口

创建多线程程序的第二种方式:实现Runnable接口
java.lang.Runnable
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
java.lang.Thread类的构造方法
Thread(Runnable target) 分配新的 Thread 对象。
Thread(Runnable target, String name) 分配新的 Thread 对象。

实现步骤:
1.创建一个Runnable接口的实现类
2.在实现类中重写Runnable接口的run方法,设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法

实现Runnable接口创建多线程程序的好处:Thread和Runnable区别
1.避免了单继承的局限性
一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口

2.增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:用来开启新线程

//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{//2.在实现类中重写Runnable接口的run方法,设置线程任务@Overridepublic void run() {for (int i = 0; i <20 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);}}
}
public class Demo01Runnable {public static void main(String[] args) {//3.创建一个Runnable接口的实现类对象RunnableImpl run = new RunnableImpl();//4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象//Thread t = new Thread(run);//打印线程名称Thread t = new Thread(new RunnableImpl2());//打印HelloWorld//5.调用Thread类中的start方法,开启新的线程执行run方法t.start();for (int i = 0; i <20 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);}}
}
//设置了线程任务和线程开启进行了分离
public class RunnableImpl2 implements Runnable{//2.在实现类中重写Runnable接口的run方法,设置线程任务@Overridepublic void run() {for (int i = 0; i <20 ; i++) {System.out.println("HelloWorld"+i);}}
}
小结:之后尽量用实现Runnable接口方式创建多线程。

匿名内部类方式实现线程的创建

匿名:没有名字
内部类:写在其他类内部的类

匿名内部类作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象合一步完成
把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

格式:

 new 父类/接口(){重复父类/接口中的方法};
public class Demo01InnerClassThread {public static void main(String[] args) {//线程的父类是Thread//之前创建多线程方法1// new MyThread().start();创建子类重写run方法,并且调用start方法new Thread(){//重写run方法,设置线程任务@Overridepublic void run() {for (int i = 0; i <20 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+"张小敬");}}}.start();//创建多线程方法2//线程的接口Runnable//Runnable r = new RunnableImpl();//多态Runnable r = new Runnable(){//重写run方法,设置线程任务@Overridepublic void run() {for (int i = 0; i <20 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+"李必");}}};new Thread(r).start();//我们不传递r,直接传递上述的new之后的内容,本身就是一个实现类对象//简化接口的方式new Thread(new Runnable(){//重写run方法,设置线程任务@Overridepublic void run() {for (int i = 0; i <20 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+"吼吼");}}}).start();}
}

线程安全问题

模拟卖票案例
创建3个线程,同时开启,对共享的票进行出售

public class RunnableImpl implements Runnable{//定义一个多个线程共享的票源private  int ticket = 100;//设置线程任务:卖票@Overridepublic void run() {//使用死循环,让卖票操作重复执行while(true){//先判断票是否存在if(ticket>0){//提高安全问题出现的概率,让程序睡眠try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//票存在,卖票 ticket--System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");ticket--;}}}
}public class Demo01Ticket {public static void main(String[] args) {//创建Runnable接口的实现类对象RunnableImpl run = new RunnableImpl();//创建Thread类对象,构造方法中传递Runnable接口的实现类对象Thread t0 = new Thread(run);Thread t1 = new Thread(run);Thread t2 = new Thread(run);//调用start方法开启多线程t0.start();t1.start();t2.start();}
}
上述代码运行时会发现有重复的票和0号 -1号票,明显是有问题

100张票改成1行进行分析。
开启三个线程,一起抢夺cpu执行权,谁抢到谁执行,抢到后就会到if语句停止使用cpu
因为使用了一个睡眠,放弃执行权。上面代码都在卖第100张票,说明同时抢到了第100张票时cpu的使用权,此时还有没执行到sicket–的操作。

解决线程安全问题

方法1、同步代码块

卖票案例出现了线程安全问题
卖出了不存在的票和重复的票

解决线程安全问题的一种方案:使用同步代码块
格式:

synchronized(锁对象){可能会出现线程安全问题的代码 或者(访问了共享数据的代码)}

注意:
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行

public class RunnableImpl implements Runnable{//定义一个多个线程共享的票源private  int ticket = 100;//创建一个锁对象 注意要创建在run方法外面,如果创建在里面,循环就会创建三个锁对象Object obj = new Object();//设置线程任务:卖票@Overridepublic void run() {//使用死循环,让卖票操作重复执行while(true){//同步代码块synchronized (obj){//先判断票是否存在if(ticket>0){//提高安全问题出现的概率,让程序睡眠try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//票存在,卖票 ticket--System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");ticket--;}}}}
}

解决线程的第二种方式:同步方法

卖票案例出现了线程安全问题
卖出了不存在的票和重复的票

解决线程安全问题的二种方案:使用同步方法
使用步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized修饰符

格式:定义方法的格式

  修饰符 synchronized 返回值类型 方法名(参数列表){可能会出现线程安全问题的代码(访问了共享数据的代码)}

定义一个同步方法
同步方法也会把方法内部的代码锁住
只让一个线程执行
同步方法的锁对象是谁? -->线程的实现类对象
就是实现类对象 new RunnableImpl()
也是就是this(不太理解)

public synchronized void payTicket1(){//先判断票是否存在if(ticket>0){//提高安全问题出现的概率,让程序睡眠try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//票存在,卖票 ticket--System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");ticket--;}
}
下面进行验证:创建同步代码块传入实现类对象,并没有创建新的对象就可以运行
public /*synchronized*/ void payTicket(){synchronized (this){//先判断票是否存在if(ticket>0){//提高安全问题出现的概率,让程序睡眠try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//票存在,卖票 ticket--System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");ticket--;}}
}主方法中打印run对象和最上面打印this的地址值一样。

静态同步方法:

静态的同步方法
锁对象是谁?
不能是this
this是创建对象之后产生的,静态方法优先于对象
**静态方法的锁对象是本类的  class属性-->class文件对象(反射会讲)**
public static /*synchronized*/ void payTicketStatic(){synchronized (RunnableImpl.class){//创建同步代码块进行锁对象的判断//先判断票是否存在if(ticket>0){//提高安全问题出现的概率,让程序睡眠try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//票存在,卖票 ticket--System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");ticket--;}}}

方法3、Lock锁:

解决线程安全问题的三种方案:使用Lock锁
java.util.concurrent.locks.Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock接口中的方法:
void lock()获取锁。
void unlock() 释放锁。
java.util.concurrent.locks.ReentrantLock implements Lock接口

使用步骤:
1.在成员位置创建一个ReentrantLock对象
2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

//1.在成员位置创建一个ReentrantLock对象
Lock l = new ReentrantLock();//多态
@Override
public void run() {//使用死循环,让卖票操作重复执行while(true){//2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁l.lock();//先判断票是否存在if(ticket>0){//提高安全问题出现的概率,让程序睡眠try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//票存在,卖票 ticket--System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");ticket--;}//3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁l.unlock();}
}

API文档中推荐代码:
把unlock放到finally 中,不管代码是否报错,都会释放锁。

public void run() {//使用死循环,让卖票操作重复执行while(true){//2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁l.lock();//先判断票是否存在if(ticket>0){//提高安全问题出现的概率,让程序睡眠try {Thread.sleep(10);//票存在,卖票 ticket--System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");ticket--;} catch (InterruptedException e) {e.printStackTrace();}finally {//3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁l.unlock();//无论程序是否异常,都会把锁释放}}}
}

小结:上述三种方法都可以解决线程安全问题,API中推荐使用lock锁。

线程状态

六种状态:

介绍几种状态:

Timed Waiting

Blocked:锁阻塞

Waiting 无限等待:

等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子

注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
同步使用的锁对象必须保证唯一
只有锁对象才能调用wait和notify方法

Obejct类中的方法
void wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void notify()
唤醒在此对象监视器上等待的单个线程。
会继续执行wait方法之后的代码

public class Demo01WaitAndNotify {public static void main(String[] args) {//创建锁对象,保证唯一Object obj = new Object();// 创建一个顾客线程(消费者)new Thread(){//使用匿名内部类@Overridepublic void run() {//一直等着买包子while(true){//保证等待和唤醒的线程只能有一个执行,需要使用同步技术synchronized (obj){//必须使用同步代码块进行包裹System.out.println("告知老板要的包子的种类和数量");//调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)try {obj.wait();//使用锁对象进行等待,失去cpu使用权。} catch (InterruptedException e) {//此处不能把异常进行throws声明抛出,因为父类run方法没有异常子类也不能有异常e.printStackTrace();}//唤醒之后执行的代码System.out.println("包子已经做好了,开吃!");System.out.println("---------------------------------------");}}}}.start();//创建一个老板线程(生产者)new Thread(){@Overridepublic void run() {//一直做包子while (true){//花了5秒做包子try {Thread.sleep(5000);//花5秒钟做包子,睡眠5秒} catch (InterruptedException e) {e.printStackTrace();}//保证等待和唤醒的线程只能有一个执行,需要使用同步技术synchronized (obj){System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");//做好包子之后,调用notify方法,唤醒顾客吃包子obj.notify();//进行唤醒}}}}.start();}
}

进入到TimeWaiting(计时等待)有两种方式

1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

唤醒的方法:

     void notify() 唤醒在此对象监视器上等待的单个线程。如果有两个线程,随机唤醒void notifyAll() 唤醒在此对象监视器上等待的所有线程。
public class Demo02WaitAndNotify {public static void main(String[] args) {//创建锁对象,保证唯一Object obj = new Object();// 创建一个顾客线程(消费者)new Thread(){@Overridepublic void run() {//一直等着买包子while(true){//保证等待和唤醒的线程只能有一个执行,需要使用同步技术synchronized (obj){System.out.println("顾客1告知老板要的包子的种类和数量");//调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}//唤醒之后执行的代码System.out.println("包子已经做好了,顾客1开吃!");System.out.println("---------------------------------------");}}}}.start();// 创建一个顾客线程(消费者)new Thread(){@Overridepublic void run() {//一直等着买包子while(true){//保证等待和唤醒的线程只能有一个执行,需要使用同步技术synchronized (obj){System.out.println("顾客2告知老板要的包子的种类和数量");//调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}//唤醒之后执行的代码System.out.println("包子已经做好了,顾客2开吃!");System.out.println("---------------------------------------");}}}}.start();//创建一个老板线程(生产者)new Thread(){@Overridepublic void run() {//一直做包子while (true){//花了5秒做包子try {Thread.sleep(5000);//花5秒钟做包子} catch (InterruptedException e) {e.printStackTrace();}//保证等待和唤醒的线程只能有一个执行,需要使用同步技术synchronized (obj){System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");//做好包子之后,调用notify方法,唤醒顾客吃包子//obj.notify();//如果有多个等待线程,随机唤醒一个obj.notifyAll();//唤醒所有等待的线程}}}}.start();}
}

等待唤醒机制

线程间通信

概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。

为什么要处理线程间通信
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。

如何保证线程间通信有效利用资源
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。

等待唤醒机制

什么是等待唤醒机制
这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。
就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。
等待唤醒中的方法
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
notifyAll:则释放所通知对象的 wait set 上的全部线程。
注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。
总结如下:
如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态

调用wait和notify方法需要注意的细节

wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

代码实现:

public class Baozi {//先定义包子类,设置成员变量String pi;String xian;//生产不同的包子的flag,初始值是falseboolean flag = false;}
public class Baozipu extends Thread {//1、成员变量位置创建一个包子变量private  Baozi bz;//2、使用带参数的构造方法,为包子成员变量赋值public Baozipu(Baozi bz) {this.bz = bz;}@Overridepublic void run() {//要运行的程序放到里面int count = 0;//定义一个数,表示到底几轮生产什么样子的包子,不要放到循环里面while(true){synchronized (bz){//把需要控制的内容放到里面,参数是thisif(bz.flag == true){//有包子,包子铺进入等待状态try {bz.wait();//} catch (InterruptedException e) {e.printStackTrace();}}//被唤醒之后执行if(count%2==0){//生产其他类型的包子bz.pi = "薄皮";bz.xian = "三鲜";}else{bz.pi = "冰皮";bz.xian = "牛肉大葱";}count++;System.out.println("包子铺正在生产"+bz.pi+bz.xian+"的包子");//生产需要三秒try {Thread.sleep(3000);//静态方法直接用类调用} catch (InterruptedException e) {e.printStackTrace();}//生产好了,变成有包子bz.flag = true;bz.notify();System.out.println("包子铺生产好了"+bz.pi+bz.xian+"的包子,"+"吃货可以吃了");}}}}
public class ChiHuo extends Thread {//1、成员变量位置创建一个包子变量private  Baozi bz;//2、使用带参数的构造方法,为包子变量赋值public ChiHuo(Baozi bz) {this.bz = bz;}@Overridepublic void run() {//进行重写run方法//线程任务:吃包子//加循环while (true){synchronized (bz){if(bz.flag == false){//没包子进行等待try {bz.wait();} catch (InterruptedException e) {e.printStackTrace();}}//if判断之后也就是有包子的状态,就会传过来被唤醒的状态//如果chihuo类被唤醒,那么baozipu类就会进入wait状态//被唤醒之后代码,吃包子,吃包子没有设置时间System.out.println("正在吃"+bz.pi+bz.xian+"的包子");//吃完,修改成falsebz.flag = false;//唤醒包子铺bz.notify();//在同步函数中使用System.out.println("包子铺开始加工");}}}
public class Test {public static void main(String[] args) {//创建包子对象Baozi bz = new Baozi();//创建包子铺线程,开启,制作包子new Baozipu(bz).start();//创建吃货线程,开启,吃包子new ChiHuo(bz).start();}
}

线程池

线程池思想概述

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池。

线程池概念

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:

如果使用list集合,取出用list.remove(0),如果使用linked集合取出用linked.removeFirst()
用完还需要归还,涉及到队列的思想,先加到队尾。JDK1.5之后内置线程池,不需要创建集合,可以直接使用。

合理利用线程池能够带来三个好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

线程池:JDK1.5之后提供的
java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
参数:
int nThreads:创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是ExecutorService接口的实现类对象,
我们可以使用ExecutorService接口接收(面向接口编程),接口等于实现类

java.util.concurrent.ExecutorService:线程池接口
方法:用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个 Runnable 任务用于执行
关闭/销毁线程池的方法
void shutdown()

线程池的使用步骤:
1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)

  创建一个类,实现Runnable接口,重写run方法,设置线程任务是一个实现类public class RunnableImpl implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"创建了一个新的线程执行");}
}
public static void main(String[] args) {//1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池ExecutorService es = Executors.newFixedThreadPool(2);//返回值(返回一个接口) 名字 = 类.静态方法(两个线程)//3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行//线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行es.submit(new RunnableImpl());//pool-1-thread-2创建了一个新的线程执行//4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)es.shutdown();//        es.submit(new RunnableImpl());//抛异常,线程池都没有了,就不能获取线程了}

进阶12 多线程、等待唤醒机制、线程池相关推荐

  1. Java——线程锁,死锁,等待唤醒机制

    一.线程锁 线程安全问题 其实,线程安全问题都是由全局变量及静态变量引起的.若每个线程中对全局变量.静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的:若有多个线程同时执行写操作,一般 ...

  2. java等待_Java学习:等待唤醒机制

    等待唤醒机制 线程的状态 NEW 至今尚未启动的线程处于这种状态 RUNNABLE 正在Java虚拟机中执行的线程处于这种状态 BLOCKED 受阻塞并等待某个监视器锁的线程处于这种状态 WAITIN ...

  3. 24.多线程(等待唤醒机制,volatile,CAS 算法,线程池,定时器,设计模式)

    1.线程间的等待唤醒机制 Object 类中   void wait ()  在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待.         ...

  4. java基础提升(二):多线程、线程安全、线程状态、等待唤醒机制、线程池

    目录 一. 多线程 1.1并发与并行 1.2 线程与进程 1.3 创建线程类 1.3.1 方式一:继承Thread类 1.3.2 方式二:实现Runnable接口 1.3.3 Thread和Runna ...

  5. 27_多线程_第27天(线程安全、线程同步、等待唤醒机制、单例设计模式)_讲义...

    今日内容介绍 1.多线程安全问题 2.等待唤醒机制 01线程操作共享数据的安全问题 *A:线程操作共享数据的安全问题如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程 ...

  6. 27_多线程_第27天(线程安全、线程同步、等待唤醒机制、单例设计模式)

    今日内容介绍 1.多线程安全问题 2.等待唤醒机制 01线程操作共享数据的安全问题 *A:线程操作共享数据的安全问题如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程 ...

  7. Java多线程02(线程安全、线程同步、等待唤醒机制)

    Java多线程2(线程安全.线程同步.等待唤醒机制.单例设计模式) 1.线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量 ...

  8. 多线程之间的通信(等待唤醒机制、Lock 及其它线程的方法)

    一.多线程之间的通信. 就是多个线程在操作同一份数据, 但是操作的方法不同. 如: 对于同一个存储块,其中有两个存储位:name   sex, 现有两个线程,一个向其中存放数据,一个打印其中的数据. ...

  9. Java线程等待唤醒机制(加深理解)

    今天看源码的时候遇到这样一个场景,某线程里面的逻辑需要等待异步处理结果返回后才能继续执行.或者说想要把一个异步的操作封装成一个同步的过程.这里就用到了线程等待唤醒机制,下面具体看一下. 等待唤醒机制示 ...

最新文章

  1. vim替换字符串带斜杠_Vim、gvim操作替换
  2. 新浪微博瘫痪,有人开心有人哭
  3. 更新linux内核版本,求问Linux最新内核版本以及发布日期。
  4. 中科视拓开源SeetaFace2
  5. Mathematica数据处理(11)--标签
  6. unity人物旋转移动代码_游戏诞生之日02 - 美术篇 快速制作人物动画
  7. 如何以可视化方式显示 Angular 应用构建后的 bundle 文件大小
  8. 使用Java 8进行分组,转换和归约
  9. game,match,competition,contest区别
  10. 【慎思堂】之JS牛腩总结
  11. 一文掌握关于Java数据结构所有知识点(欢迎一起完善)
  12. vmware-设置共享文件夹
  13. Linux下2号进程的kthreadd--Linux进程的管理与调度(七)
  14. VMware15 Pro激活密钥
  15. Windows批处理添加注释
  16. 【技术指标】MACD详解
  17. JVM--基础--19.4--垃圾收集器--Parallel Scavenge
  18. python数字识别kaggle论文_基于Python语言Kaggle的数据集分析
  19. 怎么用鸿蒙os系统,鸿蒙OS2.0系统怎么降级到EMUI11 鸿蒙OS2.0系统降级到EMUI11方法...
  20. Excel实现分时统计折线图

热门文章

  1. halcon学习之颜色与纹理
  2. 软件技术方案_智慧工地整体解决方案核心系统有哪些?
  3. 计算机国三网络技术,计算机国三网络技术.doc
  4. 简述linux中的passwd结构,51CTO博客-专业IT技术博客创作平台-技术成就梦想
  5. python可以取代excel吗_10条Python命令全面取代Excel,妈妈再也不用担心我变成黑眼圈大表哥啦...
  6. 连续arq协议的利用率_Chrome底层原理和HTTP协议 - 石吴玉
  7. pn532跳坑小指南
  8. React Native 仿天猫物流跟踪时间轴
  9. 在当前图纸中创建一个表格, AcDbTable 类
  10. Mvc.Ext.Net项目架构(一)