黑马程序员技术交流社区

标题: 刚学完多线程,把老师总结的发一下。 [打印本页]

作者: wj456123    时间: 2015-7-22 22:48
标题: 刚学完多线程,把老师总结的发一下。
多线程:应用程序有多条执行路径。
        进程:正在运行的应用程序。
        线程:一条路径,进行的执行单元。
        注意:线程是依赖于进程,而进程是操作系统直接创建的。
        (掌握)Java语言是不能直接调用操作系统的属性的。C语言可以。

为什么要使用多线程?什么时候使用?
        为了提高效率。
        当要操作的代码比较多(耗时),循环次数比较多的时候就可以使用多线程。

多线程怎么实现呢? (掌握)
        方式1:继承Thread类。
                A:创建一个类(MyThread),集成Thread类。
                B:重写Thread类中的run()方法。
                C:启动并执行线程。
                        注意:这里是不能直接调用run()方法,需要调用start()方法。
                                  start()方法做了两件事:
                                        A:启动线程。
                                        B:自动调用run()方法。
                                同一个线程对象start()两次,会报一个异常:IllegalThreadStateException(线程状态异常。)
                Thread类的两个方法:
                        public final String getName();   获取线程名称(Thread-编号,编号是从0开始)
                        public final void setName(String name);   设置线程名称。

                        public static void sleep(long mills);  让当天线程睡一段时间(根据传过来的毫秒值)
                线程执行的随机性的原理:由于CPU在做着高效的切换。
                        程序的执行,其实就是在抢CPU资源。

        方式2:实现Runnable接口。
                A:创建一个类(MyRunnable)实现Runnable接口。
                B:重写run()方法。
                C:创建类(MyRunnable)的实例。
                D:把类(MyRunnable)的实例,做为Thread类的构造参数传递。
                注意事项:
                        因为MyRunnable这个类是实现了Runnable接口,所以在类中不能直接使用Thread类的getName()这个方法。
                        怎么用呢?
                        public static Thread currentThread();  返回正在执行的线程的引用
                        (Thread.currentThread().getName())
        既然有了第一种实现方式,为什么要有第二种实现方式?
                A:避免了单继承的局限性。
                B:实现Runnable接口的方式,只创建了一个资源对象,更好的实现了数据(my)和操作(start())的分离。
                        开发中,常用:实现Runnable接口的方式。

多线程的生命周期图:
        A:新建(创建一个线程对象)
        B:就绪(具备执行资格,不具备执行权)
        C:有可能发生阻塞(不具备执行资格和执行权)
                sleep(),wait()
                notify()
        D:运行(具备执行资格和执行权)
        E:死亡(线程对象变为垃圾,等待垃圾回收器回收。)


多线程模拟买票出现了负数和重复值? 产生原因是什么? 怎么解决呢?
        产生负数:
                public class TicketRunnable implements Runnable {
        public int tickets = 200;
        @Override
        public void run() {
                while (true) {
                        //t1,t2,t3,t4都进来了
                        //最后一次tickets的值是1
                        if (tickets > 0) {
                                /*
                                 * t1满足,进来了,
                                 * t2进来了,
                                 * t3,
                                 * t4都进来了
                                 * */
                                try {
                                        Thread.sleep(100);
                                } catch (InterruptedException e) {
                                        // TODO Auto-generated catch block
                                        e.printStackTrace();
                                }
                                /*
                                 * t1醒了,抢到了资源,打印:第一张票 ,完了以后,tickets的值变成了0
                                 * t2醒了,抢到了资源,打印:第0张票,完了以后,tickets的值变成了-1
                                 * t3醒了,抢到了资源,打印:第-1张票,完了以后,tickets的值变成了-2
                                 * t4醒了,抢到了资源,打印:第-2张票,完了以后,tickets的值变成了-3
                                 * */
                                System.out.println(Thread.currentThread().getName() + "正在出售第"
                                                + (tickets--) + "张票");
                        }

                }
        }
}
        产生重复值:
                关键点:ticket--   (ticket = ticket - 1)
                 A:获取ticket的值。
                 B:修改ticke的值。
                 C:把修改后的值,重新赋值给ticket。
                 当步骤A执行完后,还没来得及执行B,这个时候,别的线程抢到了资源,打印的就是重复值。


产生原因:
        由于线程的随机性和延迟性,导致线程访问共享数据出现了问题。


