学习笔记-基础知识7-多线程

多线程
1.进程和线程
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以有多个线程。
线程是指进程中的一个执行任务(控制单元),多个线程可共享数据。
多进程:操作系统中同时运行的多个程序;

多线程具有随机性。
    造成的原因:CPU在瞬间不断切换去处理各个线程而导致的。可以理解成多个线程在抢cpu资源。
多线程是为了同步完成多项任务,不是为了提高运行效率,而是通过提高资源使用效率来提高系统的效率.
进程与线程的区别:
    进程有独立的进程空间,进程中的数据存放空间(堆空间和栈空间)是独立的。
    线程的堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的。

2.创建线程方式

方法一:继承Thread类:
    1.定义类,继承Thread
    2.复写Thread类中的run方法
    3.调用线程的start方法,该方法有两个作用:一是启动线程,二是调用run方法。
方法二:实现Runnable接口
    1.定义类实现Runnable接口
    2.覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中
    3.通过Thread类建立线程对象
    4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数 
    5.调用Thread类的start方法开启线程并调用Runnable接口子类中的run方法
Thread类中run()和start()方法的区别如下:
    run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
    start()方法:启动一个线程,调用该Runnable 对象的run()方法,不能多次启动一个线程;

方法二4中为什么要将Runnable接口的子类对象传递给Thread的构造函数
    因为,自定义的run方法所属的对象时Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该run方法所属的对象。

3.两种进程创建方式比较
A extends Thread:简单,不能再继承其他类了(单继承),同份资源不共享
A implements Runnable:(推荐)多个线程共享一个目标资源,适合多线程处理同一份资源。该类还可以继承其他类,也可以实现其他接口。

实现方式和继承方式有什么区别?
    实现方式好处:避免了单继承的局限性,在定义线程时,建议使用实现方式。
    两种方式的区别:
        继承Thread类:线程代码存放在Thread子类的run方法中。
        实现Runnable:线程代码存放在接口的子类的run方法中。
为什么要覆盖run方法呢?
    Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法.

4.线程的生命周期
Thread类内部有个public的枚举Thread.State,里边将线程的状态分为:
NEW–新建状态,至今尚未启动的线程处于这种状态。
RUNNABLE–运行状态,正在Java 虚拟机中执行的线程处于这种状态。
BLOCKED–阻塞状态,受阻塞并等待某个监视器锁的线程处于这种状态。
WAITING–冻结状态,无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
TIMED_WAITING–等待状态,等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
TERMINATED–已退出的线程处于这种状态。

                 start            sleep,wait     stop
被创建:new Tread()------>运行:run()<----------->冻结------>消亡
                                    notify

如何停止线程?
    只有一种,run方法结束。
    开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。
设置线程名称:setName()或构造函数
获取线程对象以及名称:
    线程都有自己默认的名称。Thread-编号,该编号从0开始。
    线程中有传递名称的构造函数
    static Thread currentThread():获取当前正在执行的线程对象
    getName():获取线程名称

5.控制线程
join方法:用来临时加入线程执行
调用join方法的线程对象强制运行,该线程强制运行期间,其他线程无法运行,
必须等到该线程结束后其他线程才可以运行。也把这种方式称为联合线程
join方法的重载方法:
join(long millis):
join(long millis,int nanos):
join:等待该线程终止
join():申请CPU执行权,结束后,主线程才恢复到运行状态.
join():当A线程执行到了B线程的join()方法时,A就会等待,等B线程都执行完时,A才会执行。

后台线程:处于后台运行,任务是为其他线程提供服务。也称为“守护线程”或“精灵线程”。
        JVM的垃圾回收就是典型的后台线程。
    特点:若所有的前台线程都死亡,后台线程自动死亡。
    设置后台线程:Thread对象setDaemon(true);
    setDaemon(true)必须在start()调用前。否则出现IllegalThreadStateException 异常;
    前台线程创建的线程默认是前台线程;
    判断是否是后台线程:使用Thread对象的isDaemon()方法;
    并且当且仅当创建线程是后台线程时,新线程才是后台线程。
    setDeamon():后台线程,将该线程标记为守护线程或用户线程。必须在启动线程前调用。
    当所有的前台线程都结束后,后台线程自动结束。
    当正在运行的线程都是守护线程,Java虚拟机退出。       
线程休眠:sleep,让执行的线程暂停一段时间,进入阻塞状态。
    sleep(long millis) throws InterruptedException:毫秒
    sleep(long millis,int nanos)throws InterruptedException:毫秒,纳秒
    调用sleep()后,在指定时间段之内,该线程不会获得执行的机会。
