黑马程序员技术交流社区

标题: 【石家庄校区】JavaEE个人学习总结day11-线程 [打印本页]

作者: sun2ice    时间: 2018-1-4 17:06
标题: 【石家庄校区】JavaEE个人学习总结day11-线程
本帖最后由 sun2ice 于 2018-1-4 17:32 编辑

#day 11 多线程

*    进程 Process
    *    正在运行的程序,一个应用程序在内存中的执行区域
*    线程 Thread
    *    进程中的一个执行控制单元,执行路径
*    单线程
    *    安全性高,效率低
*    多线程
    *    安全性低,效率高
*    主方法为单线程
*    创建和启动线程,传统有两种方式:
    *    方式1:继承Thread类;
    *    方式2:实现Runnable接口;

```
扩展:
    并发:并行发生,,同时发生,多线程就可以实现并发
    同步:一步接一步的执行,一个结束后下一个   `单线程同步`
    异步:同时多步, `多线程异步`
    阻塞:
```
##方式1:继承Thread类;
步骤:
```
1):定义一个类A继承于java.lang.Thread类.
2):在A类中覆盖Thread类中的run方法.
3):我们在run方法中编写需要执行的操作---->run方法里的,线程执行体.
4):在main方法(线程)中,创建线程对象,并启动线程.
    创建线程类对象:    A类 a = new A类();
    调用线程对象的start方法: a.start();//启动一个线程
注意:千万不要调用run方法,如果调用run方法好比是对象调用方法,依然还是只有一个线程,并没有开启新的线程.
```
##方式2:实现Runnable接口;
步骤:
```
1):定义一个类A实现于java.lang.Runnable接口,注意A类不是线程类.

2):在A类中覆盖Runnable接口中的run方法.
3):我们在run方法中编写需要执行的操作---->run方法里的,线程执行体.
4):在main方法(线程)中,创建线程对象,并启动线程.
创建线程类对象:    Thread  t = new Thread(new  A());   
调用线程对象的start方法:    t.start();
```
```   
static Thread currentThread()
返回对当前正在执行的线程对象的引用
String name = Thread.currentThread().getName();
```
##对比
继承方式:继承Thread类;
```
1):Java中类是单继承的,如果继承了Thread了,该类就不能再有其他的直接父类了.
2):从操作上分析,继承方式更简单,获取线程名字也简单.(操作上,更简单)
3):从多线程共享同一个资源上分析,继承方式不能做到.
```
实现方式:实现Runnable接口;
```
1):Java中类可以多实现接口,此时该类还可以继承其他类,并且还可以实现其他接口(设计上,更优雅).
2):从操作上分析,实现方式稍微复杂点,获取线程名字也比较复杂,得使用Thread.currentThread()来获取当前线程的引用.
3):从多线程共享同一个资源上分析,实现方式可以做到(是否共享同一个资源).
```
##线程不安全的问题分析:
*    当多线程并发访问同一个资源对象的时候,可能出现线程不安全的问题.
*    同步:安全性高,效率低
```
* 原因
    * 线程会在执行过程中根据CPU调度来随机切换, 可能暂停在任何一行代码上
    * 多个线程同时操作了共享数据, 在共享数据还没有发生变化时, 其他线程就对其继续操作, 导致数据不准确   
```
*    方式1:同步代码块
```
语法:
synchronized(同步锁)
{
     需要同步操作的代码
}
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着.
```   
* 锁对象
    * 锁是一个**任意类型**的对象
        * Object对象可以, String对象可以, Student对象等也可以...
    * 锁对象**必须是被所有线程共享的**, 也就是说多个线程都共用一个锁
        * 如使用同一个Runnable对象中的成员变量
        * 或使用某个类的静态变量
        * 或使用某个类的字节码对象
*    方式2:同步方法
```
使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着.
synchronized public void doWork(){
     ///TODO
}
同步锁是谁:
    对于非static方法,同步锁就是this.  
    对于static方法,我们使用当前方法所在类的字节码对象(Apple2.class).
```
* 同步代码块和同步方法如何选择?
    * 如果一段代码的功能是独立的, 会在多个地方重复执行, 且需要同步, 可以定义为同步方法, 便于复用
