目录
多线程
-
什么是进程?什么是线程?
进程是一个应用程序(可以理解为是一个软件)。
线程是一个进程中的执行场景/执行单元。
一个进程可以启动多个线程。
-
对于java程序来说,当在DOS命令窗口中输入:java HelloWorld 回车之后
先启动JVM,而JVM就是一个进程。JVM先启动一个主线程调用main方法;
同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发,
一个是执行main方法的主线程,一个是垃圾回收线程。
-
进程和线程的关系
-
阿里巴巴:进程
- 马云:阿里巴巴的一个线程
- 童文红:阿里巴巴的一个线程
-
京东:进程
- 刘强东:京东的一个线程
- 妹妹:京东的一个线程
进程可以看作:现实生活中的公司、线程可以看作:公司中的某个员工
d
注意:
-
进程A和进程B的内存独立不共享(阿里巴巴和京东资源不会共享的)
魔兽游戏是一个进程、酷狗音乐是一个进程。这两个进程都是独立的,不共享资源。
-
线程A和线程B呢?在java语言中:
线程A线程B:堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发。
-
火车站:可以看作是一个进程。
火车站中的每一个售票窗口可以看作是一个线程。我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。所以多线程并发可以提高效率。
-
java中之所以有多线程机制,目的就是为了提高程序的处理效率
-
-
思考一个问题:
使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。main方法结束只是主线程结束了,主栈空了,其他的 栈/线程 可能还在压栈弹栈。
-
分析一个问题:对于单核的CPU来说,针对可以做到真正的多线程并发吗?
对于多核的CPU来说,真正的多线程并发是没问题的。
四核表示同一个时间点上,可以真正的有四个进程并发执行。
什么是真正的多线程并发?
t1线程执行t1的,t2线程执行t2的。t1不会影响t2、t2也不会影响t1。这叫做:真正的多线程并发。
单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发“的感觉,对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给人的感觉是:同时在执行多个线程。
线程A:播放音乐、线程B:运行魔兽游戏。
线程A和线程B频繁切换执行,人会感觉音乐一直在播放,游戏一直在运行,给我们的感觉是在同时并发的。
电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度之后,人的眼睛残生了错觉:感觉是动画的。这说明人类的反应速度很慢,就像一根钢针扎到手上,到最终感觉到疼,这个过程是需要”很长的“时间的,在这个期间计算机可以进行亿万次的循环,所以计算机的执行速度很快
-
分析以下程序有几个线程
package thread; /* 分析以下程序,除垃圾回收线程之外,有几个线程? 1个线程(因为程序只有一个栈) main begin m1 begin m2 begin m3 execute m2 over m1 over main over 一个栈中,自上而下的顺序一次逐行执行。 */ public class ThreadText01 { public static void main(String[] args) { System.out.println("main begin"); m1(); System.out.println("main over"); } private static void m1() { System.out.println("m1 begin"); m2(); System.out.println("m1 over"); } private static void m2() { System.out.println("m2 begin"); m3(); System.out.println("m2 over"); } private static void m3() { System.out.println("m3 execute"); } }
-
java语言中,实现线程有两种方式:
java支持多线程机制,并且java已经将多线程实现了,我们之需要继承就行了
-
第一种方式:编写一个类,直接继承Java.lang.Thread,重写run方法
//定义线程类 public class MyThread extends Thread{ public void run{ } } //创建线程对象 MyThread t = new MyThread(); //启动线程 t.start();
package thread; /* 实现线程的第一种方式: 编写一个类,直接继承java.lang.Thread,重写run方法。 怎么创建线程对象?new就行了 如何启动栈? 调用线程对象的start()方法 注意: 亘古不变的道理:方法体中的代码永远都是自上而下的顺序依次逐行执行的。 t.start()启动分支线程后这个程序的输出结果有这样的特点: 有先有后、有多有少 主线程 >> 80 分支线程------>27 主线程 >> 81 分支线程------>28 分支线程------>29 主线程 >> 82 分支线程------>30 主线程 >> 83 */ public class ThreadText02 { public static void main(String[] args) { //这里是main方法,这里的代码属于主线程,在主栈中运行。 //新建一个分支线程对象 MyThread t = new MyThread(); //启动线程 //t.run();//与mt.start()的区别:t.run()不会分配新的分支栈/不会启动线程(这种方式就是单线程。) //t.run()执行完成之后,才会执行剩余main方法的代码。不是线程并发,只是普通的调用方法 //start()方法的作用:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。 //这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了 //启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈) //run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的。 t.start(); /* 输出结果: 主线程 >> 80 分支线程------>27 主线程 >> 81 分支线程------>28 分支线程------>29 主线程 >> 82 分支线程------>30 主线程 >> 83 */ //这里的代码还是运行到主线程中 for(int i = 0; i < 100 ; i++){ System.out.println("主线程 >> "+i); } } } class MyThread extends Thread{ @Override public void run() { //编写程序,这段程序运行在分支线程中(分支栈) for(int i = 0; i<1000 ; i++){ System.out.println("分支线程------>"+i); } } }
线程的run内存图
线程的start内存图
-
第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法
//定义一个可运行的类 public class MyRunnable implements{ public void run(){ } } //创建线程对象 Thread t = new Thread(new MyRunnable()); //启动线程 t.start();
package thread; /* 实现线程的第二种方式,编写一个类实现java.lang.Runnable接口 */ public class ThreadText03 { public static void main(String[] args) { //创建一个可运行的对象 // MyRunnable r= new MyRunnable(); //将可运行的对象封装成一个线程对象 // Thread t = new Thread(r); Thread t = new Thread(new MyRunnable()); //启动线程 t.start(); for(int i = 0;i<1000;i++){ System.out.println("主线程--->"+i); } } } //者并不是一个线程类,是一个可运行的类。它还不是一个线程 class MyRunnable implements Runnable{ @Override public void run() { for(int i = 0;i<1000;i++){ System.out.println("分支线程--->"+i); } } }
采用匿名内部类的方式new
package thread; /* 采用匿名内部类 */ public class ThreadText04 { public static void main(String[] args) { //创建线程对象,采用匿名内部类的方式,虽然不能直接new一个接口对象, // 但在这里是创建了一个类继承了这个接口,且这个类没有名字,同时这个方法写在了方法里。所以叫:匿名内部类 //通过一个没有名字的类,new出来的对象。 Thread t = new Thread(new Runnable() { @Override public void run() { for(int i =0;i<1000;i++){ System.out.println("分支线程---->"+i); } } }); //启动线程 t.start(); for(int i =0;i<1000;i++){ System.out.println("主线程 >> "+i); } } }
-
注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他的类,更灵活。
-
-
线程的生命周期
开始状态、就绪状态、运行状态、阻塞状态、死亡状态
-
怎么获取当前线程对象、获取线程名字、修改线程名字、默认线程名字
package thread; /* 1、怎么获取当前线程对象? static Thread currentThread(); Thread t = Thread.currentThread(); 返回值t就是当前线程 2、获取线程的名字 线程对象.getName(); 3、修改线程对象的名字 线程对象.setName("线程名字"); 4、当线程没有设置名字的时候,默认的名字有什么规律?(了解一下) Thread-0 Thread-1 Thread-2 Thread-3 ······ */ public class ThreadText05 { public static void main(String[] args) { //currentThread就是当前线程对象。这个代码吹现在main方法中,所以当前线程就是主线程 Thread currentThread = Thread.currentThread(); System.out.println(currentThread.getName()); //main //创建线程对象 MyThread2 t = new MyThread2(); //设置线程的名字 //t.setName("mtmt"); //获取线程的名字 System.out.println(t.getName()); //默认的线程名字:Thread-0 //启动线程 t.start(); MyThread2 t2 = new MyThread2(); System.out.println(t2.getName()); //Thread-1 } } class MyThread2 extends Thread{ @Override public void run() { for(int i=0;i<100;i++){ //System.out.println("分支线程---->"+i); //currentThread就是当前线程对象。当前线程是谁呢? //t1线程启动,执行run方法,当前线程t1,当前线程的名字:t1线程的名字 //t2线程启动,执行run方法,当前线程t2,当前线程的名字:t2线程的名字 Thread currentThread = Thread.currentThread(); System.out.println(currentThread.getName()+"分支线程--->"+i); } } }
-
关于线程的sleep(long millis)
package thread; /* 关于线程的sleep(long millis); 1、静态方法 2、参数是毫秒 3、作用:让当前线程进入休眠,进入“阻塞状态”:放弃占用的CPU时间片,让给其他线程使用 这行代码出现在A线程中,A线程就会进入休眠;这行代码出现在B线程,B线程就会进入休眠 4、Thread.sleep()方法,可以做到这种效果: 间隔特定的时间,去执行一段特定的代码,每隔多久执行一次 */ public class ThreadText06 { public static void main(String[] args) { try { //让当前线程进入休眠:睡眠5秒。当前线程:主线程 Thread.sleep(1000*5); } catch (InterruptedException e) { e.printStackTrace(); } //5秒之后执行这里 System.out.println("Hello World"); for(int i = 0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"---->"+i); //睡眠1秒 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
-
sleep方法的一个面试题
package thread; /* 关于sleep方法的面试题 */ public class ThreadText07 { public static void main(String[] args) { //创建线程对象 Thread t = new MyThread3(); t.setName("t"); t.start(); //调用sleep方法 try { //问:该代码会让线程t进入休眠吗? 不会 t.sleep(1000);//在执行的时候还是会转换成:Thread.sleep(1000); //这行代码的作用是:让当前线程进入休眠,也就是说main进入休眠。因为这行代码是出现在main方法中,所以main线程睡眠 } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0 ;i<10000;i++){ System.out.println("-------------Hello world"); } } } class MyThread3 extends Thread{ @Override public void run() { for(int i = 0; i<10000;i++){ System.out.println(Thread.currentThread().getName()+"---->"+i); } } }
-
sleep如果睡太久了,终止线程睡眠的方法:
t.interrupt();方法
package thread; /* sleep睡眠太久了,如果希望线程在睡眠中醒来,如何叫醒这个线程。 这个不是终止线程的执行,而是终止线程的睡眠 */ public class ThreadText08 { public static void main(String[] args) { Thread t = new Thread(new MyRunnable2()); t.setName("t"); t.start(); //希望5秒之后,t线程醒来工作。(5秒之后,主线程空闲下来了。) 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 { Thread.sleep(1000*60*60*24*365); } catch (InterruptedException e) { //打印异常信息 e.printStackTrace(); } //一年之后才会执行这里 System.out.println(Thread.currentThread().getName()+"---->end"); //如果在run方法里调用需要解决异常的方法,只能try..catch。因为父类的run方法没有抛出异常,子类重写此方法时也不能抛出异常。 /* try { doOther(); } catch (Exception e) { e.printStackTrace(); }*/ } public void doSome() throws Exception {//在这里则可以上抛异常。 doOther(); } //其他方法可以throws public void doOther() throws Exception{ } }
强行终止线程的方法(已过时,不建议使用)
package thread; /* 在java中怎么强行终止一个线程的执行 线程对象.stop(); stop方法的缺点: 容易丢失数据。因为这种方式是直接将线程杀死了。线程里没有保存到数据将会丢失,不建议使用。 */ public class ThreadText09 { 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(); } } } }
合理终止线程执行的常用方法
package thread; /* 如何合理的终止一个线程的执行,这种方式是很常用的。 */ public class ThreadText10 { 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(); } //5秒后终止t线程 //想要什么时候终止t的执行,那么把标记修改为false,就结束了。 r.run =false; } } class MyRunnable4 implements Runnable{ //打一个布尔标记 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; } } } }
线程调度(了解)
-
常见的线程调度模式有哪些?
-
抢占式调度模型:
哪个线程的优先级比较高,抢到的CPU时间片概率就 高/多 一些。
(java采用的就是抢占式调度模型)
-
均分式调度模型
平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样。平均分配,一切平等。
有一些编程语言,线程调度模型采用的是这种方式
-
-
java中提供了哪些方法是与线程调度有关系的呢?
-
实例方法
-
void setPriority(int newPriority) 设置线程的优先级
-
int getPriority() 获取线程优先级
最低优先级:1 、默认优先级:5 、最高优先级:10
优先级比较高的线程获取的CPU时间片可能会多一些。(但也不完全是,大概率是多的)
-
-
静态方法
-
static void yield() 让位方法。暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。只是让当前线程让位,让给其他线程使用。
yield()方法的执行会让当前线程从"运行状态"回到"就绪状态"。
注意:回到"就绪状态"之后,有可能还会再次抢到
-
-
实力方法
-
void join() 合并线程
class MyThread1 extends Thread{ public void doSome(){ MyThread t = new MyThread(); t.join();//当前线程进入阻塞,t线程执行。直到t线程结束,当前线程才可以继续 } } class MyThread2 extends Thread{ }
-
-
-
关于线程的优先级
package thread; /* 关于线程的优先级 */ public class ThreadText11 { public static void main(String[] args) { //设置主线程的优先级:1 Thread.currentThread().setPriority(1); /* System.out.println("最低优先级"+Thread.MIN_PRIORITY);//1 System.out.println("默认优先级"+Thread.NORM_PRIORITY);//5 System.out.println("最高优先级"+Thread.MAX_PRIORITY);//10*/ //获取当前线程对象,获取当前线程的优先级 /* Thread currentThread = Thread.currentThread(); System.out.println(currentThread.getName()+"线程的默认优先级是:"+currentThread.getPriority());*/ //main线程的默认优先级是:5 Thread t = new Thread(new MyRunnable5()); t.setName("t"); t.start(); //t线程的默认优先级是:5 //优先级较高的,只是抢到的CPU时间片较多一些。(是指处于运行状态的时间多一些) //大概率方向更偏向于优先级比较高的 for(int i=0;i<10000;i++){ System.out.println(Thread.currentThread().getName()+"------>"+i); } } } class MyRunnable5 implements Runnable{ @Override public void run() { //获取线程的优先级 //System.out.println(Thread.currentThread().getName()+"线程的默认优先级是:"+Thread.currentThread().getPriority()); for(int i=0;i<10000;i++){ System.out.println(Thread.currentThread().getName()+"------>"+i); } } }
-
线程的让位方法
package thread; /* 让位,当前线程暂停,回到就绪状态,让给其他线程,让了之后还是会抢夺CPU时间片。 静态方法:Thread.yield(); */ public class ThreadText12 { public static void main(String[] args) { Thread t = new Thread(new MyRunnable6()); t.setName("t"); t.start(); for(int i = 1 ; i <= 10000 ;i++){ System.out.println(Thread.currentThread().getName()+"---->"+i); } } } class MyRunnable6 implements Runnable{ @Override public void run() { for(int i = 1 ; i <= 10000 ;i++){ //每100个让位一次。 if(i %100 == 0){ Thread.yield();//当前线程暂停一下,让位给主线程 } System.out.println(Thread.currentThread().getName()+"---->"+i); } } }
-
线程的合并
package thread; /* 线程合并 线程对象t.join(); t线程合并到当前线程中,当前线程受到阻塞,t线程执行结束之后,当前线程才会执行 在内存上体现为: 两个栈之间发生了等待关系,而不是两个栈合并。 */ public class ThreadText13 { public static void main(String[] args) { System.out.println("main begin"); Thread t = new Thread(new MyRunnable7()); t.setName("t"); t.start(); //合并线程 try { t.join(); //t线程合并到当前线程中,当前线程受到阻塞,t线程执行结束之后,当前线程(即主线程)才会执行 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("main over"); } } class MyRunnable7 implements Runnable{ @Override public void run() { for(int i =0;i<100000;i++){ System.out.println(Thread.currentThread().getName()+"---->"+i); } } }
多线程并发环境下,数据的安全问题
-
为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义、线程对象的创建、线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最总要的是:你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(※※※重点※※※)
-
什么时候,数据在多线程并发的情况下会出现安全问题呢?
多线程并发、有共享数据、共享数据有修改的行为
满足以上三个条件之后,就会存在线程安全问题
-
怎么解决线程安全的问题呢?
当多线程并发的情况下,有共享数据,并且这个数据会被修改,此时就存在线程安全问题。如何解决?
线程排队执行(不能并发),用排队执行解决线程安全问题。这种机制被称为:线程同步机制。
专业术语叫:线程同步。实际上就是线程不能并发了,线程必须排队执行
怎么解决线程安全问题?
使用”线程同步机制“
线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率,数据不安全,没有效率的事。
-
说到线程同步这里,涉及到两个专业术语:
-
异步编程模型:
线程:t1、t2。t1与t2的执行互不干扰,各自执行各自的,不发生等待
这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高)
异步就是并发
-
同步编程模型:
线程:t1、t2。t1执行时,必须等待t2执行结束才执行。或t2执行时,必须等t1执行结束才会执行。
两个线程之间发生了等待关系,这就是同步变成模型。(效率较低、线程排队执行)
同步就是排队
-
-
java中三大变量【重要】
-
实例变量:在堆中
-
静态变量:在方法区
-
局部变量:在栈中
以上三大变量中:
局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈)
局部变量在栈中,所以局部变量永远不会共享
实例变量在堆中,堆只有一个;静态变量在方法区中,方法区只有一个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
局部变量+常量:不会有线程安全问题
成员变量:可能会有线程安全问题。
-
-
如果使用局部便的话:
建议使用StringBuilder。因为局部变量不存在线程安全问题。选择StringBuilder。
StringBuffer效率比较低
非线程安全:ArrayList、HashMap、HashSet
线程安全:Vector、Hashtable
-
总结:synchronized的三种使用方法:
-
同步代码块 灵活
synchronized(线程共享对象){ 同步代码块 }
-
在实例方法上使用synchronized
表示共享对象一定是this,并且同步代码块是整个方法体
-
在静态方法上使用synchronized。
表示找类锁,类锁永远只有一把。就算创建了100个对象,类锁也只有一把。
对象锁:1个对象对应一把锁。
-
-
银行账户:不使用线程同步机制,出现的线程安全问题
-
账户类
package threadsafe; /* 银行账户 不使用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题 */ public class Account { //账号 private String actNo; //余额 private double balance; public String getActNo() { return actNo; } public void setActNo(String actNo) { this.actNo = actNo; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } public Account() { } public Account(String actNo, double balance) { this.actNo = actNo; this.balance = balance; } //取款的方法 public void withdraw(double balance){ //t1和t2并发这个方法。。(t1和t2是两个栈,两个栈操作堆中同意给对象) //取款之前的余额 double before = this.balance; //取款之后的余额: double after = before - balance; //在这里模拟一下网络延迟,100%会出问题 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //更新余额 //思考:t1执行到这里了,但还没有来得及执行这行代码,t2线程进来withdraw方法了,此时一定出问题 this.setBalance(after); } }
-
线程类
package threadsafe; public class AccountThread extends Thread{ //两个线程必须共享同一个账户 private Account act; //通过构造方法传递过来的账户对象 public AccountThread(Account act){ this.act = act; } @Override public void run() { //run方法的执行表示取款操作 //假设取款5000 double money = 5000; //取款 act.withdraw(money); System.out.println(Thread.currentThread().getName()+"对"+act.getActNo()+"账户取款"+money+"成功,余额:"+act.getBalance()); } }
-
测试类
package threadsafe; public class AccountText { public static void main(String[] args) { //创建账户对象,只建一个 Account act = new Account("act-001",10000); //创建两个线程,共享这个账户 Thread t1 = new AccountThread(act); Thread t2 = new AccountThread(act); //设置name t1.setName("t1"); t2.setName("t2"); //启动线程取款 t1.start(); t2.start(); /* 出现此问题: t1对act-001账户取款5000.0成功,余额:5000.0 t2对act-001账户取款5000.0成功,余额:5000.0 */ } }
-
-
使用线程同步机制,多线程对同一个账户进行取款,解决所出现的线程安全问题。
使用synchronized同步代码块解决
-
账户类
package threadsafe2; /* 银行账户 使用线程同步机制,多线程对同一个账户进行取款,解决所出现的线程安全问题 */ public class Account { //账号 private String actNo; //余额 private double balance; //对象 Object obj = new Object(); //实例变量。(Account对象是多线程共享的,Account对象中的实力变量obj也是共享的) public String getActNo() { return actNo; } public void setActNo(String actNo) { this.actNo = actNo; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } public Account() { } public Account(String actNo, double balance) { this.actNo = actNo; this.balance = balance; } //取款的方法 public void withdraw(double balance){ //以下这几行代码必须时线程排队的,不能并发。一个线程把这里的代码全部执行结束之后,另一个线程才可以进来 /* 线程同步机制的语法是: synchronized(需要某几个线程排队,这几个线程的共享对象){ //线程同步代码块 } synchronized后面小括号中传的这个“数据”是相当关键的。这个数据必须是多线程共享的数据,才能达到多县城排队。 ()中写什么? 那要看你想让哪些线程同步。 假设t1、t2、t3、t4、t5,有5个线程,你只希望t1、t2、t3排队,t4、t5不需要排队。怎么办? 你一定要在()中写一个t1 t2 t3共享的对象,而这个对象对于t4 t5来说不是共享的 这里的共享对象是:账户对象。账户对象是共享的,那么this就是共享对象吧!不一定是this,这只要是多线程共享的那个对象就行 在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁)。一个对象对应一把锁 以下代码的执行原理: 1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。 2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,找到之后,占有这把锁。 然后执行同步代码块中的代码,执行过程中保持占有,直到同步代码块中的代码执行完,才会归还这把锁 3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有“共享对象”的这把锁,如果此时这把锁被t1占有 t2只能在同步代码块前等待,直到t1执行结束,t1会归还这把锁,此时t2占有这把锁,进入同步代码块执行程序 这样就达到了线程排队执行。 注意:这个共享对象一定要选好,共享对象一定是需要排队执行的这些线程对象所共享的。 */ Object obj2 = new Object(); /* synchronized(this){//安全 //synchronized(obj){//安全 //synchronized("abc"){//"abc"在字符串常量池中 //安全,但所有线程都会同步 //synchronized(obj2){ //这样写就不安全了,因为obj2不是共享对象,是局部变量,每个线程执行过来都会new一个,不是共享的 double before = this.balance; double after = before - balance; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setBalance(after); }*/ double before = this.balance; double after = before - balance; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setBalance(after); } }
-
线程类
package threadsafe2; public class AccountThread extends Thread{ //两个线程必须共享同一个账户 private Account act; //通过构造方法传递过来的账户对象 public AccountThread(Account act){ this.act = act; } @Override public void run() { //run方法的执行表示取款操作 //假设取款5000 double money = 5000; //取款 //多线程同步这个方法 //这种方式也可以,只不过扩大了同步范围,效率更低了 //synchronized(this){//这里的this是指AccountThread对象,这个对象new了两次,AccountThread对象不共享,共享的是act对象 synchronized(act){ act.withdraw(money); } System.out.println(Thread.currentThread().getName()+"对"+act.getActNo()+"账户取款"+money+"成功,余额:"+act.getBalance()); } }
-
测试类
package threadsafe2; public class AccountText { public static void main(String[] args) { //创建账户对象,只建一个 Account act = new Account("act-001",10000); //创建两个线程,共享这个账户 Thread t1 = new AccountThread(act); Thread t2 = new AccountThread(act); //设置name t1.setName("t1"); t2.setName("t2"); //启动线程取款 t1.start(); t2.start(); } }
-
-
使用线程同步机制,多线程对同一个账户进行取款,解决所出现的线程安全问题。
在实例方法上使用synchronized
-
账户类
package threadsafe3; /* 银行账户 使用线程同步机制,多线程对同一个账户进行取款,解决所出现的线程安全问题 */ public class Account { //账号 private String actNo; //余额 private double balance; //对象 Object obj = new Object(); //实例变量。(Account对象是多线程共享的,Account对象中的实力变量obj也是共享的) public String getActNo() { return actNo; } public void setActNo(String actNo) { this.actNo = actNo; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } public Account() { } public Account(String actNo, double balance) { this.actNo = actNo; this.balance = balance; } //取款的方法 /* 在实例方法上可以使用synchronized 缺点: synchronized出现在实例方法上,一定锁的是this。没得挑,只能是this,不能是其他对象了。 所以这种方式不灵活 synchronized出现在实例方法上,表示整个方法体都要同步,扩大了同步范围 会导致执行效率降低,所以这种方式不常用。 优点: 代码写的少了,简洁了。 如果共享的对象就是this、并且需要同步的代码块就是整个方法体,建议使用这种方式 */ public synchronized void withdraw(double balance){ double before = this.balance; double after = before - balance; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setBalance(after); } }
-
线程类
package threadsafe3; public class AccountThread extends Thread{ //两个线程必须共享同一个账户 private Account act; //通过构造方法传递过来的账户对象 public AccountThread(Account act){ this.act = act; } @Override public void run() { //run方法的执行表示取款操作 //假设取款5000 double money = 5000; //取款 act.withdraw(money); System.out.println(Thread.currentThread().getName()+"对"+act.getActNo()+"账户取款"+money+"成功,余额:"+act.getBalance()); } }
-
测试类
package threadsafe3; public class AccountText { public static void main(String[] args) { //创建账户对象,只建一个 Account act = new Account("act-001",10000); //创建两个线程,共享这个账户 Thread t1 = new AccountThread(act); Thread t2 = new AccountThread(act); //设置name t1.setName("t1"); t2.setName("t2"); //启动线程取款 t1.start(); t2.start(); } }
-
-
synchronized总结
synchronized在实例方法中时,锁住的是这个对象。 当锁被占用时,另有线程想要执行此对象中带有synchronized关键字的方法时,需要等待;但如果是执行不带有synchronized的方法时,可以执行,因为调用不带synchronized的方法时,不需要锁就可以执行。 如果某个线程得到了对象锁,但是另一个线程还是可以访问没有进行同步的方法或者代码。进行了同步的方法(加锁方法)和没有进行同步的方法(普通方法)是互不影响的,一个线程进入了同步方法,得到了对象锁,其他线程还是可以访问那些没有同步的方法(普通方法)。当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,当某个线程获得对象的锁之后,只能阻止其他线程获得同一个锁。 synchronized在静态方法中时,是类锁。 类锁被占用时,方法中带有synchronized和static的方法需要等占用类锁的线程结束之后才可以调用。当使用的是同一个对象时,synchronized的方法只能同时执行一个,需要对象锁。当一个方法只有static时,这个方法执行时不需要锁。 即:对象锁中:只有带有synchronized的方法执行时需要锁才能执行,其他的方法不受锁的影响。 类锁中:同时带有synchronized和static关键字的需要类锁才能执行;只带有synchronized关键字的方法执行需要对象锁,其他方法的执行不需要等待锁的归还便可以执行。 总结: A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。 B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。 C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
-
synchronized的面试题1
package exam01; //面试题: doOther方法执行的时候需要等待doSome方法的结束吗? /* 不需要, 虽然doSome方法有synchronized,锁住的是同一个对象,但doOther方法没有synchronized。 只是需要执行执行带有synchronized的方法时才需要锁,执行doOther方法时不需要锁, 所以不需要等待doSome的执行。 */ 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"); } }
-
synchronized的面试题2
package exam02; //面试题: doOther方法执行的时候需要等待doSome方法的结束吗? /*需要,因为doOther方法待用有synchronized。同一个对象,锁只有一个,被doSome方法占用了。 所以需要等 */ public class Exam02 { 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 synchronized void doOther(){ System.out.println("doOther begin"); System.out.println("doOther over"); } }
-
synchronized的面试题3
package exam03; //面试题: doOther方法执行的时候需要等待doSome方法的结束吗? //不需要,因为传进去的对象不是共享的。各自使用各自的锁 public class Exam03 { public static void main(String[] args) { MyClass mc1 = new MyClass(); MyClass mc2 = new MyClass(); Thread t1 = new MyThread(mc1); Thread t2 = new MyThread(mc2); 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 synchronized void doOther(){ System.out.println("doOther begin"); System.out.println("doOther over"); } }
-
synchronized的面试题4
package exam04; //面试题: doOther方法执行的时候需要等待doSome方法的结束吗? /* 需要,虽然传进去的对象不是共享的,但synchronized出现在静态方法上是找类锁, 一个类只有一个锁,所有对象共用一个锁,doSome和doOther都是静态方法,都带有synchronized关键字 都需要锁才可以执行,所以需要等。 */ public class Exam04 { public static void main(String[] args) { MyClass mc1 = new MyClass(); MyClass mc2 = new MyClass(); Thread t1 = new MyThread(mc1); Thread t2 = new MyThread(mc2); Thread t3 = new MyThread(mc2); Thread t4 = new MyThread(mc2); t1.setName("t1"); t2.setName("t2"); t3.setName("t3"); t4.setName("t4"); t1.start(); try { Thread.sleep(1000);//这里保证t1先执行 } catch (InterruptedException e) { e.printStackTrace(); } t2.start(); try { Thread.sleep(1000);//这里保证t1先执行 } catch (InterruptedException e) { e.printStackTrace(); } t3.start(); try { Thread.sleep(1000);//这里保证t1先执行 } catch (InterruptedException e) { e.printStackTrace(); } t4.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(); } if(Thread.currentThread().getName().equals("t3")){ mc.doss(); } if(Thread.currentThread().getName().equals("t4")){ mc.dosa(); } } } class MyClass { //synchronized出现在静态方法上是找类锁,一个类只有一个类锁,同时只能执行一个既带有synchronized有又带有static关键字的方法 public synchronized static void doSome(){ System.out.println("doSome begin"); try { Thread.sleep(1000*10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("doSome over"); } public synchronized static void doOther(){ System.out.println("doOther begin"); System.out.println("doOther over"); } public synchronized void doss(){ System.out.println("doooooooooooooooooooooooooooooooooo"); try { Thread.sleep(1000*10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("doooooooooooooooooooooooooooooooo"); } public synchronized void dosa(){ System.out.println("daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); System.out.println("doaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); } }
-
死锁
死锁案例
package deadlock; /* 死锁代码要会写。 一般面试官会要求你写,只有会写的,才会在以后的开发中注意这个事情。 因为死锁很难调试 以下程序: o2对象锁被t2线程占用,而t2线程继续执行需要o1的对象锁,o1正被t1使用 o1对象锁被t1线程占用,而t1线程继续执行需要o2的对象锁,o2正被t2使用 t1线程等待t2线程、t2线程等待t1线程,就发生了死锁。 结论: synchronized在开发中最好不要嵌套使用,一不小心就可能发生死锁现象。 */ public class DeadLock { public static void main(String[] args) { Object o1 = new Object(); Object o2 = new Object(); //t1和t2这两个线程共享:o1、o2 Thread t1 = new MyThread1(o1,o2); Thread t2 = new MyThread2(o1,o2); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } } class MyThread1 extends Thread{ Object o1; Object o2; public MyThread1(Object o1, Object o2) { this.o1 = o1; this.o2 = o2; } @Override public void run() { synchronized (o1){ System.out.println("1"); //占有o1的对象锁之后睡一秒 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //o2被t2线程占用,而t2线程继续执行需要o1的对象锁。 synchronized (o2){ System.out.println("2"); } } } } class MyThread2 extends Thread{ Object o1; Object o2; public MyThread2(Object o1, Object o2) { this.o1 = o1; this.o2 = o2; } @Override public void run() { synchronized (o2){ System.out.println("3"); //占有o2的对象锁后,睡一秒。 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o1){ //o1对象锁被t1线程占用,而t1线程的继续执行需要o2的对象锁 System.out.println("4"); } } } }
-
以后开发中应该怎么解决线程安全问题?
-
是一上来就选择线程同步(synchronized)吗?
不是,synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制
-
第一种方案:
尽量使用局部变量代替“实例变量“和”静态变量“,
-
第二种方案:
如果必须是实例变量,那么可以考虑拆功能键多个对象,这样实例变量的内存就不共享了。
(一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了)
-
第三种方案:
如果不能使用局部变量,对象也不能创建多个,这个使用就只能选择synchronized(线程同步机制)了
-
-
守护线程概述
-
java语言中线程分为两大类:
-
用户线程
-
守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)
-
-
守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束。守护线程自动结束
-
主线程main方法是一个用户线程
-
守护线程用在什么地方呢?比如:
每天00:00的时候系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程,一直在那里看着,每到00:00的时候就备份一次。所有的用户线程如果都结束了,守护线程自动退出,没有必要进行数据备份了。
package thread; /* 守护线程 启动线程之前,将线程设置为守护线程 线程对象t.setDaemon(true); */ public class ThreadText14 { 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{ @Override 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中其实可以采用多种方式实现:
- 可以使用sleep方法,睡眠,设置睡眠时间,每到一个固定的时间醒来,执行任务。这种方式是最原始的定时器(比较low)
- 在Java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的
- 在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器任务。
package thread; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; /* 使用定时器指定定时任务 */ public class TimerText { public static void main(String[] args) throws ParseException { //创建计时器对象 Timer timer = new Timer(); //Timer timer = new Timer(true); //守护线程方式 //指定定时任务 //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次) SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date firstTime = sdf.parse("2021-02-25 14:04:40"); timer.schedule(new LogTimerTask() , firstTime , 1000*10);//每10秒执行一次 /* 2021-04-25 14:04:40成功完成了一次数据备份 2021-04-25 14:04:50成功完成了一次数据备份 2021-05-25 14:05:00成功完成了一次数据备份 2021-05-25 14:05:10成功完成了一次数据备份 2021-05-25 14:05:20成功完成了一次数据备份 */ //可以采用匿名内部类的方式 timer.schedule(new TimerTask() { @Override public void run() { //代码 } },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+"成功完成了一次数据备份"); } }
-
实现线程的第三种方式:实现Callable接口。(JDK新特性)
这种方式实现的线程可以获取线程的返回值。之前讲解的那种方式是无法获取线程返回值的,因为run方法返回void
思考:
系统委派一个线程去执行一个任务,该线程执行完成之后,可能会有一个执行结果,我们怎么才能拿到这个执行结果呢?
使用第三种方式实现:实现Callable接口方式
package thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; //JUC包下的,属于java并发包,老JDK中没有这个包。新特性 /* 实现线程的第三种方式: 实现Callable接口 这种方式的优点:可以获取到线程的执行结果。 这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程收到阻塞 */ public class ThreadText15 { public static void main(String[] args) throws ExecutionException, InterruptedException { //第一步:创建一个“未来任务类”对象 //参数非常重要,需要给一个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 over"); int a = 100; int b =200; return a+b; //自动装箱:300结果变成Integer } }); //创建线程对象 Thread t =new Thread(task); //启动线程 t.start(); //这里是main方法,在主线程中。 //在主线程中,怎么获取t线程的返回结果? //get()方法的执行会导致“当前线程阻塞” Object obj = task.get(); System.out.println("线程执行结果:"+obj); //main方法这里的代码想要执行必须等待get()方法的结束。 //而get方法可能需要很久,因为get()方法是为了拿另一个线程的返回值,需要等这个线程执行完。 System.out.println("helloworld"); } }
-
关于Object类中的wait方法和notify方法(生产者和消费者模式)
-
第一:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是Object类中自带的
-
第二:wait()方法作用:
Object o = new Object(); o.wait();
表示: 让正在o对象上活动的线程进入等待状态,无限期等待,直到被唤醒为止。
o.wait()方法的调用,会让”当前线程“(正在o对象上活动的线程)进入等待状态
-
第三:notify()方法的作用:
Object o = new Object(); o.notify();
表示:唤醒正在o对象上等待的线程
还有一个notifyAll()方法:这个方法是唤醒o对象上处于等待的所有线程
-
-
生产者和消费者模式
package thread; import java.util.ArrayList; import java.util.List; /* 1、使用wait方法和notify方法实现“生产者和消费者模式” 2、什么是“生产者和消费者模式”? 生产线程负责生产、消费线程负责消费。 生产线程和消费线程要求达到均衡。 这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法 3、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。 4、wait方法和notify方法建立在线程同步的基础上,因为多线程要同时操作一个仓库,有线程安全问题 5、wait方法:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的对象锁 6、notify方法:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。 7、模拟这样一个需求: 仓库采用List集合,List集合中假设只能存储一个元素,一个就表示仓库满了 如果List集合元素个数是0,就表示仓库满了 保证List集合中永远都是最多存储一个元素。 必须做到:生产一个消费一个 */ public class ThreadText16 { 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){ synchronized (list){ if(list.size()>0){//大于0说明仓库中已经有一个元素了 //当前线程进入等待状态,并且释放Producer之前占用的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){//=0说明仓库中已经空了,消费者线程等待,释放list锁 try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //程序能执行到这里,说明仓库有东西,进行消费 Object obj =list.remove(0); System.out.println(Thread.currentThread().getName()+"---->"+obj); //唤醒生产者生产 list.notify(); } } } }
-
作业
package thread; /* 1、使用生产者和消费者模式实现,交替输出: 假设只有两个线程,输出以下结果: t1-->1 t2-->2 t1-->3 t2-->4 t1-->5 t2-->6 .... 要求:必须交替,并且t1线程负责输出奇数。t2线程负责输出偶数。 两个线程共享一个数字,每个线程执行时都要对这个数字进行:++ public class Num { int i; } synchronized(num){ if(num是奇数){ num.wait(); } // 输出偶数 // notifyAll() } synchronized(num){ if(num是偶数){ num.wait(); } // 输出奇数 // notifyAll(); } */ public class ThreadText17 { public static void main(String[] args) { //创建一个数 Num num = new Num(1); //创建两个线程,公用num对象 Thread ji = new Thread(new JiShu(num)); Thread ou = new Thread(new OuShu(num)); ji.setName("奇数"); ou.setName("偶数"); ji.start(); ou.start(); } } class Num{ int i; public Num() { } public Num(int i) { this.i = i; } public int getI() { return i; } public void setI(int i) { this.i = i; } } //输出奇数的线程 class JiShu implements Runnable{ private Num num; public JiShu(Num num) { this.num = num; } @Override public void run() { while (true){ synchronized (num){ //如果num是偶数,则等待 if(num.i % 2 == 0){ try { num.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //执行到这里,说明是奇数,输出后自加1。 System.out.println(Thread.currentThread().getName()+"--->"+num.i++); num.notify(); } } } } //输出偶数的线程 class OuShu implements Runnable{ private Num num; public OuShu(Num num) { this.num = num; } @Override public void run() { while (true){ synchronized (num){ //如果num是奇数,则等待 if(num.i % 2 != 0){ try { num.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //执行到这里,说明是偶数 System.out.println(Thread.currentThread().getName()+"--->"+num.i++); num.notify(); } } } }