传统线程机制之线程的同步互斥与通信(SYNCHRONIZED,WAIT(),NOTIFY())

线程的同步互斥与通信(synchronized,Wait(),notify())

同步互斥(synchronized)

线程间的互斥与同步通信的问题:互斥的问题,这是大家在使用多线程的时候要特别注意的。比如,现在有两个线程,都是程序代码,它们都在运行,然后它们要去访问同一个对象,一个要取这个对象里面的数据,并且要对它进行修改,另外一个线程,也要去取这个对象里面的数据,并对它进行修改。现在有两个线程在对同一个对象进行操作。大家想想,生活中有哪些这样的例子?多个线程对同一个事物进行操作?比如说银行,你的账户正在给一个账户汇款,然后,你的账户同时又在淘宝上买卖,客户又在往你的账户上汇钱,假设你的账户里面原来的余额有1000块,你的账户准备给一个账户汇款200块,汇款结束,结果是你的账户余额有800块。另外一个人想给你汇款,汇300,最后,你的账户余额应该是1100块。但是在实际当中可能会出现这么一种问题,这个人要跟你汇款300的时候,你的程序正好执行到取得1000要减200,的时候,还没执行减法运算,刚准备执行减法运算的时候,cpu就切换到了另外一个线程上,此时在这个线程上取出来的值还是1000,因为之前那个线程的减法操作还没有做,这个线程取到1000然后加上了汇过来的300,此时账户余额是1300。然后,此时线程有切换回之前的程序进行减法运算,因为之前已经从内存中取到了值是1000,这时候并不会又重新去从内存中取值,还是将之前取到的1000-200。此时本来你的账户应该有1100的余额,但是,现在只有800。就会出现这个问题。所以,只要涉及到多个线程操作相同一份数据的时候,就会出现线程安全的问题。这是我们在编写多线程应用的时候,特别要注意和小心的问题。

线程安全问题示例代码:

内部类不能访问局部变量,为了访问局部变量要加final.

在外部类的静态方法里面不能new非静态内部类的实例对象,为什么呢?因为,内部类的一个重要特点就是可以访问外部类的成员变量,成员变量是对象身上的,对象创建完了,成员变量才分配空间,我能访问你的成员变量,意味着你一定有了实例对象,而在静态方法执行的时候可以不用创建类的对象,就矛盾了。

存在线程安全问题的代码:

public class TraditionalThreadSynchronized {

    public static void main(String[] args) {

        new TraditionalThreadSynchronized().init();

    }

    private void init() {

        final Outputer outputer = new Outputer();

        new Thread(new Runnable() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(10);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    outputer.output("zhangxiaoxiang");

                }

            }

        }).start();

        new Thread(new Runnable() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(10);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    outputer.output("lihuoming");

                }

            }

        }).start();

    }

    class Outputer {

       

        public void output(String name) {

            int len = name.length();

            synchronized (Outputer.class) {

                for (int i = 0; i < len; i++) {

                    System.out.print(name.charAt(i));

                }

                System.out.println();

            }

        }

    }

}

