首页 > 技术文章 > Java基础总结--多线程总结1

sun1993 2017-09-15 11:38 原文

----进程和线程-----
1.概述:简单理解一个进程就是一个正在运行的程序(程序在内存中的所属空间)
程序只有在运行的时候才会被加载进内存
2.进程内部的划分
进程不会直接执行,只是被当作分配内存资源的基本单位,真正的执行单位就是线程(执行路径),一个进程至少含有一个线程,多个线程就代表多个任务,多个执行路径,可以并发运行,所以它们之间存在一定的制约条件 eg:可能存在多个线程合作完成某个任务,可能访问资源存在顺序关系。加载程序代码进内存--从某个入口执行
3.多线程存在的意义:并发执行,一段时间内可以同时执行多个任务
注意:并发并没有同时执行多个任务,微观串行,宏观并行。CPU控制程序运行,负责调度线程的运行(分时间片调度线程执行--时间片很短)
假如存在两个CPU,此时能够做到真正的并行执行--缺点还是没有足够的内存
4.多线程的好处和弊端
优点:能够解决多个程序一段时间内的并发执行
缺点:多线程被CPU调度的等待时间加多
5.目前只有一个线程--主函数线程(必须依赖于线程的执行),堆里面出现垃圾,垃圾回收线程
JVM一开启就会启动多个线程eg:主线程,垃圾回收线程等
线程的任务执行完成,线程就结束了,JVM会等着所有线程结束,JVM才会退出
6.垃圾回收器并不会一遇到垃圾就回收,而是先判断堆内存的大小,根据内存大小决定是否进行堆内存回收
7.主线程--仅仅存在时候,程序只有一条执行路径,按照代码书写的顺序执行(方法调用还是一个线程),JVM判断没有其他内容就直接关闭进程
8.什么时候可能会想到使用多线程
A-->B-->C 按照该顺序,但是B事件此时需要的循环时间花费大,所以此时就自定义一个线程,用来完成C时间的执行

-----多线程具体学习-----底层的执行与系统相关(Thread里面的方法基本是与系统相关的本地方法)
注意:关于为什么要继承Thread类,而不是直接创建Thread类对象
原因是:创建线程为了创建执行路径,主线程其执行的任务都定义在main方法里面,自定义的线程其对应的任务应该定义在Thread类定义的run()方法里面,所以不同的线程必须要进行特殊的任务时候,必须重写run(),进而就必须继承Thread类。

-----如何创建一个线程------
* 继承Thread类(已经实现Runnable接口),重写run方法
创建Thread子类的对象,启动线程 对象.start()并告诉JVM自动调用对象的run()方法
注意:利用子类对象调用run()此时不是启动线程,而是方法调用
CPU调动线程是随机的,所以执行的结果就是随机的
getName()获取线程对象的名字
Thread.currentThread()获取当前运行线程对象的引用
* 多线程运行图解
创建启动一个线程---就创建一个单独的路径(相当于每个线程有在进程的内存空间里面占有固定的空间,线程执行完后释放空间)
eg:有3的线程名字分别为1,2,3,他们分别执行,他们各自执行自己的内容(假如)
假如主线程出现异常,自会自己不执行下去,并不会导致其他线程的执行状态发生改变
谁发生异常,那个线程就结束---用于分析多线程安全问题
-----什么时候使用多线程----
某个代码要被同时执行,比如打印循环--可以将这些代码的执行利用线程来实现
让该类继承Thread类,重写run(){被多次执行的代码}。
问题出现:假如该类以及继承一个父类,无法进行继承Thread类,可以采用第二种创建线程的方式。
* 定义类实现Runnable接口,重写run()方法,创建Thread对象接受Runnable接口的子类对象
(接口可以对类的功能进行扩展)
MyThread implements Runnable{
public void run(){线程体;}
}
MyThread mt = new MyThread();
Thread t = new Thread(mt);--存在多态 t.start();
第二种创建设计思想:持有接口的引用(调用第二种构造线程对象的构造方法)-看源码
第二种创建线程好处
* 在第一种方法中继承Thread()类,其实就是仅仅想使用run()方法,并没有想要继承Thread类里面的所有方法,此时我们就可以直接实现接口Runnable(只用重写一个run方法就可以了)
* 第二个好处在要执行多次的类已经有父类的情况下,选择实现接口--避免单线程局限性
所以创建线程的第二种方式比较常见
* Thread类也是实现了Runnable接口

