什么是进程,什么是线程:
进程是一个应用程序,线程是一个进程中的执行场景或执行单元,一个进程可以启动多个线程。java中之所以有多线程机制,目的是为了提高程序的处理效率。
在java语言中,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈。
使用了多线程机制之后,main方法结束,程序可能也不会结束。main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。
在java语言中,实现线程一般有两种方式:
第一种方式:
编写一个类,直接继承java.lang.Thread,重写run()方法。
创建分线程对象,调用start()方法。
start()方法作用:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
启动成功的线程会自动调用run方法,并且run方法在分支栈的底部(压栈)。run方法在分支栈的栈底部,main方法在主线程的栈底部。run和main是平级的。
//定义线程类
public class MyThread extends Thread{
//重写run方法
public void run(){
}
}
//创建线程对象
MyThread t=new MyThread();
//启动线程
t.start();
第二种方式:
编写一个类,实现java.lang.Runnable接口,实现run方法。比较常用。
//定义线程类
public class RunnableTest implements Runnable{
//重写run方法
public void run(){
}
}
//创建一个可运行的对象
RunnableTest rt=new RunnableTest();
//将一个可运行的对象封装成一个线程对象
Thread th=new Thread(rt);
//启动线程
th.start();
采用匿名内部类方式:
public class ThreadTest04 {
public static void main(String[] args) {
//采用匿名内部类方式,创建线程对象
Thread t=new Thread(new Runnable() {
@Override
public void run() {
for (int i=1;i<101;i++){
System.out.println("分支线程-->"+i);
}
}
});
//启动线程
t.start();
for (int i=1;i<101;i++){
System.out.println("main-->"+i);
}
}
}
void setName(String name); //修改线程名字
String getName() ; //获取线程名称,默认为Thread-0
static Thread currentThread(); //返回当前线程对象
代码演示:
public class ThreadTest05 {
public static void main(String[] args) {
//创建线程对象
MyThread2 t1=new MyThread2();
// t.setName("xiaoma");
//获取线程的名字
String tName=t1.getName();
System.out.println(tName);
MyThread2 t2=new MyThread2();
System.out.println(t2.getName());
//启动线程
t1.start();
System.out.println(Thread.currentThread().getName());
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i=0;i<100;i++){
//当t1线程执行run方法,那么当前线程就是t1
//当t2线程执行run方法,那么当前线程就是t2
Thread currentThread=Thread.currentThread();
System.out.println(currentThread.getName()+"---->"+i); //Thread-0--->1
}
}
}
关于线程的sleep()方法:
//1. 静态方法: Thread.sleep(1000);
//2. 参数是毫秒
//3. 作用:让当前线程进入休眠状态,进入“堵塞状态”,放弃占用CPU时间片,让给其他线程使用
//4. Thread.sleep()方法,可以做到间隔特定的时间,去执行一段特定的代码,每隔多久执行一次
static void sleep(long millis);
代码演示:
/*
关于线程的sleep()方法
* */
public class ThreadTest06 {
public static void main(String[] args) {
/* try {
//让当前线程进入休眠,睡眠5秒
Thread.sleep(1000 *5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后执行这里代码
System.out.println("xiaoma");*/
for(int i=0;i<10;i++){
//每隔一秒,输出一次
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
问题:这行代码会让线程t进入休眠状态嘛?
不会,sleep()是静态方法和t线程没关系,在执行的时候还是会转为Thread.sleep(1000*5)。
这行代码的作用是:让当前线程进入休眠,也就是说让main线程进入休眠。
public class ThreadTest07 {
public static void main(String[] args) {
//创建线程对象
Thread t=new MyThread3();
t.setName("t");
t.start();
//调用sleep()方法
try {
//问题:这行代码会让线程t进入休眠状态嘛? 不会
//sleep()是静态方法和t线程没关系
// 在执行的时候还是会转为Thread.sleep(1000*5)
//这行代码的作用是:让当前线程进入休眠,也就是说让main线程进入休眠
t.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后这里才会执行
System.out.println("hello world");
}
}
class MyThread3 extends Thread{
public void run(){
for (int i=0;i<10000;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
void interrupt() //依靠了java的异常处理机制,终断睡眠,会打印出异常信息
代码演示:
/*
sleep睡眠太久了,如何唤醒一个正在睡眠的线程。
* */
public class ThreadTest08 {
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable2());
t.setName("t");
t.start();
//希望5秒之后t线程醒来
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断t线程的线程(这种终断睡眠的方式,依靠了java的异常处理机制。)
t.interrupt(); //干扰
}
}
class MyRunnable2 implements Runnable{
//重点:run()当中的异常不能throws ,只能try catch
//因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---> begin");
try {
// 睡眠1年
Thread.sleep(1000*60*60*24*365);
} catch (InterruptedException e) {
//打印异常信息
e.printStackTrace();
}
//1年之后才执行这个
System.out.println(Thread.currentThread().getName()+"---> end");
}
}
使用stop()方法:已过时,不建议使用,容易丢失数据。因为这种方式是直接将线程杀死,线程没有保存的数据会丢失。
代码演示:
public class ThreadTest09 {
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable3());
t.setName("t");
t.start();
//模拟5秒睡眠
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后强行终止t线程
t.stop(); //已过时(不建议使用)
}
}
class MyRunnable3 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) {
e.printStackTrace();
}
}
}
}
在自定义的线程类中打一个boolean标记,想什么时候终止t的执行,就时候把标记修改为false。这种方式是很常用的。
代码演示:
/*
如何合理的终止一个线程的执行,这种方式是很常用的
* */
public class ThreadTest10 {
public static void main(String[] args) {
MyRunnable4 r= new MyRunnable4();
Thread t=new Thread(r);
t.setName("t");
t.start();
//模拟5秒
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程
//想什么时候终止t的执行,就什么时候把标记修改为false,就行了
r.run=false;
}
}
class MyRunnable4 implements Runnable{
//打一个boolean标记
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) {
e.printStackTrace();
}
}
else {
//return就结束了,你在结束之前还有什么没保存的,在这里可以保存
//save....
//终止线程执行
return;
}
}
}
}
常见的线程模型
java中提供哪些方法和线程调度有关:
int getPriority(); //获取线程优先级
void setPriority(int newPriority); //设置线程优先级
static void yield() ; // 暂停当前正在执行的线程对象,并执行其他线程。
void join() ; //合并线程(当前线程进入堵塞,等待调用的线程执行完)
注意:线程最低优先级是1,默认优先级是5,最高优先级是10。
yield()方法不是堵塞方法。让当前线程让位,让给其他线程使用。yield()方法的执行会让当前线程从“运行状态”回到“就绪状 态,有可能再抢到CPU时间片。
join()方法演示:
class MyThread1 extends Thread{
public void doSome(){
MyThread2 t=new MyThread2();
t.join(); //当前线程进入堵塞,t线程执行,直到t线程结束,当前线程才可以执行。
}
}
class MyThread2 extends Thread{
}
为什么这个是重点:
以后在开发中,我们的项目都是运行在服务器当中的,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了,这些代码我们都不需要编写。你要知道的是,你编写的代码需要放到一个多线程的环境下运行,你更需要关注的是,这些数据在多线程并发的环境下,是否是安全的。
什么时候数据在多线程并发的环境下会存在安全问题:
存在安全问题的三个条件:
如何解决线程安全问题:
**线程排队执行,不能并发。用排队执行解决线程安全问题,这种机制被称为线程同步机制。**线程同步会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。
说到线程同步这块,就涉及到两个专业术语:
/*
* 银行账户
* */
public class Account {
//账户
private String actno;
//余额
private BigDecimal balance;
public Account() {
}
public Account(String actno, BigDecimal balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"actno='" + actno + '\'' +
", balance=" + balance +
'}';
}
//取款方法
public void withdraw(BigDecimal money){
// t1和t2并发这个方法....(t1和t2两个栈,两个栈操作堆中同一个对象。)
//取款之前的余额
BigDecimal before=this.getBalance();
//取款之后的余额
BigDecimal after=before.subtract(money);
// 在这里模拟一下网络延迟,100%会出现问题
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新余额
//假设t1执行到这里了,但还没有来得及执行这行代码,t2线程进来withdraw方法了。此时一定出问题。
this.setBalance(after);
}
}
/*
线程类
*/
public class AccountThread extends Thread{
// 两个线程必须共享同一个账户对象
private Account act;
//通过构造方法传递过来账户对象
public AccountThread(Account act){
this.act=act;
}
public void run(){
//run方法的执行表示取款操作
//假设取款5000
BigDecimal money=new BigDecimal(5000);
//取款
//多线程并发执行这个方法
act.withdraw(money);
System.out.println(Thread.currentThread().getName()+"对"+"账户"+act.getActno()+"取款成功,余额"+act.getBalance());
}
}
/*
测试类
*/
public class Test {
public static void main(String[] args) {
//创建账户对象
Account act=new Account("act-001",new BigDecimal(10000));
//创建两个线程
Thread t1=new AccountThread(act);
Thread t2=new AccountThread(act);
//设置线程name
t1.setName("t1");
t2.setName("t2");
//启动线程取款
t1.start();
t2.start();
}
}
如图:运行结果出问题,都取款5000,余额还剩5000:
想不出现上面代码问题,以下这几行代码必须是线程排队的,不能并发,一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
线程同步机制的语法是:
synchronized(){
//线程同步代码块。
}
注意:synchronized后面小括号中传的这个 “数据”是相当关键的。这个数据必须是多线程共享的数据,才能达到多线程排队。
()中写什么?假设t1,t2,t3,t4,t5有5个线程,你只希望t1,t2,t3排队,t4,t5不需要排队。怎么办
那要看你想让哪些线程同步。你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。
这里的共享对象是:账户对象。那么this就是账户对象。
改进Account类的withdraw()方法,代码如下:
//取款方法
public void withdraw(BigDecimal money){
//以下这几行代码必须是线程排队的,不能并发
//一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
/*
* 线程同步机制的语法是:
* synchronized(){
* //线程同步代码块。
* }
*
* synchronized后面小括号中传的这个 “数据”是相当关键的。
* 这个数据必须是多线程共享的数据,才能达到多线程排队。
*
* ()中写什么?
* 那要看你想让哪些线程同步。假设t1,t2,t3,t4,t5有5个线程
* 你只希望t1,t2,t3排队,t4,t5不需要排队。怎么办
* 你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。
*
* 这里的共享对象是:账户对象。
* 账户对象是共享的,那么this就是账户对象。
* 这里不一定是this,这里只要是多线程共享的那个对象就行。
* */
synchronized (this){
//取款之前的余额
BigDecimal before=this.getBalance();
//取款之后的余额
BigDecimal after=before.subtract(money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新余额
this.setBalance(after);
}
}
synchronized是如何保证线程安全的:
在java语言中,任何一个对象都有 “一把锁”,其实这把锁就是一个标记。(只是把它叫做锁),100个对象,100把锁,1个对象,1把锁。
上面代码的执行原理:
假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。若t1先执行了,遇到synchronized,这个时候自动找 "后面共享对象"的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中t1一直都是占有这把锁的。t2此时都是在同步代码块外面等待t1的。直到同步代码块代码结束,t1才会归还这把锁。此时t2才会获得这把锁,然后t2占有这把锁之后,才进入同步代码块,执行程序。
这样就达到了线程排队执行。要注意的是这个共享对象一定要选好,这个共享对象一定是你需要排队执行的这些线程对象所共享的。
注意:共享对象中的实例变量obj也是共享的。注意不能是局部变量。若写字符串“abc”时(不可变,存在字符串常量池中,共享),可以,但现有线程都会共享。
如:
public class Account {
//账户
private String actno;
//余额
private BigDecimal balance;
//对象
//实例变量(Account对象是多线程共享的,Account对象中的实例变量obj也是共享的)
Object obj=new Object();
....
// synchronized (this){
synchronized (obj){
//取款之前的余额
BigDecimal before=this.getBalance();
//取款之后的余额
BigDecimal after=before.subtract(money);
...
java中有三大变量:
以上三大变量中,局部变量永远都不会存在线程安全问题,因为局部变量在栈中,所有局部变量永远都不会共享。(一个线程一个栈)。实例变量在堆中,堆只有一个。静态变量在方法区中,方法区只有一个。堆和方法区都是多线程共享的。
总结:
局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。
可以在实例方法上使用synchronized,不常用。
缺点:
优点:
...
//可以在实例方法上使用synchronized,synchronized出现在实例方法上,一定锁的是this
public synchronized void withdraw(BigDecimal money){
//取款之前的余额
BigDecimal before=this.getBalance();
//取款之后的余额
BigDecimal after=before.subtract(money);
.....
如果使用局部变量的话,建议使用StringBuilder。因为局部变量不存在线程安全问题。选择StringBuffer效率比较低。
另外:ArrayList是非线程安全的,Vector是线程安全的。HashMap,HashSet是非线程安全的,Hashtable是线程安全的。
synchronized的三种写法:
同步代码块:灵活
synchronized(线程共享对象){
同步代码块;
}
在实例方法上使用synchronized:需要共享对象一定是this,并且同步代码块是整个方法体。
public synchronized void withdraw(BigDecimal money){
同步代码块;
}
在静态方法上使用synchronized:表示找类锁。类锁永远只有一把。就算创建了100个对象,类锁还是一把。保证静态变量安全
以下代码,doOther方法执行的时候需要等待doSome方法结束嘛?
/*
* 面试题: doOther方法执行的时候需要等待doSome方法结束嘛?
* 不需要,因为doOther()方法没有synchronized ,t2不需要等待。
* */
public class Exam01 {
public static void main(String[] args) {
Myclass mc=new Myclass();
;
Thread t1=new MyThread(mc);
Thread t2=new MyThread(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000); //保证t1先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread{
private Myclass mc;
public MyThread(Myclass mc){
this.mc=mc;
}
public void run(){
if(Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if (Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class Myclass{
public synchronized void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000 *10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
....
class Myclass{
public synchronized void doSome(){
....
}
public synchronized void doSome(){
.....
}
}
.....
3.不需要,因为this指向的不是同一个对象,锁不同。
public class Exam01 {
public static void main(String[] args) {
Myclass mc1=new Myclass();
Myclass mc2=new Myclass();
Thread t1=new MyThread(mc1);
Thread t2=new MyThread(mc2);
....
}
}
....
class Myclass{
public synchronized void doSome(){
....
}
public synchronized void doSome(){
.....
}
}
.....
4.需要,因为synchronized出现在静态方法上是类锁,不管创建几个对象,类锁只有1把。
public class Exam01 {
public static void main(String[] args) {
Myclass mc1=new Myclass();
Myclass mc2=new Myclass();
Thread t1=new MyThread(mc1);
Thread t2=new MyThread(mc2);
....
}
}
....
class Myclass{
//synchronized出现在静态方法上是类锁
public synchronized static void doSome(){
....
}
public synchronized static void doSome(){
.....
}
}
.....
什么是死锁:
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
代码实现:
/*
* 死锁代码要会写
*
* */
public class DeadLock {
public static void main(String[] args) {
Object o1=new Object();
Object o2=new Object();
//t1和t2两个线程共享o1,o2
Thread t1=new MyThread01(o1,o2);
Thread t2=new MyThread02(o1,o2);
t1.start();
t2.start();
}
}
class MyThread01 extends Thread{
Object o1;
Object o2;
public MyThread01(Object o1,Object o2){
this.o1=o1;
this.o2=o2;
}
public void run(){
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread02 extends Thread{
Object o1;
Object o2;
public MyThread02(Object o1,Object o2){
this.o1=o1;
this.o2=o2;
}
public void run(){
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
我们在以后开发中应该怎么解决线程安全问题?
概述:
java语言中线程分为两大类。一种是用户线程,一种是守护线程(后台线程)。其中具有代表性的就是垃圾回收线程(守护线程)。
守护线程特点**:一般守护线程是一个死循环**,所有的用户线程只要结束,守护线程自动结束。主线程main方法是一个用户线程。
守护一般用在什么地方:如每天00:00的时候,系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程,一直去监听,每到00:00的时候就备份一次
如何设置线程为守护线程:
void setDaemon(boolean on) //boolean为true时,即该线程为守护线程
/*
* 守护线程
* */
public class ThreadTest14 {
public static void main(String[] args) {
Thread t=new BakDataThread();
t.setName("备份数据的线程");
//启动之前将备份线程设置为守护线程
t.setDaemon(true);
t.start();
//主线程: 主线程是用户线程
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class BakDataThread extends Thread{
public void run(){
int i=0;
//即使是死循环,但由于该线程是守护线程,当用户线程结束,守护线程自动结束
while (true){
System.out.println(Thread.currentThread().getName()+"-->"+(++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
定时器的作用:
间隔特定的时间,执行特定的程序。比如每周要进行银行账户的走账操作,每天要进行数据的备份操作。在实际的开发中,每隔多久执行一段特定的程序,这种需求很常见。在java中有多种方式实现。
如何实现定时器:
//创建定时器对
Timer timer=new Timer();
//指定定时任务
//timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
//其中定时任务可写一个类继承TimerTask接口,或者使用匿名内部类的方式
代码如下
/*
* 使用定时器指定定时任务
* */
public class TimerTest {
public static void main(String[] args) throws ParseException {
//创建定时器对象
Timer timer=new Timer();
//Timer timer1=new Timer("ma",true);以守护线程的方式创建一个叫ma的定时器
//指定定时任务
//timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime=sdf.parse("2020-07-30 10:40:00");
//timer.schedule(new LogTimerTask(),firstTime,1000*10);
//采用匿名内部类的方式
timer.schedule(new TimerTask() {
@Override
public void run() {
//编写你需要执行的任务
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime=sdf.format(new Date());
System.out.println(strTime+":成功完成一次数据备份");
}
},firstTime,1000*10);
}
}
//编写一个定时任务类
//假设这是一个记录日志的定时任务
/*
class LogTimerTask extends TimerTask {
@Override
public void run() {
//编写你需要执行的任务
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime=sdf.format(new Date());
System.out.println(strTime+":成功完成一次数据备份");
}
}*/
创建一个FutureTask未来任务类对象,通过在Callable接口的实现类中重写call()方法,来实现多线程。这种实现的线程可以通过**get()**方法获取线程的返回值。之前的两种方法都是无法获取返回值的,因为run方法返回void。
优点:可以获取线程执行结果。
缺点:效率比较低,在获取t线程的执行结果的时候,当前线程会受堵塞。
代码演示:
/*
* 实现线程的第三种方式,实现Callable接口
* */
public class ThreadTest15 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1. 创建一个未来任务类对象。
// 参数非常重要,需要给一个Callable接口的实现类对象
FutureTask task=new FutureTask(new Callable() {
@Override
public Object call() throws Exception { //call()方法相当于run()方法,这个有返回值
// 线程执行一个任务,执行之后可能会有一个执行结果
//模拟执行
System.out.println("call method begin");
Thread.sleep(1000*10);
System.out.println("call method end");
int a=100;
int b=200;
return a+b; //自动装箱
}
});
//创建线程对象
Thread t=new Thread(task);
//启动线程
t.start();
//在主线程中,如何获取t线程的返回结果。
//get()方法的执行会导致当前线程阻塞。
System.out.println("线程执行结果"+task.get());
//main方法这里的程序要想执行,必须等待get()方法结束
// 而get()方法可能需要很久,因为get()方法是为了拿另一个线程的执行结果
//另一个线程执行是需要时间的
System.out.println("hello world");
}
}
概述:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是Object类中自带的。wait方法和notify方法不是通过线程对象调用。
两个方法的作用:
wait()方法作用:
Object o=new Object();
o.wait();
//表示 让正在o对象上活动的线程进入无期限等待状态,直到被notify()或notifyAll()唤醒
notify()方法作用:唤醒正在等待的线程。
分析如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hEPE6CFP-1596362088799)(C:\Users\user\Desktop\java分析图\生产者和消费者.png)]
代码实现:
/*
* 使用wait()方法和notify()方法实现生产者和消费者模式
* 1. 生产线程负责生产,消费线程负责消费,生产线程和消费线程要达到均衡这是一种特殊的业务需求
* 在这种特殊的情况下,需要使用wait()和notify()方法。
* 2. wait和notify不是线程对象的方法,是普通java对象都有的方法。
* 3. wait方法和notify方法建立在线程同步的基础之上,因为多线程要同时操作一个仓库,
* 有线程安全问题。
* 4. wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放t对象之前占
* 有的o对象的锁
* 5. notify()作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前
* 占用的锁
*
* 6. 模拟这样的需求:
* 仓库我们采用list集合,List集合中假设只能存储1个元素。1个元素就表示仓库满了,
* 如果List集合中元素个数是0,就表示仓库是空的。保证List集合中是最多存储1个元素。
* 必须做到这样效果:生产一个,消费一个
* */
public class ThreadTest16 {
public static void main(String[] args) {
//创建一个仓库对象,共享的
List list=new ArrayList();
//创建两个线程对象
//生产者线程
Thread t1=new Thread(new Producer(list));
//消费者线程
Thread t2=new Thread(new Consumer(list));
t1.setName("生产者线程");
t2.setName("消费者线程");
t1.start();
t2.start();
}
}
//生产线程
class Producer implements Runnable{
//仓库
private List list;
public Producer(List list){
this.list=list;
}
@Override
public void run() {
//一直生产(使用死循环模拟一直生产)
while (true){
//表示给仓库对象list加锁
synchronized (list){
if(list.size()>0){ //大于0 说明仓库已经有一个元素了,仓库已满
//当前线程进入等待状态,并且释放list仓库的锁
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序能够执行到这里说明仓库是空的,可以生产
Object obj=new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName()+"生产了--->"+obj);
//唤醒消费者进行消费。
list.notify();
}
}
}
}
// 消费线程
class Consumer implements Runnable{
//仓库
private List list;
public Consumer(List list){
this.list=list;
}
@Override
public void run() {
//一直消费
while (true){
synchronized (list){
if(list.size()==0){ //仓库已经空了
try {
//仓库空了,消费者线程等待,释放lsit集合的锁
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序能够执行到此,说明仓库中有数据进行消费
Object obj=list.remove(0);
System.out.println(Thread.currentThread().getName()+"消费了 --->"+obj);
//唤醒生产者生产
list.notify();
}
}
}
}
public class ThreadTest17 {
public static void main(String[] args) {
Num num=new Num();
Thread t1=new Thread(new ji(num));
Thread t2=new Thread(new ou(num));
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class Num{
int i=1;
}
class ji implements Runnable{
private Num num ;
public ji(Num num){
this.num=num;
}
@Override
public void run() {
while (true){
synchronized (num){
if(num.i%2==0){
try {
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"-->"+num.i);
num.i++;
num.notifyAll();
}
}
}
}
class ou implements Runnable{
private Num num;
public ou(Num num){
this.num=num;
}
@Override
public void run() {
while (true){
synchronized (num){
if(num.i%2!=0){
try {
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"-->"+num.i);
num.i++;
num.notifyAll();
}
}
}
}