为了防止上面的问题,咱们使用的是同步技术。打印的那段代码要实现原子性,所谓原子性,就是说,当有一个线程来执行这段代码的时候,别的线程不能打断它的执行,不能来执行它。就跟厕所里面的坑一样,当有一个人在里面的时候,另外的人不能进来,想上面那段代码,一个线程在打印,它还没打印完呢,另外一个线程进来了,要执行另一个打印,能不出问题吗?在java里面是这么来解决这个问题的,他是说把你要保护起来的代码,用一个关键字synchronized把这段代码括起来,意思是,我这段代码要排他性,要进行排他的。就是说,当有某个线程在运行这段代码的时候,别的线程也要运行这段代码,那对不起,你在外面等着。等这个在运行的线程出来了,你再进去。要实现这个,就是加一个关键字就可以了,就是synchronized。这个synchronized关键字要给他一个参数,这个参数是一个对象,找任意一个对象就行。排他性的原理是这样的,这个参数对象可以比作一把门闩或锁,任何一个对象都可以当作这个门闩,就是说,我在等这个对象的闩字,随便找一个对象就可以了,a线程进来以后,看到这个对象参数,就说好,我要拿这个对象的闩子,如果这个闩子没被别人拿走,a线程就进去了这段被synchronized括起来的代码,同时就把参数对象的闩子拿走了,b线程来了,也要等这个闩子,一看这个闩子已经被a线程拿走了,他就等,等a线程运行完这段代码,这个闩子就还回去了,b线程就等到这个闩子了,它就可以拿了这个闩子继续向下执行。现在问题是,张某在a坑里面蹲着,李某过来以后,它检查那个闩子,他不是检查张某蹲的那个坑的闩子,他跑到隔壁坑去检查那个门闩在在不在,隔壁那个门闩还在,它就拿了闩子进去了,这样就达不到互斥的效果。互斥必须是为synchronized代码块传递的是同一个对象参数。

虽然用了synchronized关键字,但因为用的对象锁不是同一个,根本达不到互斥效果的例子:

public class TraditionalThreadSynchronized {

    /**

     * @param args

     */

    public static void main(String[] args) {

        new TraditionalThreadSynchronized().init();

    }

    private void init() {

        final Outputer outputer = new Outputer();

        new Thread(new Runnable() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(10);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    outputer.output("zhangxiaoxiang");

                }

            }

        }).start();

        new Thread(new Runnable() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(10);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    outputer.output("lihuoming");

                }

            }

        }).start();

    }

    static class Outputer {

       

        public void output(String name) {

            int len = name.length();

            synchronized (name) {

                for (int i = 0; i < len; i++) {

                    System.out.print(name.charAt(i));

                }

                System.out.println();

            }

        }

    }

}

互斥一定要用同一个对象锁,怎么做到同一个对象锁呢?从代码中看,两个线程都用到了outputer对象来调用输出打印方法,它们两个用的都是同一个outputer对象。如果我们在这个对象里面声明一个成员变量作为锁,就没有线程安全的问题了。

示例代码:用同一个对象中的同一个成员变量作为对象锁:

public class TraditionalThreadSynchronized {

    /**

     * @param args

     */

    public static void main(String[] args) {

        new TraditionalThreadSynchronized().init();

    }

    private void init() {

        final Outputer outputer = new Outputer();

        new Thread(new Runnable() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(10);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    outputer.output("zhangxiaoxiang");

                }

            }

        }).start();

        new Thread(new Runnable() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(10);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    outputer.output("lihuoming");

                }

            }

        }).start();

    }

    static class Outputer {

        String xxx = “”;

        public void output(String name) {

            int len = name.length();

            synchronized (xxx) {

                for (int i = 0; i < len; i++) {

                    System.out.print(name.charAt(i));

                }

                System.out.println();

            }

        }

    }

}

一定要注意,那把锁一定要是同一个对象。

上面的代码,在对象里面特意定义一个成员变量作为锁,其实有点多此一举。本身outputer对象就已经是同一个对象了,直接用这个outputer对象作为锁就可以了。可是这个对象叫什么名字呢?在Outputer类的外面看,这个对象叫outputer,可是,现在要在Outputer类output方法内部用它,它叫什么名字呢?不知道它叫什么,因为人家new这个Outputer类的对象的时候,可以把它的变量名取作任意一个名字,在类的内部,你根本就不知道人家将来会给这个类的对象的引用变量取作什么名字。你只能保证一个原则,反正是谁调用的我,就是这个调用我的对象。我们用this关键字来引用当前调用这个方法的对象。this,就是调用这个方法的这个家伙。不用管他的变量名是什么了,就是this就可以了。用这个this作锁,就可以实现互斥。

示例代码:

public class TraditionalThreadSynchronized {

    /**

     * @param args

     */

    public static void main(String[] args) {

        new TraditionalThreadSynchronized().init();

    }