控制线程之优先级:
    每个线程都有优先级,优先级的高低只和线程获得执行机会的次数多少有关。
    并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度;
    默认情况下main线程具有普通的优先级,而它创建的线程也具有普通优先级。
    Thread对象的setPriority(int x)和getPriority()来设置和获得优先级。
        MAX_PRIORITY : 值是10
        MIN_PRIORITY : 值是1
        NORM_PRIORITY : 值是5(主方法默认优先级)
        d.setPriority(Thread.MAX_PRIORITY)
线程礼让:yield,暂停当前正在执行的线程对象,并执行其他线程;
    yield是Thread的静态方法,可使当前线程暂停,但是不会阻塞该线程,而是进入就绪状态。
    所以有可能:某个线程调用了yield()之后,线程调度器又把他调度出来重新执行。
    yield():暂停当前正在执行的线程对象。
    Thread.yield():临时停止,临时释放,达到线程都有机会运行。

6.多线程安全问题:
通过Thread.sleep(long time)方法来简单模拟延迟情况。
导致安全问题的出现的原因:多个线程访问出现延迟;线程随机性。
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

7.同步:解决安全问题
三种方法:

同步代码块:
    synchronized(obj)
    {
        执行语句
    }
同步方法:同步监听器其实的是this
    格式:在方法上加上synchronized 修饰符即可。(一般不直接在run方法上加!)
    synchronized 返回值类型方法名(参数列表)
    {
        执行语句
    }
静态方法的同步:静态方法的默认同步锁是当前方法所在类的.class 对象
    使用的锁是该方法所在类的字节码文件对象。类名.class这个对象在内存中是唯一的
static不能和this连用
同步锁:jkd1.5后的另一种同步机制:
    通过显示定义同步锁对象来实现同步,这种机制,同步锁应该使用Lock对象充当。
    在实现线程安全控制中,通常使用ReentrantLock(可重入锁)。使用该对象可以显示地加锁和解锁。
    具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
    lock替换synchronized
    condition对象的await(),signal(),signalAll()
    替换Object对象的wait(),notify(),notifyAll()
    lock的方法lock和unlock

同步的前提:
    1.必须要有两个或两个以上的线程。
    2.必须是多个线程使用同一个锁。
    必须保证同步中只有一个线程在运行。
同步代码块利弊:
    好处:解决了多线程的安全问题
    弊端:多个线程都需要判断锁,较为消耗资源。
如何找问题?
    1.明确哪些代码是多线程运行代码
    2.明确共享数据
    3.明确多线程运行代码中哪些语句是操作共享数据的
死锁:同步中嵌套同步,而锁却不同。

8.等待唤醒机制
boolean flag=true/false;

等待唤醒机制
    wait():等待-----让线程进入线程池
    notify():唤醒---
    notifyAll():唤醒线程池中的全部
    以上都是object中的方法,所以异常只能try,不能throw
    都使用在同步中,必须标示他所操作的线程所持有的锁。
wait(),notify(),notifyAll()都使用在同步中,
--因为要对持有监视器(锁)的线程进行操作,所以要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义在wait(),notify(),notifyAll()object类中呢?
--因为这些方法在操作同步线程时,都必须要标示他们所操作线程持有的锁。
只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程唤醒。
也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,
所以可以被任意对象调用的方法定义在object中。
为什么定义notifyAll()?
--因为需要唤醒对方线程,只用notify,容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待。

同步锁:lock
Lock代替了同步方法或同步代码块,lock替换synchronized
Condition代替了同步监视器的功能;
Condition对象通过Lock对象的newCondition()方法创建;
condition对象的await(),signal(),signalAll()
替换Object对象的wait(),notify(),notifyAll()
方法包括:
    await(): 等价于同步监听器的wait()方法;
    signal(): 等价于同步监听器的notify()方法;
    signalAll(): 等价于同步监听器的notifyAll()方法;
    lock的方法lock和unlock
释放锁的动作一定要执行finally{lock.unlock()}

9.停止线程
如何停止线程?
–只有一种,run方法结束。
开启多线程运行,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。

特殊情况:
    当线程处于了冻结状态,就不会读取到标记,那么线程就不会结束。
    当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,
    强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。
Thread类中提供了该interrupt方法。
Thread类中的interrupt方法。
    interrupt():将处于冻结状态的线程,强制恢复到运行状态中。
    interrupt()是在强制清除冻结状态。
    sleep().wait()notify()是正常唤醒。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值