以前学过的东西容易让人忘记,尤其是在工作中不怎么用到的东西,例如java基础里面的线程、反射甚至IO、网络编程这些,因为我们在做项目的时候,为了提高开发效率和项目稳定,一般会使用框架来扶持,用了框架,就是套一些东西了,把框架搭建好,然后我们根据框架定义的规范去编写我们的代码,而且很多功能在框架里面都提供了很好的技术支持,所以那些跟底层相关的知识学习的比较少,总感觉自己的技术不怎么有长进,还是得靠自己自觉去学习咯。
最近又复习了下java的相关线程知识,通过一些代码慢慢的理解了一些概念,有关线程的一些描述之类的,就不多说了,直接来通过代码来学习学习,当自己慢慢忘记这些基础知识点的时候,也好拿出来回忆回忆:
1、用户不定线程线程来输出i的值,其实我们这里使用的是系统提供的主线程,main方法就是在主线程上运行,所有的程序都是由线程来执行的:
public class Test {
public static void main(String[] args){
for(int i=0;i<5;i++){
System.out.println("main--->"+i);
}
}
}
这不用想,大家肯定都知道会输出什么:
main--->0
main--->1
main--->2
main--->3
main--->4
main线程执行就像是一条直线,从main方法开始入口一直向下执行!
2、定义一个线程和主线程一起执行:
package com.xin.thread;
public class Test2 {
public static void main(String[] args) {
Thread t=new TestThread();
t.start();
for(int i=0;i<5;i++){
System.out.println("main--->"+i);
}
}
}
class TestThread extends Thread{
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
知识点回顾:
1)TestThread是我们定义的一个线程类,定义线程我们可以通过继承自Thread类或者实现Runnable接口来实现,两者区别在于我们java是单继承的,如果一个类已经继承了一个其他的类,而它又要想是一个线程类,我们就不能继承Thread了,只能通过实现Runnable来定义线程了,其实Thread类也实现了Runnable接口,继承Thread类我们需要重写run方法,而实现Runnable接口我们必须要实现里面的run方法。实现Runnble的另一个好处就是还可以定义多个线程对这一个实现接口线程进行操作。
2)线程的启动不能直接调用run方法,直接调用run方法只是一个普通方法的调用,并不能启动一个线程,启动一个线程类必须调用其start方法,调用start方法后由JVM来启动和执行一个线程。
3)如果不为线程定义名称,系统会给线程默认提供Thread-num来定义线程名称,如果有多个线程,num会自动递增,例如Thread-1,Thread-2,Thread-3...,该num被所有的线程对象所共享。
上面的程序输出:
main--->0
main--->1
main--->2
main--->3
main--->4
Thread-0--->0
Thread-0--->1
Thread-0--->2
Thread-0--->3
Thread-0--->4
多次执行,输出的结果是不一样的,我们可以看到,虽然我们的线程启动在主线程之前,但是最先执行的还是主线程,其实,我是在我的双核CPU上面执行的,所以执行的结果总会是上面的结果,如果是在单核CPU上运行的话,会是乱序的哦,按道理来说,每次执行的结果是不一样的,我们来修改一下上面的代码来看看,多开几个线程:
public static void main(String[] args) {
Thread t=new TestThread();
t.start();
t=new TestThread();
t.start();
t=new TestThread();
t.start();
for(int i=0;i<5;i++){
System.out.println("main--->"+i);
}
}
执行结果:
Thread-0--->0
Thread-1--->0
Thread-1--->1
Thread-2--->0
Thread-1--->2
main--->0
Thread-0--->1
main--->1
Thread-1--->3
Thread-1--->4
Thread-2--->1
Thread-2--->2
Thread-2--->3
Thread-2--->4
main--->2
main--->3
main--->4
Thread-0--->2
Thread-0--->3
Thread-0--->4
哈哈,看到这里,是不是证明了我的结果,并且每次执行结果都不是一样的,谁先执行谁后执行都是不确定的,它是比较乱的,就像是我们去买火车票,只有一个窗口,并且人们不排队,那就是谁挤到窗口或者说谁比较幸运就谁先买到票咯,这里也是一样的,当我们定义线程后,执行程序,那么我们自定义的线程和主线程都在抢占CPU资源,只要抢到了CPU资源就能够执行输出,但是谁先抢到谁后抢到是不确定的,所以出现了我们上面的那种结果!就跟我们的抢票是一样一样的,单核CPU和双核CPU运行的结果也是不一样的哦,因为线程的操作和底层系统是有关系的。到这里,不知道多线程的概念在你的脑海里面有没有一个初步的认识呢!
如果程序只有主线程,那么就是一条直路,你就沿着这条直路直走就行;
如果我们启动了线程,那么每次启动一个线程就像是开辟了一条新的道路,我们可以在新的道路上走,它们和主线程都在抢占系统的CPU资源,谁先抢到谁就先执行,谁就跑到了终点!
3、线程对成员变量和局部变量的访问
package com.xin.thread;
public class ThreadTest1 {
public static void main(String[] args) {
MyThread myThread=new MyThread();
Thread t1=new Thread(myThread);
Thread t2=new Thread(myThread);
t1.start();
t2.start();
}
}
class MyThread implements Runnable{
private int count;
@Override
public void run() {
while(true){
System.out.println(Thread.currentThread().getName()+"--->"+(count++));
if(count==5){
break;
}
}
}
}
输出结果:
Thread-0--->0
Thread-0--->2
Thread-1--->1
Thread-0--->3
Thread-1--->4
该结果是不确定的,但是确定的是它只会执行5次,也就是说输出0,1,2,3,4,而且是乱序的,上面就是这样,而且是哪个线程先执行输出也是不确定的,这和我们上面一样的,因为抢占CPU的时候,你也不知道是t1快还是t2快,谁抢到谁先执行,并且将成员变量加1,当i=5的时候,就终止循环了!下面来看看对局部变量的访问:
package com.xin.thread;
public class ThreadTest1 {
public static void main(String[] args) {
MyThread myThread=new MyThread();
Thread t1=new Thread(myThread);
Thread t2=new Thread(myThread);
t1.start();
t2.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
int count=0;
while(true){
System.out.println(Thread.currentThread().getName()+"--->"+(count++));
if(count==5){
break;
}
}
}
}
输出结果:
Thread-0--->0
Thread-1--->0
Thread-1--->1
Thread-1--->2
Thread-1--->3
Thread-1--->4
Thread-0--->1
Thread-0--->2
Thread-0--->3
Thread-0--->4
对于结果的输出顺序,大家就不要再去多想了,因为每次执行输出结果都不是一样的,线程的执行是无序的,要让它有序,等下会介绍哦。这里会输出10次结果,线程t1和线程t2都会无序的输出0、1、2、3、4,这是因为:
1)如果一个 变量是成员变量,那么多个线程对同一个对象的成员变量进行操作的时候,他们对该成员变量是彼此影响的,也就是说一个线程的操作会影响到另一个线程
2)如果一个变量是局部变量,那么每个线程都拥有一个局部变量的拷贝,一个线程对该局部变量的操作不会影响到其他线程
4、线程休眠,sleep
class TestThread extends Thread{
@Override
public void run() {
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
定义线程并且启动之后,每次执行循环输出之前,线程都会休眠1s的时间之后再执行输出,线程的休眠是指让正在运行的线程暂停执行一段时间,进入阻塞状态,在休眠的时间内,该线程不会获得执行机会,也不会占用CPU资源,即使系统中没有其他可运行的线程,处于休眠中的线程也不会得到执行!
5、线程优先级
package com.xin.thread;
public class Test2 {
public static void main(String[] args) {
Thread t=new TestThread();
Thread t1=new TestThread();
t.setPriority(7);
t1.setPriority(8);
t.start();
t1.start();
}
}
class TestThread extends Thread{
@Override
public void run() {
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
看到优先级,大家可能会想,是不是优先级越高就越先执行呢,来看看上面程序的输出,上面给线程t1设置优先级为7,t2的优先级为8,那么是不是t2先执行呢,答案是肯定不是:
Thread-0--->0
Thread-1--->0
Thread-1--->1
Thread-0--->1
Thread-1--->2
Thread-0--->2
Thread-1--->3
Thread-0--->3
Thread-0--->4
Thread-1--->4
看,还是一样吧,线程的优先级的定义不能说优先级越高越先执行,定义优先级的级别,只能说高级别的线程比低级别的线程有更高的得到执行的机会,所以虽然定了优先级,它的输出还是无序的。java中线程的优先级的范围是0到10,一般不定义优先级默认为5。
6、线程同步synchronized
A:多个线程对同一对象的一个synchronized方法进行访问
所谓线程同步就是多个线程访问同一个对象,有可能导致不安全性问题,看下面的代码:
public class ThreadTest2 {
public static void main(String[] args) {
ObjTest obj=new ObjTest();
Thread t1=new MyThreadTest(obj);
Thread t2=new MyThreadTest(obj);
t1.start();
t2.start();
}
}
class MyThreadTest extends Thread{
private ObjTest obj;
public MyThreadTest(ObjTest obj){
this.obj=obj;
}
@Override
public void run() {
obj.test();
}
}
class ObjTest{
public void test(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
上面代码中,我们的两个线程t1、t2都对ObjTest的test方法进行了访问,输出结果如下:
Thread-0---0
Thread-1---0
Thread-1---1
Thread-1---2
Thread-1---3
Thread-0---1
Thread-1---4
Thread-0---2
Thread-0---3
Thread-0---4
这时候的执行结果是不确定的,因为t1、t2谁先执行都不知道,如果我们想让一个线程执行完test方法后,就是一个线程在执行循环输出的时候,另一个线程不能对该方法进行操作,这就可以用到java中定义的synchronized,它可以修饰方法或者在方法中定义一个synchronized块,它相当于一把锁,当被synchronized定义后,线程访问的时候,相当于获得该锁,其他的线程不能访问,只有当线程执行完毕或者抛出异常,释放掉锁后其他的一个线程才能执行,看修改后的代码,对test方法加上synchronized:
public synchronized void test(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
或者在test方法中定义synchronized块:
class ObjTest{
Object lock=new Object();
public void test(){
synchronized(lock){//synchronized(this)也可以
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
}
其中synchronized(lock){}中lock可以为任意对象,它相当于是定义在方法里面的一个标志,我们也可以这样定义,synchronized(this),锁定当前对象
通过synchronized定义方法后,无论执行多少次,结果都会输出:
Thread-0---0
Thread-0---1
Thread-0---2
Thread-0---3
Thread-0---4
Thread-1---0
Thread-1---1
Thread-1---2
Thread-1---3
Thread-1---4
一个线程进来,它拿到了锁,其他的线程都不能进行访问,只能在外面等候,然后当这个线程执行完毕后,释放掉锁,其他抢占到CPU资源的线程再次拿到锁执行,里面无论生成多少个线程,它的执行都是按照顺序的,相当于买票的时候,我们不再无序了,每个人都有一个编号(在这个里面相当于一把锁),轮到编号的人对应顺序买票。
B:多个线程对同一对象的多个synchronized方法进行访问
package com.xin.thread;
/**
* 使用synchronized定义后,是给当前的对象上锁,即多个线程访问同一对象的一个或多个
* synchronized方法后,锁定的是当前对象,一个线程进入执行的时候,其他的线程不能
* 进行访问,直到线程执行完毕或者抛出异常后其他的一个线程再次执行,所以如下代码会出现
* 如下的结果
* 多个线程访问同一对象的不同的synchronized修饰的方法,一个线程执行完后再执行另一个线程,同步执行,锁定的是当前对象
* 多个线程访问同一对象的不同的static及synchronized修饰的方法,一个线程执行完后再执行另一个线程,同步执行,锁定的是对应的class对象
* @author dell
*
*/
public class ThreadTest2 {
public static void main(String[] args) {
ObjTest obj=new ObjTest();
Thread t1=new MyThread2(obj);
Thread t2=new MyThread3(obj);
t1.start();
t2.start();
}
}
class MyThread2 extends Thread{
private ObjTest obj;
public MyThread2(ObjTest obj){
this.obj=obj;
}
@Override
public void run() {
obj.test2();
}
}
class MyThread3 extends Thread{
private ObjTest obj;
public MyThread3(ObjTest obj){
this.obj=obj;
}
@Override
public void run() {
obj.test3();
}
}
class ObjTest{
public synchronized void test2(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
public synchronized void test3(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
输出结果:
Thread-0---0
Thread-0---1
Thread-0---2
Thread-0---3
Thread-0---4
Thread-1---0
Thread-1---1
Thread-1---2
Thread-1---3
Thread-1---4
无论执行多少次,输出结果也是有顺序的。
给synchronized方法加上static关键字:
public static synchronized void test2(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
public static synchronized void test3(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
输出结果:
Thread-0---0
Thread-0---1
Thread-0---2
Thread-0---3
Thread-0---4
Thread-1---0
Thread-1---1
Thread-1---2
Thread-1---3
Thread-1---4
无论执行多少次,输出结果仍然是有序的
使用synchronized定义后,是给当前的对象上锁,即多个线程访问同一对象的一个或多个synchronized方法后,锁定的是当前对象,一个线程进入执行的时候,其他的线程不能进行访问,直到线程执行完毕或者抛出异常后其他的一个线程再次执行;
多个线程访问同一对象的不同的synchronized修饰的方法,某个时刻某个线程进入到某个synchronized方法,那么方法没有执行完毕之前,其他线程是无法访问该对象的任何synchronized方法的。一个线程执行完后再执行另一个线程,同步执行,锁定的是当前对象
多个线程访问同一对象的不同的static及synchronized修饰的方法,一个线程执行完后再执行另一个线程,同步执行,锁定的是对应的class对象
如果上面出现了非synchronized方法或者非static synchronized方法,那么执行的结果仍然是乱序的。
当多个线程对同一对象进行操作的时候,因为线程的执行是无序的,所以会导致对象的访问出现各种问题,这个时候我们需要引入同步机制synchronized,确保对象访问的安全性,但是另一方面,安全性提高了,执行的效率就低了,因为线程不同步可以一起执行,线程同步后每次只能执行一个。例如我们通常所说的StringBuffer和StringBuilder类,查看源代码就会知道StringBuffer类里面的方法很多都是被synchronized定义,即线程同步的,它的安全性高,但是执行效率就没有StringBuilder高,因为StringBuilder是线程不同步的,它的安全性方面就会低一些,例如还有HashTable和HashSet,ArrayList和Vector等等
8、线程之间的通信,生产者和消费者模式(wait和notify)
例如我们要实现一个这样的功能,定义两个线程和一个类里面的成员变量i,初始值为0,要求一个线程对i加1,一个线程对i减1,并且执行顺序是先加后减,也就是这样输出:0,1,0,1,0,1,0,1...,这就是一种生产者和消费者模式。
如果我们在类中synchronized方法进行循环操作,那么等获得锁的线程执行完该方法后才,下一个获得锁的线程才能执行
如果我们在线程中的run方法中执行循环操作,那么执行类中synchronized方法的时候,会是乱序的,因为它循环一次,执行synchronized方法一次,然后释放掉锁,然后下一个执行就不确定是哪个线程获得当前锁了。
我们开始可能会这样来编写代码:
定义一个计算变量的工厂类:
package com.xin.thread;
public class Factory {
private int i;
public synchronized void add(){
i++;
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
public synchronized void sub(){
i--;
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
定义一个增加线程,包含对工厂对象的引用:
package com.xin.thread;
public class AddThread2 implements Runnable{
private Factory factory;
public AddThread2(Factory factory){
this.factory=factory;
}
@Override
public void run() {
for(int i=0;i<5;i++){
factory.add();
}
}
}
定义一个减少的线程,包含对工厂对象的引用:
package com.xin.thread;
public class SubThread2 implements Runnable{
private Factory factory;
public SubThread2(Factory factory){
this.factory=factory;
}
@Override
public void run() {
for(int i=0;i<5;i++){
factory.sub();
}
}
}
测试类:
package com.xin.thread;
public class FactoryTest {
public static void main(String[] args) {
Factory factory=new Factory();
Thread t1=new Thread(new AddThread2(factory),"t1");
Thread t2=new Thread(new SubThread2(factory),"t2");
t1.start();
t2.start();
}
}
好了,看似我们的代码已经写好了,可是能达到我们那种效果么,运行结果发现,每次执行的结果不一样:
t1--->1
t1--->2
t2--->1
t2--->0
t2--->-1
t2--->-2
t2--->-3
t1--->-2
t1--->-1
t1--->0
从上面的结果可以看出,t1、t2线程没有做好协调工作,因为每次执行循环进入synchronized方法,获得锁的线程t1、t2是不确定的,当线程执行完毕释放锁第二个线程再次获得锁的线程也是不确定的,因此会产生无序,所以我们要定义一种协调机制,这个时候,就要用到Object类里面的两个重要方法了:wait和notify,它们是在synchronized操作中的对线程之间协调通讯的很重要的机制,当线程进入synchronized方法后,满足特定的条件使线程处于wait状态,这个时候线程会释放掉锁,然后等待其他线程执行完毕后调用notify方法会随机唤醒一个处于wait状态的线程,执行下面的操作,所以wait和notify是成对出现的,它们保证了线程之间的一种协调合作,那么使用wait和notify修改上面的Factory中的add和sub方法:
package com.xin.thread;
public class Factory {
private int i;
public synchronized void add(){
if(i==1){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
i++;
notify();
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
public synchronized void sub(){
if(i==0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
i--;
notify();
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
这个时候开辟两个线程,对其进行测试:
Factory factory=new Factory();
Thread t1=new Thread(new AddThread2(factory),"t1");
Thread t2=new Thread(new SubThread2(factory),"t2");
t1.start();
t2.start();
无论执行多少次,结果都会是:
t1--->1
t2--->0
t1--->1
t2--->0
t1--->1
t2--->0
t1--->1
t2--->0
t1--->1
t2--->0
达到我们的要求,下面我们来分析一下它的执行过程:
1、t1、t2线程启动后,对Factory的add或者sub方法进行操作,假如t2先获得锁,那么它将执行sub操作,发现此时i=0,这个时候它就处于wait状态,处于wait状态会释放掉当前锁
2、假如这个时候t1得到执行,它进入add方法,不满足i=1,它执行i++,将i变成1输出,然后执行notify,notify会随便
唤醒一个处于wait状态的线程,这里只有一个,所以t2线程得到唤醒,开始执行下面的代码,i--,然后将0输出,然后又唤醒处于wait状态的线程,一次类推
这个里面,就会有一种协调,两个线程,只要你满足if条件,那么当前线程就会处于wait状态,释放掉当前锁,其他的线程如果仍然满足if条件,仍然会处于wait状态,当不满足条件的线程执行完操作后随机唤醒一个线程,执行操作,所以无论你执行多少次,都会按照1、0、1、0...有序输出结果。因为里面通过wait和notify对线程之间做了很好的协调,那么使用上面的代码多开辟几个线程呢,让我们来看看:
Factory factory=new Factory();
Thread t1=new Thread(new AddThread2(factory),"t1");
Thread t2=new Thread(new SubThread2(factory),"t2");
Thread t3=new Thread(new AddThread2(factory),"t3");
Thread t4=new Thread(new SubThread2(factory),"t4");
t1.start();
t2.start();
t3.start();
t4.start();
其中,我们开辟两个线程t1和t3来进行增加,t2和t4来进行减少,这个时候能保证上面的结果输出么,执行结果会发现:
t1--->1
t2--->0
t1--->1
t2--->0
t1--->1
t2--->0
t4--->-1
t4--->-2
t4--->-3
t4--->-4
t4--->-5
t1--->-4
t1--->-3
t2--->-4
t2--->-5
t3--->-4
t3--->-3
t3--->-2
t3--->-1
t3--->0
多开了两个线程就出现乱序了,这又是为什么呢!
这里面,其实我们主要来理解这句话:当线程处于wait状态的时候,会释放掉当前锁,这句话很重要!!!!!
我们来假设上面情况的执行过程:t1、t2、t3、t4,其实上面的执行,只要有一个乱了,其他的都会变乱:
1、t1、t3是增加线程,t2、t4是减少线程,假如我们t2先进来,那么它满足i=0,那么这个时候它处于wait状态,释放掉当前锁,这个时候,t4也进来了,它也满足i=0,也处于wait状态,释放掉当前锁,这个时候t1进来了;
2、它不满足i=1,执行i++,输出1,然后notify任一个线程,假设唤醒了t2,这个时候,t2开始执行i--,将0输出,t2执行完毕后也notify随机一个线程了,这个时候,我们假设它notify了t4,那么t4就会执行i--,将-1输出....到这里,已经乱序了,所以后面的执行就全部乱了,因为notify是随机的一个处于wait状态的线程,我们不能很好控制它。
那么该如何解决上面的问题呢,其实里面有一个关键点,那就是在t2唤醒t4的时候,如果t4再去执行一次判断i是否等于0,是不是就解决了呢,答案是肯定的,其他的线程也是一样的,就是唤醒之后再去执行一个判断,满足条件将继续等待,不满足条件才去执行,这样我们的问题就会得到解决,方法是将if判断变成while判断,修改后的代码如下,就是将方法中的if判断修改为while判断:
package com.xin.thread;
public class Factory {
private int i;
public synchronized void add(){
while(i==1){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
i++;
notify();
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
public synchronized void sub(){
while(i==0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
i--;
notify();
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
这个时候,我们会发现,无论你开辟多少个增加线程,或者多少个减少的线程,它的输出都将会是:
t1--->1
t2--->0
t1--->1
t2--->0
t1--->1
t2--->0
t1--->1
t2--->0
t1--->1
t2--->0
t3--->1
t4--->0
t3--->1
t4--->0
t3--->1
t4--->0
t3--->1
t4--->0
t3--->1
t4--->0
因为在每次唤醒处于wait状态的线程之后,我们都去进行条件判断了,如果继续满足条件则会继续wait,不满足执行下面的逻辑,需要注意的是,上面的代码可能会出现死锁状态,因为在wait和notify的时候,可能notify的线程总是处于一种while循环状态,这点我们得注意一下,在进行类似操作的时候,得将代码控制好!!
知识点:
1、要实现线程之间的协调通讯,我们需在synchronized方法或者synchronized块中使用wait和notify关键字,它们是成对出现的, notify唤醒的是处于wait状态的线程!
2、处于wait状态的线程会释放掉当前对象的锁,而sleep方法也会使线程处于等待状态,但是它不会释放掉对象的锁。
好了,到这里,我想大家也能理解生产者和消费者模式了,就像上面的i操作一样的,1、0、1、0、1、0...就是一种生产者和消费者模式,生产者生产东西通知消费者取东西,消费者取完之后又通知生产者进行生产!!!
生产消费鸡蛋核心代码:
import java.util.ArrayList;
import java.util.List;
public class Plate {
List<Object> eggs = new ArrayList<Object>();
// 定义一个拿鸡蛋的方法,返回鸡蛋(基本数据类型)
public synchronized Object getEggs() {
if (eggs.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object egg = eggs.get(0);// 当放入鸡蛋的时候拿到鸡蛋
eggs.clear();// 清空盘子
notify();// 唤醒等待的单个线程
System.out.println("拿到鸡蛋,你该放鸡蛋了");
return egg;
}
//定义一个放鸡蛋的方法,传入鸡蛋参数
public synchronized void putEggs(Object egg) {
if (eggs.size() >0 ) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
eggs.add(egg);// 往盘子里面放鸡蛋
notify();// 唤醒等待的单个线程
System.out.println("放入鸡蛋,你可以拿了!");
}
public static void main(String[] args) {
Plate plate = new Plate();
Thread t1 = new PutThread(plate);
Thread t2 = new GetThread(plate);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 给放鸡蛋添加单个线程
class PutThread extends Thread {
private Plate plate;
private Object egg = new Object();
public PutThread(Plate plate) {
this.plate = plate;
}
public void run() {
for (int i = 0; i < 10; i++) {
plate.putEggs(egg);
}
}
}
// 给拿鸡蛋添加单个线程
class GetThread extends Thread {
private Plate plate;
public GetThread(Plate plate) {
this.plate = plate;
}
public void run() {
for (int j = 0; j < 10; j++) {
plate.getEggs();
}
}
}
通过代码,希望能让大家更好的理解线程,欢迎学习交流分享!!