    private void init() {

        final Outputer outputer = new Outputer();

        new Thread(new Runnable() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(10);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    outputer.output("zhangxiaoxiang");

                }

            }

        }).start();

        new Thread(new Runnable() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(10);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    outputer.output("lihuoming");

                }

            }

        }).start();

    }

    static class Outputer {

        public void output(String name) {

            int len = name.length();

            synchronized (this) {

                for (int i = 0; i < len; i++) {

                    System.out.print(name.charAt(i));

                }

                System.out.println();

            }

        }

    }

}

另外一种互斥的办法:加入我要保护的不是某一段代码,而是这整个方法里面的所有代码,那就有一种省事的方式,直接在方法声明中用synchronized关键字修饰这个方法就可以了;其实它的所对象就是this

示例代码:

public synchronized void output2(String name) {

            int len = name.length();

            for (int i = 0; i < len; i++) {

                System.out.print(name.charAt(i));

            }

            System.out.println();

}

synchronized关键字,在一段代码上,最好只有一个sychronized关键字作用到。如果在方法上用了synchronized,又在方法里面的某段代码用了synchronized代码块,这样极容易产生死锁。

只要两部分代码,它们用的是同一个锁对象,那么它们就是线程互斥的。

public class TraditionalThreadSynchronized {

    /**

     * @param args

     */

    public static void main(String[] args) {

        new TraditionalThreadSynchronized().init();

    }

    private void init() {

        final Outputer outputer = new Outputer();

        new Thread(new Runnable() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(10);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    outputer.output("zhangxiaoxiang");

                }

            }

        }).start();

        new Thread(new Runnable() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(10);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    outputer.output2("lihuoming");

                }

            }

        }).start();

    }

    class Outputer {

       

        public void output(String name) {

            int len = name.length();

            synchronized (this) {

                for (int i = 0; i < len; i++) {

                    System.out.print(name.charAt(i));

                }

                System.out.println();

            }

        }

        public synchronized void output2(String name) {

            int len = name.length();

            for (int i = 0; i < len; i++) {

                System.out.print(name.charAt(i));

            }

            System.out.println();

        }

    }

}

思考题:以下代码中output2方法和output3方法能否同步?

答案是不能,因为用的不是同一个对象锁。output2方法用的对象是this,当前调用这个方法的实例对象。而output3是静态同步方法,静态方法与实例无关,是类方法。它里面不会有this指针。它的对象锁是当前类的Class对象。静态方法能够同步,也必须要有一个对象锁,但是静态方法可以运行的时候,这个类不一定要创建对象,直接用类名就可以调用。因此,此时只存在一个对象跟这个类相关,那么就是用于描述这个类的.class字节码文件的Class类的对象,也称类对象。它在类被加载的时候创建在堆中。所以output方法跟output3方法可以互斥,因为用的是同一个对象锁。

public class TraditionalThreadSynchronized {

    /**

     * @param args

     */

    public static void main(String[] args) {

        new TraditionalThreadSynchronized().init();

    }

    private void init() {

        final Outputer outputer = new Outputer();

        new Thread(new Runnable() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(10);

                    } catch (InterruptedException e) {

                        // TODO Auto-generated catch block

                        e.printStackTrace();

                    }

                    outputer.output("zhangxiaoxiang");

                }

            }

        }).start();

        new Thread(new Runnable() {

            @Override

            public void run() {

                while (true) {

                    try {

                        Thread.sleep(10);

                    } catch (InterruptedException e) {

                        // TODO Auto-generated catch block

                        e.printStackTrace();

                    }

                    outputer.output3("lihuoming");

                }

            }

        }).start();

    }

    static class Outputer {

       

        public void output(String name) {

            int len = name.length();

            synchronized (Outputer.class) {

                for (int i = 0; i < len; i++) {

                    System.out.print(name.charAt(i));

                }

                System.out.println();

            }

        }

        public synchronized void output2(String name) {

            int len = name.length();

            for (int i = 0; i < len; i++) {

                System.out.print(name.charAt(i));

            }

            System.out.println();

        }

        public static synchronized void output3(String name) {

            int len = name.length();

            for (int i = 0; i < len; i++) {

                System.out.print(name.charAt(i));

            }

            System.out.println();

        }

    }

}

