发表于: 2017-08-15 22:32:36

1 1162


今天完成的事情:

复习了一下Java的多线程:

1.线程概述

进程:正在运行的程序,负责了这个程序的内存空间分配,代表了内存中的执行区域。
线程:就是在一个进程中负责一个执行路径。
多线程:就是在一个进程中多个执行路径同时执行。

多线程的好处:
1. 解决了一个进程里面可以同时运行多个任务(执行路径)。
2. 提供资源的利用率,而不是提供效率。

多线程的弊端:
1. 降低了一个进程里面的线程的执行频率。
2. 对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。
3. 公有变量的同时读或写。当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,发生线程安全问题。
4. 线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。

2.创建线程

2.1 继承Thread类


使用步骤:
1.继承Thread类
2.重写run()方法,将要执行的任务放在run()方法中
3.调用start()方法启动线程

线程的使用细节:
1. 线程的启动使用父类的start()方法
2. 如果线程对象直接调用run(),那么JVN不会当作线程来运行,会认为是普通的方法调用。
3. 线程的启动只能有一次,否则抛出异常
4. 可以直接创建Thread类的对象并启动该线程,但是如果没有重写run(),什么也不执行。
5. 匿名内部类的线程实现方式

线程的状态:
1. 创建:新创建了一个线程对象。
2. 可运行:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取cpu的执行权。
3. 运行:就绪状态的线程获取了CPU执行权,执行程序代码。
4. 阻临时塞: 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
5. 死亡:线程执行完它的任务时。

常见的方法


Thread(String name)     初始化线程的名字
getName()              返回线程的名字
setName(String name)    设置线程对象名
sleep()                 线程睡眠指定的毫秒数。
getPriority()           返回当前线程对象的优先级   默认线程的优先级是5
setPriority(int newPriority) 设置线程的优先级,虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10 ,最小的1 , 默认是5)。
currentThread()       返回CPU正在执行的线程的对象

2.2 实现Runable接口


创建线程的第二种方式.使用Runnable接口.
该类中的代码就是对线程要执行的任务的定义.
1:定义了实现Runnable接口
2:重写Runnable接口中的run方法,就是将线程运行的代码放入在run方法中
3:通过Thread类建立线程对象
4:将Runnable接口的子类对象作为实际参数,传递给Thread类构造方法
5:调用Thread类的start方法开启线程,并调用Runable接口子类run方法

为什么要将Runnable接口的子类对象传递给Thread的构造函数,因为自定义的run方法所属对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法

理解Runnable:
Thread类可以理解为一个工人,而Runnable的实现类的对象就是这个工人的工作(通过构造方法传递).Runnable接口中只有一个方法run方法,该方法中定义的事会被新线程执行的代码.当我们把Runnable的子类对象传递给Thread的构造时,实际上就是让给Thread取得run方法,就是给了Thread一项任务.

3.锁对象


什么是锁对象?
每个java对象都有一个锁对象.而且只有一把钥匙.
如何创建锁对象:
   可以使用this关键字作为锁对象,也可以使用所在类的字节码文件对应的Class对象作为锁对象
1. 类名.class      
2. 对象.getClass()  

Java中的每个对象都有一个内置锁,只有当对象具有同步方法代码时,内置锁才会起作用,当进入一个同步的非静态方法时,就会自动获得与类的当前实例(this)相关的锁,该类的代码就是正在执行的代码。获得一个对象的锁也成为获取锁、锁定对象也可以称之为监视器来指我们正在获取的锁对象。

因为一个对象只有一个锁,所有如果一个线程获得了这个锁,其他线程就不能获得了,直到这个线程释放(或者返回)锁。也就是说在锁释放之前,任何其他线程都不能进入同步代码(不可以进入该对象的任何同步方法)。释放锁指的是持有该锁的线程退出同步方法,此时,其他线程可以进入该对象上的同步方法。

1:只能同步方法(代码块),不能同步变量或者类
2:每个对象只有一个锁
3:不必同步类中的所有方法,类可以同时具有同步方法和非同步方法
4:如果两个线程要执行一个类中的一个同步方法,并且他们使用的是了类的同一个实例(对象)来调用方法,那么一次只有一个线程能够执行该方法,另一个线程需要等待,直到第一个线程完成方法调用,总结就是:一个线程获得了对象的锁,其他线程不可以进入该对象的同步方法。
5:如果类同时具有同步方法和非同步方法,那么多个线程仍然可以访问该类的非同步方法。
同步会影响性能(甚至死锁),优先考虑同步代码块。
6:如果线程进入sleep() 睡眠状态,该线程会继续持有锁,不会释放。

4.死锁


当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁。
1:两个任务以相反的顺序申请两个锁,死锁就可能出现
2:线程T1获得锁L1,线程T2获得锁L2,然后T1申请获得锁L2,同时T2申请获得锁L1,此时两个线程将要永久阻塞,死锁出现
如果一个类可能发生死锁,那么并不意味着每次都会发生死锁,只是表示有可能。要避免程序中出现死锁。
例如,某个程序需要访问两个文件,当进程中的两个线程分别各锁住了一个文件,那它们都在等待对方解锁另一个文件,而这永远不会发生。
3:要避免死锁

5. 线程间通信

5.1 等待唤醒机制


wait:告诉当前线程放弃执行权,并放弃监视器(锁)并进入阻塞状态,直到其他线程持有获得执行权,并持有了相同的监视器(锁)并调用notify为止。

notify:唤醒持有同一个监视器(锁)中调用wait的第一个线程,例如,餐馆有空位置后,等候就餐最久的顾客最先入座。注意:被唤醒的线程是进入了可运行状态。等待cpu执行权。

notifyAll:唤醒持有同一监视器中调用wait的所有的线程。

线程间通信其实就是多个线程在操作同一个资源,但操作动作不同,wait,notify(),notifyAll()都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。

为什么这些方法定义在Object类中
因为这些方法在操作线程时,都必须要标识他们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被统一锁上notify唤醒,不可以对不同锁中的线程进行唤醒,就是等待和唤醒必须是同一个锁。而锁由于可以使任意对象,所以可以被任意对象调用的方法定义在Object类中

wait() 和 sleep()有什么区别?
wait():释放资源,释放锁。是Object的方法
sleep():释放资源,不释放锁。是Thread的方法

定义了notify为什么还要定义notifyAll?
因为只用notify容易出现只唤醒本方线程情况,导致程序中的所有线程都在等待。

5.2 线程声生命周期


任何事物都是生命周期,线程也是,
1. 正常终止  当线程的run()执行完毕,线程死亡。
2. 使用标记停止线程
注意:Stop方法已过时,就不能再使用这个方法。
如何使用标记停止线程停止线程。
开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,线程就结束。

5.3 后台线程


后台线程:就是隐藏起来一直在默默运行的线程,直到进程结束。
实现:
     setDaemon(boolean on)
特点:
当所有的非后台线程结束时,程序也就终止了同时还会杀死进程中的所有后台线程,也就是说,只要有非后台线程还在运行,程序就不会终止,执行main方法的主线程就是一个非后台线程。

必须在启动线程之前(调用start方法之前)调用setDaemon(true)方法,才可以把该线程设置为后台线程。
一旦main()执行完毕,那么程序就会终止,JVM也就退出了。
可以使用isDaemon() 测试该线程是否为后台线程(守护线程)。

Thread的join方法
当A线程执行到了B线程Join方法时A就会等待,等B线程都执行完A才会执行,Join可以用来临时加入线程执行

明天打算做的事情:

看一遍《MySQL必知必会》

遇到的问题:


收获:



返回列表 返回列表
评论

    分享到