------线程的状态转化-------
1.被创建状态---变成线程对象
2.调用start被启动成为就绪状态--等待CPU的调度-有被执行的资格
3.假如CPU调动--就变成运行状态--CPU正在处理该线程
(就绪--运行 运行--就绪 运行(等待事件的发生/自己调用sleep休眠一定的时间)--阻塞 阻塞(被别的对象唤醒)--就绪)通过对象调用wait/notify方法(Object里面的方法)
调用sleep()后会让出CPU的执行权,等醒来后,再继续在就绪队中排队等待调度
注意:sleep()在那个线程体里面,那个线程休眠
4.阻塞状态:释放执行权被CPU调度,放弃执行资格
5.线程死亡:线程被强制结束stop/线程体执行完了
6.线程启动后,不能重复启动---会发生异常

------多线程安全问题的引出------
四个卖票卖完--多线程同时执行--
1.方法一处理:继承Thread类,此时num会存在于4个线程对象中,总数400票
可以静态处理num--但是这不太符合题目的要求
2.方法2处理实现接口,但是只用创建一个Ticket对象,所以总数字就是100票
3.多线程安全问题产生的原因
* 多线程操作共享数据
* 操作共享数据的代码有多条
* 当一个线程在操作共享数据的多条代码中时候,又有其他线程参与其中,就会导致线程安全问题的产生
------多线程问题的解决办法-------
本质就是防止一个线程在操作共享数据的时候,另外没有其他线程的参与
具体的设计:
就是将多线程操作共享数据的代码封装起来,当有线程执行代码的时候,不允许其他的线程访问,只有当该线程执行完后,别的线程才可以访问。
1.对象锁synchronized修饰代码块--同步代码块--实现同步
具体格式synchronized(对象){需要被同步的代码;}
对象--this/其他任何对象
观察结果:只有部分线程打印--原因就是其他的线程未抢到被执行的机会
2.修饰方法--同步方法(需要被按照次序执行)---锁定的是该方法所属对象--也是一种封装
锁是方法所属对象--this
可以让线程1--使用同步代码块;线程2--使用同步方法,只要他们的锁定为同一个this,就可以实现线程1,2的同步:
注意:eg:卖票例子的具体实现过程:
1.创建2个卖票线程,以及本来就存在的主线程
2.主线程运行,启动线程1,主线程休眠让出cpu执行权,线程1获得执行权,持有this锁进入run方法里面,先休眠(该过程不会释放锁),醒来后继续执行(或者),cpu分配的时间片结束后,让出cpu执行权。
3.主线程修改Flag参数,线程2启动并获得执行可能执行到结束。

-----同步代码块与同步方法的区别-----
同步函数的对象锁---this
同步代码块的锁---可以为任意的对象
一般比较推荐使用同步代码块

----静态同步函数的锁----
静态同步函数的锁:所属类的字节码文件被封装的为Class类型的对象--字节码文件对象
获取该对象的方式:this.getClass()/Thicket.class(字节码文件对象具有静态属性class)

----同步使用的前提----
同步并不能解决所有的线程安全问题,它解决的是问题必须具备以下两点
1.程序中存在多个线程
2.保证多个线程访问同一个资源时候使用相同的锁,否则就不能保证某个线程在访问该资源的时候,没有其他线程的进入。
3.锁:其实本质就是保证线程在被CPU调度后,执行的过程中不会存在别的线程的进入
(不管任何任何情况--休眠/时间片用完等情况,只要是线程处在同步中,就会保证只有一个线程在其中--一旦出了同步其他线程在获得调度权的情况下,就可以进入同步中)
4.多个线程访问共享资源,有安全隐患,并不是每次都发生安全问题

---单例设计模式与多线程安全----
单例设计模式
1.懒汉式--延迟加载单例模式,先创建引用,调用get方法的时候,再创建对象--比较推荐
2.饿汉式--创建对象,提供返回该对象的公共的方法
3.对于懒汉式单例---加了同步方法,会每次都判断锁,费时间,所以加同步代码块,会先判断假如已经创建一个对象,不要判断锁,直接获取单例对象。

---死锁----
1.常见形式之一同步嵌套--线程A在具备a锁情况下,还需要b锁,同时线程B在具备b锁情况下,还需要a锁,俩个线程互不相让--导致两个线程都不能进行下去

推荐阅读