线程互斥我们就说到这,互斥就是要用到一个synchronized关键字,这个synchronized关键字后面是检查一把锁对象,你几个线程要在某段代码上实现互斥,那么,必须使用同一把锁(同一个锁对象),去挡住这些线程,如果是不同的锁是挡不了的。就好比下图:

同步通信(wait(),notify())

接下来,我们讲线程同步通信的问题:

下面我们先来看一道代码题,这是一道跟线程同步相关的思考题

子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序。

实现:

第一步:子线程有一个循环10次的代码,它在执行的时候,主线程不能执行。主线程有一个循环100次的代码,它在执行的时候,同样子线程也不能执行。所以这两个线程必须是互斥的,所以它们应该用的同一个对象锁(对象头中的mark指向的monitor监视器对象)。先实现这个功能:

Main方法本身是一个线程

public class ThreadTest {

    public static void main(String[] args) {

        new Thread() {

            public void run() {

                for (int i = 1; i <= 50; i++) {

                    synchronized (ThreadTest.class) {

                        for (int j = 1; j <= 10; j++) {

                            System.out.println("子线程循环次数:" + j + ";第" + i + "次循环子线程");

                        } 

                    }                  

                }

            }

        }.start();

        for (int i = 1; i <= 50; i++) {

            synchronized (ThreadTest.class) {

                for (int j = 1; j <= 100; j++) {

                    System.out.println("主线程循环次数:" + j + ";第" + i + "次循环主线程");

                } 

            }           

        }

    }

}

以上的代码存在的问题,对象锁用了类的Class对象,范围太大,不利于线程间的互斥分组。这时候我们需要掌握一个面向对象编程的思想:把有关联的方法集中放到一个类来管理。如果说两个方法使用了同样的算法,或者说这两个方法访问到了同样的数据,或者说这两个方法在做同一件事情的某一部分等等。有关联的方法就应该收集到同一个类。这样子做更好维护,好更改。这就是高内聚,外部只要调用这个对象的方法,而不需要管这个对象的方法是怎么实现的具体细节。把相关联的方法一定要归到同一个类,如果两个方法的代码要同步,这两个方法也是有关联。所以,上面的代码可以改为下面这样实现。

再次分析题目。子线程循环10次,接着主线程循环100,这两个属于业务。要就是说子线程要做的事情是循环10次,主线程要做的事情是循环100次。让他们交替循环执行50次这样的业务,这个属于对业务的外部调用,不应该写到业务方法里面。

public class ThreadTest {

    public static void main(String[] args) {

        final Business business = new Business();

        new Thread() {

            public void run() {

                for (int i = 1; i <= 50; i++) {

                      business.sub(i);           

                }

            }

        }.start();

        for (int i = 1; i <= 50; i++) {

              business.main(i);      

        }

    }

    private static class Business{

        public synchronized void sub(int i){

            for (int j = 1; j <= 10; j++) {

                System.out.println("子线程循环次数:" + j + ";第" + i + "次循环子线程");

            } 

        }

        public synchronized void main(int i){

            for (int j = 1; j <= 100; j++) {

                System.out.println("主线程循环次数:" + j + ";第" + i + "次循环主线程");

            } 

        }

       

    }

}

这样就做到了线程互斥,但还没有做到线程间的同步通信。

经验:要用到共同数据(包括同步锁)或相同算法的若干个方法应该归在同一个类,这种设计正好体现了程序的高内聚和健壮性。

第二步:实现子线程运行完了主线程运行,主线程运行完了子线程运行这样的交替执行。这需要用到线程间的同步通信。

代码思路讲解:

