A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

多线程:应用程序有多条执行路径。
        进程:正在运行的应用程序。
        线程:一条路径,进行的执行单元。
        注意:线程是依赖于进程,而进程是操作系统直接创建的。
        (掌握)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方法也是一个线程。

评分

参与人数 6黑马币 +18 收起 理由
0618张军 + 3 很给力!
g552092947 + 2 赞一个!
0618-蔺飞飞 + 3 很给力!
tangpeng + 2 赞一个!
jkdong + 4
Poskey + 4 赞一个!

查看全部评分

16 个回复

倒序浏览
就是这个死锁。。
回复 使用道具 举报
谢谢呀,你是第一个,抢到沙发了。
回复 使用道具 举报
顶一下。
回复 使用道具 举报
不错,写的很好
回复 使用道具 举报
骑士@魔龙 来自手机 中级黑马 2015-7-22 23:05:41
地板
多线程是重点,必须掌握好
回复 使用道具 举报
不错啊,值得学习
回复 使用道具 举报
还没学到那,多线程难不难?
回复 使用道具 举报
感谢分享,我再巩固下
回复 使用道具 举报
感谢分享,多线程、死锁是难点,继续理解下。
回复 使用道具 举报
不错不错。。。。
回复 使用道具 举报
谢谢分享
回复 使用道具 举报
感谢分享
回复 使用道具 举报
赞一个,,,挺认真的
回复 使用道具 举报
江峰 来自手机 中级黑马 2015-7-25 23:12:32
15#
不都是顺序执行的,分时罢了
回复 使用道具 举报
赞一个  谢谢分享
回复 使用道具 举报
难不难啊
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马