*   
```
在线程的run方法上不能使用throws来声明抛出异常,只能在方法中使用try-catch来处理异常.
原因是:子类覆盖父类方法的原则,子类不能抛出新的异常.
    在Runnable接口中的run方法,都没有声明抛出异常.
```
*    方式3:锁机制(Lock)
```
同步锁(Lock):
Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,
同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象.
// 创建锁对象, 该锁对象也要所有线程共享唯一一个
private Lock lock = new ReentrantLock();
lock.lock();    // 加锁, 相当于执行到synchronized
// 同步的代码
lock.unlock();  // 释放锁, 相当于同步代码块执行完毕
```
##synchronized的好与坏:
*   
```
好处:保证了多线程并发访问时的同步操作,避免线程的安全性问题.
缺点:使用synchronized的方法/代码块的性能比不用要低一些.
建议:尽量减小synchronized的作用域
```
## 继承Thread类和实现Runnable接口在共享资源上有什么区别
```
* 继承Thread类
    * Thread类中的非静态成员变量是每个线程各自拥有的, 不能作为共享资源
    * 静态成员变量是所有线程对象共享的, 可以作为共享资源
* 实现Runnable接口, 如果为所有Thread对象都传入同一个Runnable对象, 则
    * Runnable对象中的非静态成员变量会被所有Thread对象(也就是所有线程)共享, 可以作为共享资源
    * Runnable对象中的静态成员变量也会被所有Thread对象共享, 也可以作为共享资源
* 综上所述
    * 继承Thread类方式下, 共享资源要定义为Thread类的静态资源
    * 实现Runnable接口方式下, 共享资源要定义为Runnable接口的静态或非静态资源, 且所有Thread对象都要传入同一个Runnable对象
```
*    线程通信-wait和notify方法介绍:
```
java.lang.Object类提供类两类用于操作线程通信的方法.
wait():执行该方法的线程对象释放同步锁,JVM把该线程存放到等待池中,等待其他的线程唤醒该线程.
notify:执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待.
notifyAll():执行该方法的线程唤醒在等待池中等待的所有的线程,把线程转到锁池中等待.
注意:上述方法只能被同步监听锁对象(同步代码块中)来调用,否则报错IllegalMonitorStateException..
```
## 线程之间的通信
* 使用`Object`类的成员方法
    * `void wait()`: 使当前线程处于等待状态, 并且会立刻释放锁
    * `void notify()`: 随机唤醒一个处于等待状态的线程
    * `void notifyAll()`: 唤醒所有处于等待状态的线程
    * 注意: **这三个方法必须在同步代码块中, 且只能用锁对象来调用, 否则会抛异常**
* `sleep()``wait()`的区别
    * sleep
        * 让当前线程在指定时间内睡眠, 时间结束后继续执行
        * 是Thread类的静态方法
        * 不会释放锁
    * wait
        * 让当前线程等待, 直到有人唤醒
        * 是Object类的非静态方法
        * 等待会立刻释放锁
* 注意:
    * wait的线程必须要有人notify才能唤醒, 如果所有的线程都wait了, 那程序整个就全都在等了. 所以在写代码时必须考虑仔细
```
*    死锁原因:
```
## 常见线程问题: 死锁
* 死锁: dead lock
    * 同步代码块中的线程不出来, 也不释放锁; 同步代码块外的线程拿不到锁, 只能等在外面.
* 发生死锁的原因:
    1. 同步代码块内的线程, 可能处在死循环, IO阻塞, sleep()状态, 导致内部持有锁的线程无法出同步代码块
    2. 多个线程互相持有锁又不释放锁: 两个线程执行的任务都是双层同步代码块, 每层同步都需要一个锁, 两个线程中同步代码块的锁是相反的
* 死锁的结果: 程序卡死, 无法继续执行
* 如何避免死锁:
    * 避免在同步代码块中执行死循环, IO阻塞操作, sleep()
    * 避免多个线程互相持有锁又不释放锁的情况
```
## 线程释放锁的3种情况
1. synchronized同步代码执行完毕
2. 线程发生了异常导致线程终止
3. 线程调用了`wait()`方法进行等待, 等待会立刻释放锁
## 线程的生命周期
* 线程的声明周期中有5种状态
    1. 创建: new一个线程对象, 此时还没有调用start()
    2. 就绪: 调用start()方法后, 进入就绪状态, 等待CPU执行
    3. 运行: 获取了CPU的执行权, 开始运行线程
    4. 阻塞: 调用了sleep(), wait(), 或由于IO操作导致阻塞. 阻塞解除后仍会返回就绪状态, 等待CPU执行
    5. 销毁: 线程执行完毕







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