------- android培训、java培训、期待与您交流!
多线程
进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元,一般运行一个应用程序,就生成了一个进程, 这个进程拥有自己的内存空间。
线程:就是进程中的一个独立的控制单元
l 进程 一个进程中至少有一个线程
l 线程(例:FlashGet)
l 多线程存在的意义和特性。
l 线程的创建方式 thread run() start()
java VM 启动的时候就会有一个进程java.exe
该进程中至少有一个线程负责java程序的执行。而这个线程运行的代码存在于main()方法中,该线程称之为主线程。
扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程
补充:
进程概念
进程是表示资源分配的基本单位,又是调度运行的基本单位。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,把该进程放人进程的就绪队列。进程调度程序选中它,为它分配CPU以及其它有关资源,该进程才真正运行。所以,进程是系统中的并发执行的单位。
线程概念
线程是进程中执行运算的最小单位,亦即执行处理机调度的基本单位。如果把进程理解为在逻辑上操作系统所完成的任务,那么线程表示完成该任务的许多可能的子任务之一。
1,如何在自定义代码中 自定义一个线程??
创建线程方式一 继承Thread类
继承Thread类
- 继承Thread类的子类覆盖父类中的run方法,将线程运行的代码存放在run中。
- 建立子类对象的同时线程也被创建。
- 通过调用start方法开启线程,同时调用run方法。
public class ThreadTest extends Thread {
public voidrun() {
// 在这里编写线程执行的主体
// dosomething
}
线程命名
继承Thread线程 可以通过构造函数 直接为线程命名 而实现Runnable的线程必须通过new Thread(Runnable target, String name)为线程命名,,当没有显示为线程命名的时候。线程就使用自己默认的名称。Thread-编号,该编号默认从0开始
继承Thread线程 可以通过this.getName()或者Thread.currentThread().getName()来获取当前线程名字 而实现Runnable的线程必须通过Thread.currentThread().getName()来获取当前线程名字
一个线程只可以开启一次及一个线程只可以一次start();
Cpu在某一时刻只能执行一个程序cpu一直在切换执行每一个进程中的线程多个cpu可以同时执行多个进程多线程随机获取执行权 cpu随机选取线程执行 出现了多线程随机执行
明确一点,在某一时刻,只能有一个线程在运行(多核除外)cpu在做着快速的切换,已达到看上去是同时运行的效果,我们可以形象的把多线程的运行行为理解为在互相抢夺cpu的执行权。这就是多线程的一个特性:随机性。谁抢到就执行谁,至于执行多久,cpu说的算
可是为什么 要覆盖run方法呢??
Thread类用于描述 线程,该类就定义了一个功能,用于存储线程要运行的代码,具备该存储功能的就是run方法,也就是说Thread类中的run方法用于存储 要运行的代码
虚拟机定义 主线程的代码存储在main函数里 所以虚拟机必须调用main方法 其他函数只要main方法中没调用就不会被调用 main函数就是一个线程而且是主线程其他线程要通过thread类的子类开启
创建线程方式二 Runnable
1. 创建子类实现Rubbable接口
2. 子类覆盖接口中的run方法。
3. 通过Thread类创建线程对象
4. 将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
5. Thread类对象调用start方法开启线程。Thread通过start()函数开启线程 线程内通过调用Runnable子类的对象来调用run()方法,,,所以说调用run()方法的对象是Runnable子类的对象
public class RunnableTest implements Runnable {
public void run() {
// 在这里编写线程执行的主体
// do something
}
}
main函数中开启线程的方法 new Thread(newRunnableTest()).start();
思考:为什么要给Thread类的构造函数传递Runnable的子类对象?
因为,自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定指定对象的run方法,就必须明确该run方法所属对对象
实现方式和继承方式有什么区别呢
这两种实现方式的区别并不大。继承Thread类的方式实现起来较为简单,但是继承它的类就不能再继承别的类了,因此也就不能继承别的类的有用的方法了。而使用是想Runnable接口的方式就不存在这个问题了,而且这种实现方式将线程主体和线程对象本身分离开来,逻辑上也较为清晰,所以推荐大家更多地采用这种实现方式。
通过继承Thread开启的线程 开启了几个线程 就有几个各自的run()方法 无法实现资源共享
例子:多个窗口买票问题,每个窗口买票1000张点击查看代码
通过实现Runnable开启的线程 开启几个线程 这些线程都共享一个run()方法 操作的是同一个资源
例子:多个窗口买票问题,所有窗口一共买票1000张 点击查看代码
继承Thread:线程代码存放在Thread子类的run方法中
实现Runnable:线程代码存放在接口的子类对象的run方法中
实现方式的好处:避免了单继承的局限性,在定义线程时,建议使用实现方式。Java只支持单继承
线程安全问题
导致安全问题的出现的原因:
多个线程访问出现延迟。线程执行的随机性。当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程就参与进来执行,导致共享数据错误。
解决方法:
对多条线程操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行
注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
sleep方法需要指定睡眠时间,单位是毫秒。 需要内部try
一个特殊的状态:就绪。具备了执行资格,但是还没有获取资源。
同歨(sychronized)
格式: 上厕所例子
synchronized(对象) 锁是一个对象 Object
{
需要同步的代码;
}
同步可以解决安全问题的根本原因就在那个对象上。
该对象如同锁的功能。持有锁的线程可以在同步中执行,没有持有锁的线程技术获取cpu的执行权,也进不去,因为没有获取锁
同步的特点
同步的前提:
1, 同步需要两个或者两个以上的线程。
2, 多个线程使用的是同一个锁。
未满足这两个条件,不能称其为同步。
同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。 对象A执行完后 对象B才能执行 A出了B才能进
如何找问题:
1, 明确哪些代码是多线程运行代码
2, 明确共享数据
3, 明确多线程运行代码中哪些语句是操作共享数据的
同步函数
格式:
在函数上加上synchronized修饰符即可。
思考:同步函数用的是哪个锁呢? this
函数需要被对象调用,那么函数都有一个所属对象的引用,就是this。所以同步函数使用的锁是this
如果同步函数被静态修饰后,使用的锁匙什么呢??
通过验证,发现并不是this,因为静态方法也不不可以定义this。类名.class
静态进内存,内存中没有本类对象,但是一定有该类对应的字节码文件对象。类名.class 该对象类型Class
静态 函数 的同步方法:使用的锁匙该方法所在类的字节码文件对象,;类名.class
执行资格 &&& 执行权 &&&& 注意临界状态
性别问题引出的线程通信
当没使用同步的时候 输出的人对应的性别出现错位 张冠李戴 点击查看代码
当使用到同步的时候 输出的人对应的性别正确 但是输出的是一段男一段女 点击查看代码
如何让输出为男女交替呢??此时涉及到线程间的通信,等待唤醒机制 点击查看代码
sleep()和wait()的区别
sleep()方法和wait()方法都成产生让当前运行的线程停止运行的效果,这是它们的共同点。
这两个方法都可以让调用它的线程沉睡(停止运行)指定的时间,到了这个时间,线程就会自动醒来,变为可运行状态,但这并不表示它马上就会被运行,因为线程调度机制恢复线程的运行也需要时间。调用sleep()方法并不会让线程释放它所持有的同步锁;而且在这期间它也不会阻碍其它线程的运行。上面的连个方法都声明抛出一个InterruptedException类型的异常,这是因为线程在sleep()期间,有可能被持有它的引用的其它线程调用它的interrupt()方法而中断。中断一个线程会导致一个InterruptedException异常的产生,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。
当调用了某个对象的wait()方法时,当前运行的线程就会转入等待状态,等待别的线程再次调用这个对象的notify()或者notifyAll()方法(这两个方法也是本地方法)唤醒它,或者到了指定的最大等待时间,线程自动醒来。如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()方法的对象。wait()方法同样会被Thread类的interrupt()方法中断,并产生一个InterruptedException异常,效果同sleep()方法被中断一样。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
wait():导致当前线程等待。当前线程必须拥有此对象监视器。等待线程都存在线程池中
notify():唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。
notifyAll() :唤醒在此对象监视器上等待的所有线程。
Wait() notify()全部用在同步当中,同时还必须明确锁,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。
线程运行的时候 内存中会创建一个线程池 等待线程都存在线程池中
为什么这些操作线程的方法要定义在Object类中呢?对象就是充当锁的角色
:因为这些方法在操作同步线程时,都必须要标识它们所操作的线程 持有的锁,因为只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,noty()不可以对不同锁监视的线程进行唤醒。
也就是说 等待和唤醒必须持有同一个锁对象只能被锁对象调用,,锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
、、、、、···、、、、、、、、、、、
生产者消费者 需求:生产一个商品消费一个商品,两线程完成,一个线程负责生产一个负责消费 发现 如期输出 没有异常 点击查看代码
当有两个线程负责生产,两个线程负责消费的时候 ,发现输出有异常,会出现生产一个消费两次还有 生产两个 只有一个被消费
解决方案:对生产和消费的两个线程 每次被唤醒都通过while进行flag判断,生产之后唤醒所有线程(否则会出现所有线程都等待) 结果输出正常
但是以上的解决方案不合理,需求是唤醒对方线程 点击查看代码
生产者消费者 引出的1.5新特性:多线程的升级方案,将同步synochronized替换成显示的Lock操作。将Object中的wait,notify,notifyAll替换成了Condition 对象
该对象Condition 可以通过Lock锁获取(newCondition()) 实现了本方只唤醒了对方线程的操作
======================================================================================
接口 Condition java.util.concurrent.locks
通过Lock子类对象的newCondition()获取 一个线程一个Condition对象
void await() 与此 Condition 相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,
boolean await(long time, TimeUnit unit) time - 最长等待时间 unit - time 参数的时间单位
造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
void signal() 唤醒一个与此condition关联的等待线程。
void signalAll() 唤醒所有此condition关等待线程。
接口 Lock java.util.concurrent.locks 通过其子类ReentrantLock
获取Lock对象
void lock() 获取锁。
void unlock() 释放锁。
Condition newCondition() 返回绑定到此 Lock 实例的新Condition 实例。
Lock l =new ReentrantLock(); l.lock(); try { // access theresource protected by this lock } finally { l.unlock(); } 锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或try-catch 加以保护,以确保在必要时释放锁。 |
停止线程
由于stop方法已经过时。但是如何停止线程呢:::
只有一种方法:run()方法结束,线程自然结束
开启多线程运行,运行代码通常是循环结构、只要控制住循环,就可以让run()方法结束,也是线程结束
特殊情况
当线程处于冻结状态,就不会读取到标记,那么线程就不会结束
当没有指定的方式让冻结的线程恢复到运行状态时,这时就需呀对冻结进行清除,强制让线程恢复到运行状态中来,这样就可以操作标记线程结束。。
interrupt()该方法就是 结束线程冻结状态 让线程恢复到运行状态中
join
当A线程执行到了B线程的join()方法时,A就会等待。等B线程都执行完,A才会执行
join方法可以用来临时加入线程执行
调用join()会抛异常 所以函数 要声明抛异常 throws Exception
Thread.currentThread().toString() 输出的是 线程名称 线程优先级 线程组
由主函数开启的线程 此线程的线程组就是主函数
线程对象.setDaemon(true)此线程设置为后台线程或者说守护线程 主线程运行结束 虚拟机退出 导致 此线程也结束 此方法必须在线程开启前调用 才能够说明线程是守护线程
主线程结束(虚拟机退出)守护线程结束
============================================================================================
线程同步中的单例模式
//单例 饿汉模式
class single {
private static single s = new single();
private single() {
}
public static single getinstance() {
return s;
}
}
// 单例 懒汉模式
class single2 {
private static single2 s = null;
private single2() {
}
public static synchronized single2 getinstance()
{
if (s == null)
s = new single2();
return s;
}
}