首先第一步,我们两个已经互斥了,现在我们两个要通信。通信的第一步就是说,首先要让谁能够运行呢,让子线程运行,子线程运行的时候能,主线程不能运行。子线程运行完了呢,它告诉主线程,说,你走。主线程运行完了呢,又跟子线程说,小弟,你走。小弟干完了跟大哥说,大哥,你走。非常和谐。这就要用到一个变量,因为它们是一家的嘛,所以这个变量也是定义在它们家里的,private boolean isSubRun = true;因为刚开始的时候是子线程先走,所以等于true.一旦进入了子线程的方法,他就要检查,假如不该我子线程走,我就不走了,就算已经进来了方法,我也不能走,我一不小心闯进去了,对不对,CPU让我闯进来的,我一看检查,哎哟,我得遵守规则,现在没轮到我,我要停下来,让给大哥,怎么让呢,还一定要用那个同步监视器对象,进来这个方法的时候用的是this作为同步监视器对象,就调用this的wait方法,等,放弃cpu的执行权并且把锁释放掉,自己进入同步监视器对象的wait队列,继续等待cpu的执行权和同步监视器锁。这就等于是,cpu照顾你,说让你走,你一下就被激活了,但是激活了你以后呢,你很遵守规矩,你检查以下变量的值,你一看,啊,不归我走,我等吧。我不能前进了。同样,对于那个大哥,它进了方法以后,检查一下变量,它一看,该小弟做,cpu让他进入方法执行,因为方法它不能控制自己说我不进去,控制不了的,cpu跑到你头上来,就把你代码揪上,揪上去了就开始运行了,是cpu来调度你,你呢,内部检查自己一个变量,说,哎呦,不该我,那我要等,所以this.wait();好,接下来对于子线程来说,它等是一种情况,还有一种情况就是,检查这个变量的时候,他就是该自己执行了,那么他就不等了,他就直接往下执行了,自己的事就做完了,自己的事情做完了呢,他就要遵守一点交通规则,他就应该把变量的值该掉,因为接下来不该自己执行了,而是轮到大哥执行了。对于主线程也是一样,主线程做完了就该子线程做了,所以主线程里面做完了自己的事之后,也要把变量的值该掉,让子线程知道该自己执行了。这样的话,这个逻辑基本上就通了,一般的情况下应该没问题了,现在还有一个,如果线程执行出现了等待的情况,就又有问题了,现在假设,cpu一下就给了主线程,主线程已进入方法一看,不该自己,那么他就要等,等的话,那cpu就说,那我就找别人去,就找到子线程,子线程一看,该自己,是不是子线程就执行完了,子线程运行完了,把变量值改了,说该大哥执行了,但是大哥已经在那里wait了,他不会反推回去去检查那个变量,这时应该把大哥给唤醒,说大哥你醒醒,这时候子线程就要this.notify();说凡是在等的,唤醒其中的一个,现在的程序里面在等的只有大哥那一个,所以这时大哥被唤醒,唤醒了之后,大哥就继续往下执行,同样,大哥也要this.notify();说如果有小弟在等,就唤醒。这样,我们基本上就做完了。

记住一个原则,把这些相关的代码设计到一个类里面,让他们自己去管理自己的状态,不要让线程外面的类去管。

有一个业务类,这个业务类里面有两个业务方法,一个叫主方法,一个叫子方法,我们的这个同步,通信都是放在这个业务类,也就是这个资源对象身上,就是这个具体干事的对象身上,那个线程只是把这个对象拿上去跑,去调用它们而已,所以,这些互斥的问题,不是写在线程上的,而是写在线程所要访问的那个资源的内部的。这样的好处在于,以后我这个类交给任何一个线程去访问,他天然就同步了,不需要考虑线程的线程同步问题。如果今天是在这个线程上写,明天又有第三个线程要调用我,那我是不是还要在第三个线程上去写同步代码。全部在这个类内部写,而不要在线程的代码上去写,Thread,Runnable,run,这些是线程有关的代码,它们要去访问一个资源,它们访问的资源要同步,要互斥,那么这些互斥,这些同步的代码要放到这个资源内部的方法上去搞,有这种事项,处理线程问题就简单了。

public class ThreadTest {

