Thread类的子类: MyThread java
//1.建立一个Thread类的子类 public class MyThread extends Thread{ //2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要作什么?) @Override public void run() { for (int i = 0; i <20 ; i++) { System.out.println("run:"+i); } } }
主线程: MyThread 程序员
public class MainThread { 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); } } }
结果:随机性打印编程
main:0 run:0 main:1 run:1 main:2 run:2 main:3 run:3 main:4 main:5 。。。。
原理:安全
一、JVM执行 MainThread类的main 方法时,知道OS开辟了一条main方法通向CPU的路径。服务器
这个路径叫作main线程,主线程。CPU经过这个路径能够执行main方法。多线程
二、JVM执行到 mt.start();时,开辟了一条通向CPU的新路径来 执行run方法。并发
三、对于CPU而言,就有了两条执行的路径,CPU就有了选择权,咱们控制不了CPU,异步
两个线程,main线程和,新的 MyThread的新线程,一块儿抢夺CPU的执行权ide
(执行时间),谁抢到谁执行。函数
线程的内存解析图:
实现Runable接口的子类:MyRunbale
//1.建立一个Runnable接口的实现类 public class MyRunnable implements Runnable { //2.在实现类中重写Runnable接口的run方法,设置线程任务 @Override public void run() { for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } }
主线程: MyThread
public class MainThread { public static void main(String[] args) { //3.建立一个Runnable接口的实现类对象 MyRunnable run = new MyRunnable(); //4.建立Thread类对象,构造方法中传递Runnable接口的实现类对象 //Thread t = new Thread(run);//打印线程名称 Thread t = new Thread(run);//打印HelloWorld //5.调用Thread类中的start方法,开启新的线程执行run方法 t.start(); for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } }
结果:随机性打印
main-->0
Thread-0-->0
main-->1
Thread-0-->1
main-->2
Thread-0-->2
main-->3
Thread-0-->3
main-->4
Thread-0-->4
main-->5
Thread-0-->5
main-->6
Thread-0-->6
main-->7
Thread-0-->7
原理和内存和实现Thread相识。
实现Runnable接口建立多线程程序的好处: 1.避免了单继承的局限性 一个类只能继承一个类(一我的只能有一个亲爹),类继承了Thread类就不能继承其余的类 实现了Runnable接口,还能够继承其余的类,实现其余的接口 2.加强了程序的扩展性,下降了程序的耦合性(解耦) 实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦) 实现类中,重写了run方法:用来设置线程任务 建立Thread类对象,调用start方法:用来开启新线程
匿名内部类做用:简化代码
把子类继承父类,重写父类的方法,建立子类对象合一步完成
把实现类实现类接口,重写接口中的方法,建立实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
实现代码:
/* 匿名内部类方式实现线程的建立 匿名:没有名字 内部类:写在其余类内部的类 格式: new 父类/接口(){ 重复父类/接口中的方法 }; */ public class InnerClassThread { public static void main(String[] args) { //线程的父类是Thread // new MyThread().start(); new Thread(){ //重写run方法,设置线程任务 @Override public void run() { for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getName()+"-->"+"NOT_Copy"); } } }.start(); //线程的接口Runnable //Runnable r = new RunnableImpl();//多态 Runnable r = new Runnable(){ //重写run方法,设置线程任务 @Override public void run() { for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getName()+"-->"+"程序员"); } } }; new Thread(r).start(); //简化接口的方式 new Thread(new Runnable(){ //重写run方法,设置线程任务 @Override public void run() { for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getName()+"-->"+"MWW"); } } }).start(); } }
一、单线程不会出现线程安全问题
二、多个线程,没有访问共享资源,也不会产生线程安全问题
三、多个线程,且访问了共享资源,就会产生线程安全问题。
代码示例:
线程实现类:
/* 实现卖票案例 */ public class RunnableImpl implements Runnable{ //定义一个多个线程共享的票源 private int ticket = 100;
//设置线程任务:卖票 @Override public 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--; } } } }
MainThread类:
/* 模拟卖票案例 建立3个线程,同时开启,对共享的票进行出售 */ public class MainThread { 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(); } }
结果:
Thread-1-->正在卖第100张票 Thread-0-->正在卖第99张票 Thread-2-->正在卖第98张票 Thread-1-->正在卖第97张票 Thread-0-->正在卖第96张票 Thread-2-->正在卖第95张票 Thread-1-->正在卖第94张票 Thread-0-->正在卖第94张票 Thread-2-->正在卖第92张票 Thread-1-->正在卖第91张票 Thread-0-->正在卖第91张票 Thread-2-->正在卖第89张票 Thread-0-->正在卖第88张票 Thread-1-->正在卖第88张票 Thread-2-->正在卖第86张票 Thread-0-->正在卖第85张票 Thread-1-->正在卖第85张票 Thread-2-->正在卖第83张票 Thread-1-->正在卖第82张票 Thread-0-->正在卖第82张票 Thread-2-->正在卖第80张票 Thread-0-->正在卖第79张票 Thread-1-->正在卖第79张票 Thread-2-->正在卖第77张票 Thread-1-->正在卖第76张票 Thread-0-->正在卖第76张票 Thread-2-->正在卖第74张票 Thread-1-->正在卖第73张票 Thread-0-->正在卖第73张票 Thread-2-->正在卖第71张票 Thread-1-->正在卖第70张票 Thread-0-->正在卖第70张票 Thread-2-->正在卖第68张票 Thread-0-->正在卖第67张票 Thread-1-->正在卖第67张票 Thread-2-->正在卖第65张票 Thread-0-->正在卖第64张票 Thread-1-->正在卖第64张票 Thread-2-->正在卖第62张票 Thread-1-->正在卖第61张票 Thread-0-->正在卖第61张票 Thread-2-->正在卖第59张票 Thread-0-->正在卖第58张票 Thread-1-->正在卖第58张票 Thread-2-->正在卖第56张票 Thread-1-->正在卖第55张票 Thread-0-->正在卖第55张票 Thread-2-->正在卖第53张票 Thread-1-->正在卖第52张票 Thread-0-->正在卖第52张票 Thread-2-->正在卖第50张票 Thread-0-->正在卖第49张票 Thread-1-->正在卖第49张票 Thread-2-->正在卖第47张票 Thread-0-->正在卖第46张票 Thread-1-->正在卖第46张票 Thread-2-->正在卖第44张票 Thread-1-->正在卖第43张票 Thread-0-->正在卖第42张票 Thread-2-->正在卖第41张票 Thread-2-->正在卖第40张票 Thread-1-->正在卖第40张票 Thread-0-->正在卖第40张票 Thread-1-->正在卖第37张票 Thread-2-->正在卖第37张票 Thread-0-->正在卖第37张票 Thread-0-->正在卖第34张票 Thread-1-->正在卖第34张票 Thread-2-->正在卖第34张票 Thread-1-->正在卖第31张票 Thread-2-->正在卖第31张票 Thread-0-->正在卖第31张票 Thread-1-->正在卖第28张票 Thread-0-->正在卖第28张票 Thread-2-->正在卖第28张票 Thread-2-->正在卖第25张票 Thread-1-->正在卖第25张票 Thread-0-->正在卖第25张票 Thread-1-->正在卖第22张票 Thread-0-->正在卖第22张票 Thread-2-->正在卖第22张票 Thread-1-->正在卖第19张票 Thread-0-->正在卖第19张票 Thread-2-->正在卖第19张票 Thread-0-->正在卖第16张票 Thread-1-->正在卖第16张票 Thread-2-->正在卖第16张票 Thread-1-->正在卖第13张票 Thread-0-->正在卖第13张票 Thread-2-->正在卖第13张票 Thread-0-->正在卖第10张票 Thread-1-->正在卖第10张票 Thread-2-->正在卖第10张票 Thread-0-->正在卖第7张票 Thread-1-->正在卖第7张票 Thread-2-->正在卖第7张票 Thread-2-->正在卖第4张票 Thread-1-->正在卖第4张票 Thread-0-->正在卖第4张票 Thread-0-->正在卖第1张票 Thread-1-->正在卖第0张票 Thread-2-->正在卖第-1张票
结果出现了卖重复的票,卖不存在的票。
二、线程安全问题的解决方案:
1)、同步代码块:
/* 格式: synchronized(锁对象){ 可能会出现线程安全问题的代码(访问了共享数据的代码) } 注意: 1.经过代码块中的锁对象,可使用任意的对象 2.可是必须保证多个线程使用的锁对象是同一个 3.锁对象做用: 把同步代码块锁住,只让一个线程在同步代码块中执行 */
修改线程实现类:
/* 实现卖票案例 */ public class RunnableImpl implements Runnable{ //定义一个多个线程共享的票源 private int ticket = 100; //建立一个锁对象 Object object=new Object(); //设置线程任务:卖票 @Override public void run() { //使用死循环,让卖票操做重复执行 while(true){ //同步代码块 synchronized (object){ //先判断票是否存在 if(ticket>0){ try { Thread.sleep(10); //提升安全问题出现的几率,让程序睡眠 } catch (InterruptedException e) { e.printStackTrace(); } //票存在,卖票 ticket-- System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); ticket--; } } } } }
将可能出现线程安全问题的代码放到 synchronized (object){} 代码块中。
结果:
Thread-0-->正在卖第100张票 Thread-0-->正在卖第99张票 Thread-0-->正在卖第98张票 Thread-0-->正在卖第97张票 Thread-2-->正在卖第96张票 Thread-2-->正在卖第95张票 Thread-2-->正在卖第94张票 Thread-2-->正在卖第93张票 Thread-2-->正在卖第92张票 Thread-2-->正在卖第91张票 Thread-2-->正在卖第90张票 Thread-2-->正在卖第89张票 Thread-2-->正在卖第88张票 Thread-2-->正在卖第87张票 Thread-2-->正在卖第86张票 Thread-2-->正在卖第85张票 Thread-2-->正在卖第84张票 Thread-2-->正在卖第83张票 Thread-2-->正在卖第82张票 Thread-2-->正在卖第81张票 Thread-2-->正在卖第80张票 Thread-2-->正在卖第79张票 Thread-2-->正在卖第78张票 Thread-2-->正在卖第77张票 Thread-2-->正在卖第76张票 Thread-2-->正在卖第75张票 Thread-2-->正在卖第74张票 Thread-2-->正在卖第73张票 Thread-2-->正在卖第72张票 Thread-2-->正在卖第71张票 Thread-2-->正在卖第70张票 Thread-2-->正在卖第69张票 Thread-2-->正在卖第68张票 Thread-2-->正在卖第67张票 Thread-2-->正在卖第66张票 Thread-2-->正在卖第65张票 Thread-2-->正在卖第64张票 Thread-2-->正在卖第63张票 Thread-2-->正在卖第62张票 Thread-2-->正在卖第61张票 Thread-2-->正在卖第60张票 Thread-2-->正在卖第59张票 Thread-2-->正在卖第58张票 Thread-2-->正在卖第57张票 Thread-2-->正在卖第56张票 Thread-2-->正在卖第55张票 Thread-2-->正在卖第54张票 Thread-2-->正在卖第53张票 Thread-2-->正在卖第52张票 Thread-2-->正在卖第51张票 Thread-2-->正在卖第50张票 Thread-2-->正在卖第49张票 Thread-2-->正在卖第48张票 Thread-2-->正在卖第47张票 Thread-2-->正在卖第46张票 Thread-2-->正在卖第45张票 Thread-2-->正在卖第44张票 Thread-2-->正在卖第43张票 Thread-2-->正在卖第42张票 Thread-2-->正在卖第41张票 Thread-2-->正在卖第40张票 Thread-2-->正在卖第39张票 Thread-2-->正在卖第38张票 Thread-2-->正在卖第37张票 Thread-2-->正在卖第36张票 Thread-2-->正在卖第35张票 Thread-2-->正在卖第34张票 Thread-2-->正在卖第33张票 Thread-2-->正在卖第32张票 Thread-2-->正在卖第31张票 Thread-2-->正在卖第30张票 Thread-2-->正在卖第29张票 Thread-2-->正在卖第28张票 Thread-2-->正在卖第27张票 Thread-2-->正在卖第26张票 Thread-2-->正在卖第25张票 Thread-2-->正在卖第24张票 Thread-2-->正在卖第23张票 Thread-2-->正在卖第22张票 Thread-2-->正在卖第21张票 Thread-2-->正在卖第20张票 Thread-2-->正在卖第19张票 Thread-2-->正在卖第18张票 Thread-2-->正在卖第17张票 Thread-2-->正在卖第16张票 Thread-2-->正在卖第15张票 Thread-2-->正在卖第14张票 Thread-2-->正在卖第13张票 Thread-2-->正在卖第12张票 Thread-2-->正在卖第11张票 Thread-2-->正在卖第10张票 Thread-2-->正在卖第9张票 Thread-1-->正在卖第8张票 Thread-1-->正在卖第7张票 Thread-1-->正在卖第6张票 Thread-1-->正在卖第5张票 Thread-1-->正在卖第4张票 Thread-1-->正在卖第3张票 Thread-1-->正在卖第2张票 Thread-1-->正在卖第1张票
再也不出现重复票,和不存在的票了。
实现原理:使用一个锁对象,这个对象叫作同步锁,也叫做对象监视器。
上述代码中三个线程抢占CPU执行权,
t0 抢到了CPUd 执行权,执行run方法,遇到synchronized代码块,
这时候 t0 会检查 synchronized 代码块是否有锁对象,
发现有就会获取到锁对象进入到同步代码块执行。
t1 抢到了CPU的执行权,执行 run 方法,遇到 synchronized 代码块
这时 t1会检查synchronized代码块是否有锁对象
发现没有 t1 就会进入到阻塞状态,会一直等到 t0 线程归还 锁对象
一直到 t0 线程执行完同步代码,会把锁对象归还给同步代码块。
t1 才能获取到 锁对象 进入到同步执行。
总结:同步中的线程没有执行完毕不会释放锁,没有锁也进不进同步代码,
这样就保证了只有一个线程在同步中执行共享数据,保证了安全,
程序频繁的判断锁,获取锁,释放锁,效率会下降。
2)、同步方法
修改线程实现类:
/* 实现卖票案例 */ public class RunnableImpl implements Runnable{ //定义一个多个线程共享的票源 private int ticket = 100; //建立一个锁对象 Object object=new Object(); //设置线程任务:卖票 @Override public void run() { //使用死循环,让卖票操做重复执行 while(true){ payTicket(); } } /* 定义一个同步方法 同步方法也会把方法内部的代码锁住 只让一个线程执行 同步方法的对象是谁? 就是实现类对象 new RunnableImpl() */ public synchronized void payTicket(){ //先判断票是否存在 if(ticket>0){ try { Thread.sleep(10); //提升安全问题出现的几率,让程序睡眠 } catch (InterruptedException e) { e.printStackTrace(); } //票存在,卖票 ticket-- System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); ticket--; } } }
还能够用静态的同步代码:
package DemoThread; /* 实现卖票案例 */ public class RunnableImpl implements Runnable{ //定义一个多个线程共享的票源 private static int ticket = 100; //建立一个锁对象 Object object=new Object(); //设置线程任务:卖票 @Override public void run() { //使用死循环,让卖票操做重复执行 while(true){ payTicketStatic(); } } /* 定义一个静态的同步方法 对象是谁? 就是实现类对象 本类的class属相 --> class文件对象(反射) */ public static synchronized void payTicketStatic(){ //先判断票是否存在 if(ticket>0){ try { Thread.sleep(10); //提升安全问题出现的几率,让程序睡眠 } catch (InterruptedException e) { e.printStackTrace(); } //票存在,卖票 ticket-- System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); ticket--; } } }
将可能出现线程安全问题的代码放到 synchronized 修饰的方法中。
结果:
Thread-0-->正在卖第100张票 Thread-2-->正在卖第99张票 Thread-2-->正在卖第98张票 Thread-2-->正在卖第97张票 Thread-2-->正在卖第96张票 Thread-2-->正在卖第95张票 Thread-2-->正在卖第94张票 Thread-2-->正在卖第93张票 Thread-2-->正在卖第92张票 Thread-2-->正在卖第91张票 Thread-2-->正在卖第90张票 Thread-2-->正在卖第89张票 Thread-2-->正在卖第88张票 Thread-2-->正在卖第87张票 Thread-2-->正在卖第86张票 Thread-2-->正在卖第85张票 Thread-2-->正在卖第84张票 Thread-2-->正在卖第83张票 Thread-2-->正在卖第82张票 Thread-2-->正在卖第81张票 Thread-2-->正在卖第80张票 Thread-2-->正在卖第79张票 Thread-2-->正在卖第78张票 Thread-2-->正在卖第77张票 Thread-2-->正在卖第76张票 Thread-2-->正在卖第75张票 Thread-2-->正在卖第74张票 Thread-2-->正在卖第73张票 Thread-2-->正在卖第72张票 Thread-2-->正在卖第71张票 Thread-2-->正在卖第70张票 Thread-2-->正在卖第69张票 Thread-2-->正在卖第68张票 Thread-2-->正在卖第67张票 Thread-2-->正在卖第66张票 Thread-2-->正在卖第65张票 Thread-2-->正在卖第64张票 Thread-2-->正在卖第63张票 Thread-2-->正在卖第62张票 Thread-2-->正在卖第61张票 Thread-2-->正在卖第60张票 Thread-2-->正在卖第59张票 Thread-2-->正在卖第58张票 Thread-2-->正在卖第57张票 Thread-2-->正在卖第56张票 Thread-2-->正在卖第55张票 Thread-2-->正在卖第54张票 Thread-2-->正在卖第53张票 Thread-2-->正在卖第52张票 Thread-2-->正在卖第51张票 Thread-2-->正在卖第50张票 Thread-2-->正在卖第49张票 Thread-2-->正在卖第48张票 Thread-2-->正在卖第47张票 Thread-2-->正在卖第46张票 Thread-2-->正在卖第45张票 Thread-2-->正在卖第44张票 Thread-2-->正在卖第43张票 Thread-2-->正在卖第42张票 Thread-2-->正在卖第41张票 Thread-2-->正在卖第40张票 Thread-2-->正在卖第39张票 Thread-2-->正在卖第38张票 Thread-2-->正在卖第37张票 Thread-2-->正在卖第36张票 Thread-2-->正在卖第35张票 Thread-2-->正在卖第34张票 Thread-2-->正在卖第33张票 Thread-2-->正在卖第32张票 Thread-2-->正在卖第31张票 Thread-2-->正在卖第30张票 Thread-2-->正在卖第29张票 Thread-2-->正在卖第28张票 Thread-2-->正在卖第27张票 Thread-2-->正在卖第26张票 Thread-2-->正在卖第25张票 Thread-2-->正在卖第24张票 Thread-2-->正在卖第23张票 Thread-2-->正在卖第22张票 Thread-2-->正在卖第21张票 Thread-2-->正在卖第20张票 Thread-2-->正在卖第19张票 Thread-2-->正在卖第18张票 Thread-2-->正在卖第17张票 Thread-2-->正在卖第16张票 Thread-2-->正在卖第15张票 Thread-2-->正在卖第14张票 Thread-2-->正在卖第13张票 Thread-2-->正在卖第12张票 Thread-2-->正在卖第11张票 Thread-2-->正在卖第10张票 Thread-2-->正在卖第9张票 Thread-2-->正在卖第8张票 Thread-2-->正在卖第7张票 Thread-2-->正在卖第6张票 Thread-2-->正在卖第5张票 Thread-2-->正在卖第4张票 Thread-2-->正在卖第3张票 Thread-2-->正在卖第2张票 Thread-2-->正在卖第1张票
同步方法和静态同步的方法的不一样点在于,同步锁的对象不一样。
同步方法 的锁对象就是 实现类对象 new RunnableImpl()
静态同步的方法 的锁对象就是 本类的 RunnableImpl.class。
3)、锁机制
代码实现:
/* 卖票案例出现了线程安全问题 卖出了不存在的票和重复的票 解决线程安全问题的三种方案:使用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释放锁 */ public class RunnableImpl implements Runnable{ //定义一个多个线程共享的票源 private int ticket = 100; //1.在成员位置建立一个ReentrantLock对象 Lock lock1 = new ReentrantLock(); //设置线程任务:卖票 @Override public void run() { //使用死循环,让卖票操做重复执行 while(true){ lock1.lock(); //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁 if(ticket>0){ try { Thread.sleep(10); //提升安全问题出现的几率,让程序睡眠 //票存在,卖票 ticket-- System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); ticket--; } catch (InterruptedException e) { e.printStackTrace(); }finally { lock1.unlock();//3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁 //放在finally{} 中 不管程序是否异常,都会把锁释放。 } } } } }
结果同上面的两种方式相同。
线程状态 | 致使状态发生条件 |
NEW(新建) | 线程刚被建立,可是并未启动。还没调用start方法。 |
Runnable(可运行)
|
线程能够在java虚拟机中运行的状态,可能正在运行本身代码,也可能没有,这取决于操
做系统处理器。
|
Blocked(锁阻塞)
|
当一个线程试图获取一个对象锁,而该对象锁被其余的线程持有,则该线程进入Blocked状
态;当该线程持有锁时,该线程将变成Runnable状态。
|
Waiting(无限等待)
|
一个线程在等待另外一个线程执行一个(唤醒)动做时,该线程进入Waiting状态。进入这个
状态后是不能自动唤醒的,必须等待另外一个线程调用notify或者notifyAll方法才可以唤醒。
|
TimedWaiting(计时等待)
|
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态
将一直保持到超时期满或者接收到唤醒通知。带有超时参数的经常使用方法有Thread.sleep 、Object.wait。
|
Teminated(被终止)
|
由于run方法正常退出而死亡,或者由于没有捕获的异常终止了run方法而死亡。 |
上面已经讲过了同步机制,那么这个状态也就很是好理解了,好比线程A与线程B代码中使用
同一把锁,若是线程A获取到锁,线程A进入到Runnable状态,那么线程B进入到Blocked锁阻塞状态。
这里是由Runnable状态进入Blocked状态,除此以外Waiting(无限等待)以及
Time Waiting(计时等待)也会在某种状况下进入到阻塞状态
/* 等待唤醒案例:线程之间的通讯 建立一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待) 建立一个老板线程(生产者):花了5秒作包子,作好包子以后,调用notify方法,唤醒顾客吃包子 注意: 顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行 同步使用的锁对象必须保证惟一 只有锁对象才能调用wait和notify方法 Obejct类中的方法 void wait() 在其余线程调用此对象的 notify() 方法或 notifyAll() 方法前,致使当前线程等待。 void notify() 唤醒在此对象监视器上等待的单个线程。 会继续执行wait方法以后的代码 */ public class MainThread { public static void main(String[] args) { //建立锁对象,保证惟一 Object obj = new Object(); // 建立一个顾客线程(消费者) new Thread(){ @Override public void run() { //一直等着买包子 while(true){ //保证等待和唤醒的线程只能有一个执行,须要使用同步技术 synchronized (obj){ System.out.println("告知老板要的包子的种类和数量"); //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待) try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //唤醒以后执行的代码 System.out.println("包子已经作好了,开吃!"); System.out.println("---------------------------------------"); } } } }.start(); //建立一个老板线程(生产者) new Thread(){ @Override public void run() { //一直作包子 while (true){ //花了5秒作包子 try { Thread.sleep(5000);//花5秒钟作包子 } catch (InterruptedException e) { e.printStackTrace(); } //保证等待和唤醒的线程只能有一个执行,须要使用同步技术 synchronized (obj){ System.out.println("老板5秒钟以后作好包子,告知顾客,能够吃包子了"); //作好包子以后,调用notify方法,唤醒顾客吃包子 obj.notify(); } } } }.start(); } }
结果:
告知老板要的包子的种类和数量 老板5秒钟以后作好包子,告知顾客,能够吃包子了 包子已经作好了,开吃! --------------------------------------- 告知老板要的包子的种类和数量 老板5秒钟以后作好包子,告知顾客,能够吃包子了 包子已经作好了,开吃! --------------------------------------- 告知老板要的包子的种类和数量
代码实现:
/* 进入到TimeWaiting(计时等待)有两种方式 1.使用sleep(long m)方法,在毫秒值结束以后,线程睡醒进入到Runnable/Blocked状态 2.使用wait(long m)方法,wait方法若是在毫秒值结束以后,尚未被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态 唤醒的方法: void notify() 唤醒在此对象监视器上等待的单个线程。 void notifyAll() 唤醒在此对象监视器上等待的全部线程。 */ public class MainThread { public static void main(String[] args) { //建立锁对象,保证惟一 Object obj = new Object(); // 建立一个顾客线程(消费者) new Thread(){ @Override public 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(){ @Override public 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(){ @Override public 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(); } }
结果:
顾客1告知老板要的包子的种类和数量 顾客2告知老板要的包子的种类和数量 老板5秒钟以后作好包子,告知顾客,能够吃包子了 包子已经作好了,顾客2开吃! --------------------------------------- 顾客2告知老板要的包子的种类和数量 包子已经作好了,顾客1开吃! --------------------------------------- 顾客1告知老板要的包子的种类和数量 老板5秒钟以后作好包子,告知顾客,能够吃包子了 包子已经作好了,顾客1开吃! --------------------------------------- 顾客1告知老板要的包子的种类和数量 包子已经作好了,顾客2开吃! ---------------------------------------
以下所示:
等待唤醒机制其实就是经典的“生产者与消费者”的问题。
从新整理代码:
BaoZi类:
/* 资源类:包子类 设置包子的属性 皮 陷 包子的状态: 有 true,没有 false */ public class BaoZi { private String pier ; private String xianer ; private boolean flag = false ;//包子资源 是否存在 包子资源状态 public String getPier() { return pier; } public void setPier(String pier) { this.pier = pier; } public String getXianer() { return xianer; } public void setXianer(String xianer) { this.xianer = xianer; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } }
BaoZiPu类:
/* 生产者(包子铺)类:是一个线程类,能够继承Thread 设置线程任务(run):生产包子 对包子的状态进行判断 true:有包子 包子铺调用wait方法进入等待状态 false:没有包子 包子铺生产包子 增长一些趣味性:交替生产两种包子 有两种状态(i%2==0) 包子铺生产好了包子 修改包子的状态为true有 唤醒吃货线程,让吃货线程吃包子 注意: 包子铺线程和包子线程关系-->通讯(互斥) 必须同时同步技术保证两个线程只能有一个在执行 锁对象必须保证惟一,可使用包子对象做为锁对象 包子铺类和吃货的类就须要把包子对象做为参数传递进来 1.须要在成员位置建立一个包子变量 2.使用带参数构造方法,为这个包子变量赋值 */ public class BaoZiPu extends Thread { //1.须要在成员位置建立一个包子变量 private BaoZi bz; //2.使用带参数构造方法,为这个包子变量赋值 public BaoZiPu(BaoZi bz) { this.bz = bz; } //设置线程任务(run):生产包子 @Override public void run() { //定义一个变量 int count = 0; //让包子铺一直生产包子 while(true){ //必须同时同步技术保证两个线程只能有一个在执行 synchronized (bz){ //对包子的状态进行判断 if(bz.isFlag()==true){ //包子铺调用wait方法进入等待状态 try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //被唤醒以后执行,包子铺生产包子 //增长一些趣味性:交替生产两种包子 if(count%2==0){ //生产 薄皮三鲜馅包子 bz.setPier("薄皮"); bz.setXianer("三鲜馅"); }else{ //生产 冰皮 牛肉大葱陷 bz.setPier("冰皮"); bz.setXianer("牛肉大葱陷"); } count++; System.out.println("包子铺正在生产:"+bz.getPier()+bz.getXianer()+"包子"); //生产包子须要3秒钟 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //包子铺生产好了包子 //修改包子的状态为true有 bz.setFlag(true); //唤醒吃货线程,让吃货线程吃包子 bz.notify(); System.out.println("包子铺已经生产好了:"+bz.getPier()+bz.getXianer()+"包子,吃货能够开始吃了"); } } } }
ChiHuo类:
/* 消费者(吃货)类:是一个线程类,能够继承Thread 设置线程任务(run):吃包子 对包子的状态进行判断 false:没有包子 吃货调用wait方法进入等待状态 true:有包子 吃货吃包子 吃货吃完包子 修改包子的状态为false没有 吃货唤醒包子铺线程,生产包子 */ public class ChiHuo extends Thread{ //1.须要在成员位置建立一个包子变量 private BaoZi bz; //2.使用带参数构造方法,为这个包子变量赋值 public ChiHuo(BaoZi bz) { this.bz = bz; } //设置线程任务(run):吃包子 @Override public void run() { //使用死循环,让吃货一直吃包子 while (true){ //必须同时同步技术保证两个线程只能有一个在执行 synchronized (bz){ //对包子的状态进行判断 if(bz.isFlag()==false){ //吃货调用wait方法进入等待状态 try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //被唤醒以后执行的代码,吃包子 System.out.println("吃货正在吃:"+bz.getPier()+bz.getXianer()+"的包子"); //吃货吃完包子 //修改包子的状态为false没有 bz.setFlag(false); //吃货唤醒包子铺线程,生产包子 bz.notify(); System.out.println("吃货已经把:"+bz.getPier()+bz.getXianer()+"的包子吃完了,包子铺开始生产包子"); System.out.println("----------------------------------------------------"); } } } }
测试类:
/* 测试类: 包含main方法,程序执行的入口,启动程序 建立包子对象; 建立包子铺线程,开启,生产包子; 建立吃货线程,开启,吃包子; */ public class Demo { public static void main(String[] args) { //建立包子对象; BaoZi bz =new BaoZi(); //建立包子铺线程,开启,生产包子; new BaoZiPu(bz).start(); //建立吃货线程,开启,吃包子; new ChiHuo(bz).start(); } }
结果:
包子铺正在生产:薄皮三鲜馅包子 包子铺已经生产好了:薄皮三鲜馅包子,吃货能够开始吃了 吃货正在吃:薄皮三鲜馅的包子 吃货已经把:薄皮三鲜馅的包子吃完了,包子铺开始生产包子 ---------------------------------------------------- 包子铺正在生产:冰皮牛肉大葱陷包子 包子铺已经生产好了:冰皮牛肉大葱陷包子,吃货能够开始吃了 吃货正在吃:冰皮牛肉大葱陷的包子 吃货已经把:冰皮牛肉大葱陷的包子吃完了,包子铺开始生产包子 ---------------------------------------------------- 包子铺正在生产:薄皮三鲜馅包子 包子铺已经生产好了:薄皮三鲜馅包子,吃货能够开始吃了 吃货正在吃:薄皮三鲜馅的包子 吃货已经把:薄皮三鲜馅的包子吃完了,包子铺开始生产包子 ---------------------------------------------------- 包子铺正在生产:冰皮牛肉大葱陷包子
原理说明:
通信:对包子的状态进行判断
没有包子-->吃货线程 唤醒包子铺线程-->吃货等待-->包子铺线程作包子-->作好包子-->修改包子的状态为有包子
有包子-->包子铺线程唤醒 吃货线程-->包子铺线程等待-->吃货线程吃包子-->吃完包子-->修改包子的状态为没有包子
。。。
为何使用线程:
当并发的线程数量不少,而且每一个线程都是执行一个时间很短的任务就结束了,
这样频繁建立线程就会大大的下降系统的效率,由于频繁建立线程和销毁线程须要时间。
线程池:其实就是一个容纳多个线程的容器,其中线程能够反复使用,省去了频繁建立线程对象的操做,
无需反复建立线程池而消耗了过多的资源。
图解:
合理利用线程池可以带来三个好处:
一、下降资源消耗。减小了建立和销毁线程的次数,每一个工做线程均可以被重复使用,可执行多个任务。
二、提升响应速度。当任务到达时,任务能够不须要的等到线程建立就能当即执行
三、提升线程的可管理性。能够根据系统的承受能力,调整线程池中工做线线程的数目,防止由于消耗过多的内
/* 2.建立一个类,实现Runnable接口,重写run方法,设置线程任务 */ public class RunnableImpl implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"建立了一个新的线程执行"); } }
测试类;
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /* 线程池: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销毁线程池(不建议执行) */ public class MainThread { 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());//抛异常,线程池都没有了,就不能获取线程了 } }
结果:
pool-1-thread-1建立了一个新的线程执行 pool-1-thread-2建立了一个新的线程执行 pool-1-thread-1建立了一个新的线程执行 pool-1-thread-2建立了一个新的线程执行 pool-1-thread-2建立了一个新的线程执行
由于线程使用完了,会自动把线程归还给线程池,线程能够继续使用,因此我只在线程池中设置了两个线程,
却能够反复使用,执行屡次任务。
待续。。。。