解决方案:
        找到可能出问题的代码,然后把可能出问题的代码用锁锁起来。
        A:如何找可能出问题的代码?
                1、是否有共享数据。
                2、是否有多条语句操作共享数据。
                3、是否在多线程环境中。
                这三点也是多线程产生问题的原因。
        B:怎么锁?
                可以采用同步代码块的方式锁。
                synchronized (锁对象)
                {
                        要被锁的代码。
                }
        注意:
                1、锁对象可以是任意对象。
                2、多个线程的锁对象必须是同一个。不然可能会出现锁不住的情况。
        锁的状态:开,关。


不同同步机制的锁对象分别是什么?
        同步代码块:
                可以是任意类型。
        同步方法:把synchronized定义在方法的声明上。(写在方法的返回值的数据类型之前)
                public synchronized void show();
                this。
        静态方式:
                当前类的字节码文件对象。
                类名.class

以后使用同步代码块还是同步方法?
        看需求。但是同步的代码越少越好,所以一般使用同步代码块。
        如果一个方法中所有的代码都被加锁了,那么,我们可以考虑使用同步方法。

面试题:
        写一个死锁的代码。

线程间的通信问题:
        线程通信:不同种类的线程间对共享数据的操作问题。
        用学生类的案例来演示线程通信;
                学生类: Student  (资源类)
                设置值: SetStudent (一个共享数据,实现了Runnable接口)
                获取值: GetStudent(一个共享数据,实现了Runnable接口)
                测试类:
        正常的逻辑:
                针对输出:( GetStudent)
                        判断是否有数据,如果有就输出数据,没有的话,就等待设置。
                        (喝水:判断水杯是否有水,如果有就喝,如果没有,就等待小蜜去接水)
                针对设置;(SetStudent)
                        判断是否有数据,如果有就等待输出,如果没有,就设置。
                        (接水:判断水杯是否有水,如果有等待喝,如果没有,就接水。)
                定义了一个标记: true代表有数据,false代表没有数据。

        面试题:wait()方法和sleep()这个方法有什么区别?
                wait():Object类下的方法,不需要传递参数。释放锁对象,释放资源。
                sleep():Thread类,需要传递参数,不释放锁对象。
        等待唤醒机制的原理:
                Object类中的两个方法:
                        wait():让当前线程处于等待状态。
                        notify():唤醒等待的线程。
线程的优先级;
        public final void setPriority(int newPriority);  设置线程的优先级
        public final int getPriority();  获取线程的优先级
        线程默认的优先级是5,范围是1-10.
        注意:
                线程的优先级越高,并不代表该线程一定第一个执行。
                线程优先级可以再一定程度上,让该线程获取较多的执行机会。

线程的拓展:
        暂停线程:
                public static void yield();   暂停当前正在执行的线程对象,并执行其他线程。
                什么时候用?
                        他可以让线程更和谐一点的运行,避免出现成片数据的情况。
                        注意:如果想要真正的实现数据依次输出,还得使用等待唤醒机制。
        加入线程:
                public final void join();  等待该线程终止。
                什么时候用?
                        当某个线程(A)需要在某个线程(B)执行结束后才执行,就可以使用“加入线程”。
                        例子:当线程B执行完后才能执行线程A,
        守护线程:
                public final void setDaemon(boolean on); 设置线程为守护线程,一旦前台线程(主线程)结束了,守护线程紧跟着就结束了。
                main方法也是一个线程。

                main方法也是一个线程。


作者: Poskey    时间: 2015-7-22 22:57
就是这个死锁。。
作者: wj456123    时间: 2015-7-22 23:00
谢谢呀,你是第一个,抢到沙发了。
作者: 黑马的学员    时间: 2015-7-22 23:00
顶一下。
作者: tangpeng    时间: 2015-7-22 23:03
不错,写的很好
作者: 骑士@魔龙    时间: 2015-7-22 23:05
多线程是重点,必须掌握好
作者: 飞飞飞丨    时间: 2015-7-22 23:29
不错啊,值得学习
作者: 路遥知马力    时间: 2015-7-22 23:31
还没学到那,多线程难不难?
作者: umbriel    时间: 2015-7-22 23:33
感谢分享,我再巩固下
作者: wy8215866    时间: 2015-7-22 23:37
感谢分享,多线程、死锁是难点,继续理解下。
作者: 康恒强    时间: 2015-7-23 08:25
不错不错。。。。
作者: fantianfei    时间: 2015-7-23 08:33
谢谢分享
作者: 為了夢想    时间: 2015-7-23 11:23
感谢分享

作者: 0618张军    时间: 2015-7-25 22:10
赞一个,,,挺认真的
作者: 江峰    时间: 2015-7-25 23:12
不都是顺序执行的,分时罢了
作者: 叫什么    时间: 2015-7-25 23:39
赞一个  谢谢分享
作者: 比例思哒    时间: 2016-4-30 18:41
难不难啊




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2