    public static void main(String[] args) {

        final Business business = new Business();

        new Thread() {

            public void run() {

                for (int i = 1; i <= 50; i++) {

                    business.sub(i);

                }

            }

        }.start();

        for (int i = 1; i <= 50; i++) {

            business.main(i);

        }

    }

    private static class Business {

        private boolean isSubRun = true;

        public synchronized void sub(int i) {

            if (!isSubRun) {

                try {

                    this.wait();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

            for (int j = 1; j <= 10; j++) {

                System.out.println("子线程循环次数:" + j + ";第" + i + "次循环子线程");

            }

            isSubRun = false;

            this.notify();

        }

        public synchronized void main(int i) {

            if (isSubRun) {

                try {

                    this.wait();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

            for (int j = 1; j <= 100; j++) {

                System.out.println("主线程循环次数:" + j + ";第" + i + "次循环主线程");

            }

            isSubRun = true;

            this.notify();

        }

    }

}

接下来再把上面的代码优化一下,把if改成while.我们来看一下这个执行逻辑,首先进入子线程方法,进行while循环判断,假如不该我子线程执行,那我就等,最后主线程干完了,他通知我,我是不是就被唤醒了,我被唤醒了以后,我是往下走还是回来再检查一边变量呢?回来,再去检查这个变量,确实是该我了,刚才主线程搞完把这个变量改成true了,循环条件就不成立了,我就不进循环体了,而是向下执行,看起来跟if好像没有什么太大区别,就多判断一次,第一判断就进循环体执行了this.wait()方法,开始等待,等着等着,别人把我唤醒了,唤醒了变量也被人家改了,我再来检查一下条件,肯定就不是等了,就继续往下走了,所以这里是可以用while的,效果跟if是一样的,比if还要好,这样更牢靠,你把我唤醒了,我都还要检查一下,是不是真的该我,你把我叫醒了,我还很认真,我说,真的该我了吗?如果是真的,我就继续往下走,这样代码就更健壮。正规的用法是用while来判断,对象的wait()方法必须放在synchronized修饰的代码块里面,synchronized用的是哪个对象的监控锁,这个wait()方法就用哪个对象调用。否则,程序会报异常。那么,这个wait()为什么要用while判断呢?因为在有一些情况下,wait()会被假唤醒,就是说并没有被另外一个线程通知,就唤醒了,这种称之为伪唤醒。就是说不要完全相信唤醒,有时候小弟没有通知你,不知道你怎么自己做了一个梦,被这个噩梦给吓醒了,有这种情况,别人没叫你,你自己就醒了。这时候用while循环判断,就可以防止伪唤醒。

public class ThreadTest {

    public static void main(String[] args) {

        final Business business = new Business();

        new Thread() {

            public void run() {

                for (int i = 1; i <= 50; i++) {

                    business.sub(i);

                }

            }

        }.start();

        for (int i = 1; i <= 50; i++) {

            business.main(i);

        }

    }

    private static class Business {

        private boolean isSubRun = true;

        public synchronized void sub(int i) {

            while (!isSubRun) {

                try {

                    this.wait();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

            for (int j = 1; j <= 10; j++) {

                System.out.println("子线程循环次数:" + j + ";第" + i + "次循环子线程");

            }

            isSubRun = false;

            this.notify();

        }

        public synchronized void main(int i) {

            while (isSubRun) {

                try {

                    this.wait();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

            for (int j = 1; j <= 100; j++) {

                System.out.println("主线程循环次数:" + j + ";第" + i + "次循环主线程");

            }

            isSubRun = true;

            this.notify();

        }

    }

}

最终的程序代码如下:

public class ThreadTest {

    /**

     * @param args

     */

    public static void main(String[] args) {

        // TODO Auto-generated method stub

        new ThreadTest().init();

    }

    public void init() {

        final Business business = new Business();

        new Thread(new Runnable() {

            public void run() {

                for (int i = 0; i < 50; i++) {

                    business.SubThread(i);

                }

            }

        }

        ).start();

        for (int i = 0; i < 50; i++) {

            business.MainThread(i);

        }

    }

    private class Business {

        boolean bShouldSub = true;// 这里相当于定义了控制该谁执行的一个信号灯

        public synchronized void MainThread(int i) {

            if (bShouldSub)

                try {

                    this.wait();

                } catch (InterruptedException e) {

                    // TODO Auto-generated catch block

                    e.printStackTrace();

                }

            for (int j = 0; j < 5; j++) {

                System.out.println(Thread.currentThread().getName() + ":i=" + i + ",j=" + j);

            }

            bShouldSub = true;

            this.notify();

        }

        public synchronized void SubThread(int i) {

            if (!bShouldSub)

                try {

                    this.wait();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            for (int j = 0; j < 10; j++) {

                System.out.println(Thread.currentThread().getName() + ":i=" + i + ",j=" + j);

            }

            bShouldSub = false;

            this.notify();

        }

    }

}

备注:不可能一上来就写出上面的完整代码,最初写出来的代码如下,问题在于两个线程的代码要参照同一个变量,即这两个线程的代码要共享数据,所以,把这两个线程的执行代码搬到同一个类中去:

public class ThreadTest {

    private static boolean bShouldMain = false;

    public static void main(String[] args) {

        // TODO Auto-generated method stub

        /*

         * new Thread(){ public void run() { for(int i=0;i<50;i++) { for(int j=0;j<10;j++) {

         * System.out.println("i=" + i + ",j=" + j); } } }

         *

         * }.start();

         */

        // final String str = new String("");

        new Thread(new Runnable() {

            public void run() {

                for (int i = 0; i < 50; i++) {

                    synchronized (ThreadTest.class) {

                        if (bShouldMain) {

                            try {

                                ThreadTest.class.wait();

                            } catch (InterruptedException e) {

                                e.printStackTrace();

                            }

                        }

                        for (int j = 0; j < 10; j++) {

                            System.out.println(Thread.currentThread().getName() + "i=" + i + ",j=" + j);

                        }

                        bShouldMain = true;

                        ThreadTest.class.notify();

                    }

                }

            }

        }).start();

        for (int i = 0; i < 50; i++) {

            synchronized (ThreadTest.class) {

                if (!bShouldMain) {

                    try {

                        ThreadTest.class.wait();

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                }

                for (int j = 0; j < 5; j++) {

                    System.out.println(Thread.currentThread().getName() + "i=" + i + ",j=" + j);

                }

                bShouldMain = false;

                ThreadTest.class.notify();

            }

        }

    }

}

下面使用jdk5中的并发库来实现的:

import java.util.concurrent.Executors;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

import java.util.concurrent.locks.Condition;

public class ThreadTest {

    private static Lock lock = new ReentrantLock();

    private static Condition subThreadCondition = lock.newCondition();

    private static boolean bBhouldSubThread = false;

    public static void main(String[] args) {

        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        threadPool.execute(new Runnable() {

            public void run() {

                for (int i = 0; i < 50; i++) {

                    lock.lock();

                    try {

                        if (!bBhouldSubThread)

                            subThreadCondition.await();

                        for (int j = 0; j < 10; j++) {

                            System.out.println(Thread.currentThread().getName() + ",j=" + j);

                        }

                        bBhouldSubThread = false;

                        subThreadCondition.signal();

                    } catch (Exception e) {

                    } finally {

                        lock.unlock();

                    }

                }

            }

        });

        threadPool.shutdown();

        for (int i = 0; i < 50; i++) {

            lock.lock();

            try {

                if (bBhouldSubThread)

                    subThreadCondition.await();

                for (int j = 0; j < 10; j++) {

                    System.out.println(Thread.currentThread().getName() + ",j=" + j);

                }

                bBhouldSubThread = true;

                subThreadCondition.signal();

            } catch (Exception e) {

            } finally {

                lock.unlock();

            }

        }

    }

}

猜你喜欢

转载自my.oschina.net/u/3512041/blog/1822014