目录
一、进程和线程的区别
1.1 进程
1.2 线程
二、并发和并行
2.1 并行
2.2 并发
2.3 监控线程的执行情况
三、创建方式
3.1 继承Thread类
思考:为什么不直接通过对象调用start()方法?
3.2 实现Runnable接口
3.3 使用Callable创建线程
3.4 Callable 和 Runnable的区别
继承Thread和实现Runnable接口两种方法的区别
3.5使用线程池例如Executor框架
3.5.1 概念
3.5.2 好处
3.5.3 线程池执行基本原理
3.5.4 线程池创建方式
3.5.5 线程池的分类
3.5.6 线程池的核心参数
四、常用方法
五、多线程实现方案
六、线程终止
七、常用方法
八、线程的生命周期
8.1 线程的状态
8.2 wait()、notify()、notifyAll()类比
九、 线程安全
9.1 安全问题原因编辑
9.2 synchronized
9.3 Lock
9.4 线程锁
9.5 并发工具类 ConcurrentHashMap HashMap HashTable
9.6 线程死锁
9.7 释放锁
十、思考题
1.volatile关键字
2.count++ 是不是原子操作
3.怎么保证原子操作
4.原子类原理--CAS原理
5.ConcurrentHashMap HashMap 和HashTbale的区别
一、进程和线程的区别
1.1 进程
操作系统中正在运行的软件,比如我们使用的QQ,就启动了一个进程,操作系统就为该进程分配了新的内存空间,进程具有独立性,动态性和并发性
- 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
- 动态性:进程的实质是程序的一次执行过程,进程是动态产生的,动态是消亡的
- 并发性:任何进程都可以同其它进程一起并发执行
1.2 线程
线程是由进程创建的,是进程的一个实体,一个进程可以拥有多个线程。是进程中的单个顺序控制流,是一条执行路径。就是应用程序中做的事情。比如:360软件中的杀毒,扫描木马,清理垃圾
单线程
同一个时刻只允许执行一个线程
多线程
同一个时刻,可以执行多个线程
线程
用户线程
也称为工作线程,当线程的任务执行完或以通知的方式结束
守护线程
一般是为工作线程服务的,当所有的用户线程结束后,守护线程自动结束,常见的守护线程有 垃圾回收机制
应用:如果我们希望当main线程结束之后,子线程自动结束,只需要将子线程设置为守护线程即可。设置方法 实例.setDaemon(),位置在实例.start()之前
注意:一个进程至少包括一个线程
二、并发和并行
2.1 并行
在同一时刻,有多个指令在多个CPU上同时执行
造成一种 貌似同时 的错觉,简单的说,单核cpu实现的多任务就是并行
2.2 并发
在同一时刻,有多个指令在多个CPU上交替执行
多核cpu可以执行并行
Notes:并发、并行可以同时发生
2.3 监控线程的执行情况
在终端输入Jconsole可以监控线程的执行情况
三、创建方式
3.1 继承Thread类
在继承Thread类后,该类就可以当做线程使用。往往要重写run()(Thread实现了Runnable接口),在此方法内写入自己的业务逻辑,运行需要创建该类的实例,通过实例调用start方法。
思考:为什么不直接通过对象调用start()方法?
当我们运行一个程序时相当于启动了一个线程,当进程启动了以后,在进程开了一个主线程,当main线程执行 实例.start()主线程不会阻塞,会继续执行,如果直接去调用run(),此时的run()就是一个普通的方法,会发生阻塞,没有真正的开启一个线程,只有将run()执行完之后才会去执行其他的语句。真正实现多线程效果的是native 方法 start0()而不是run()。注start0()是由JVM调用,底层是c/c++来实现的(主线程执行完任务就会结束的,但是有子线程在运行时,进程也不会结束的,当所有的线程结束了,进程就结束了)
3.2 实现Runnable接口
Notes :java是单继承的,在某些情况下一个类可能已经继承了某个父类,这是再用继承Thread类方法来创建线程显然是已经不可能的了。因此Java设计者们提供了另一个方法创建线程,就是通过实现Runnable接口来实现方法
运行需要新建一个线程,将实现Runnable接口的方法的类的实例放入线程的有参构造的方法,这里是使用设计模式的代理模式 。
3.3 使用Callable创建线程
和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。
3.4 Callable 和 Runnable的区别
- call()方法可以有返回值
- call()方法可以声明抛出异常 callable接口实现类中的run方法允许异常向上抛出,可以在内部处理,try catch,但是runnable接口实现类中run方法的异常必须在内部处理,不能抛出
callable和runnable都可以应用于executors。而thread类只支持runnable,
继承Thread和实现Runnable接口两种方法的区别
- 从java的设计来看,通过继承Thread或者实现Runnale接口来创建线程本质上没有区 别,Thread本质上也是实现了Runnable接口。
- 实现Runnbale接口的方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制
3.5使用线程池例如Executor框架
3.5.1 概念
盛放线程的容器,盛放线程的池子
3.5.2 好处
1. 减少频繁创建及销毁线程的操作,节省资源
2. 线程池会将闲置的线程处理任务,达到线程复用
3.5.3 线程池执行基本原理
1. 创建一个池子,此时没有线程 pool.getPoolSize()
2. 当第一次提交任务给线程池,线程池就会创建线程执行任务。当任务执行完毕。线程对象归还给池子线程并不会消亡
3.当再次提交任务,此时线程池就会将闲置的线程去处置任务,如果没有闲置的线程,才会创建新的线程
3.5.4 线程池创建方式
创建线程池
ExecutorService pool = Executors.newCachedThreadPool();
线程池最多创建int最大值个线程
ExecutorService pool = Executors.newFixedThreadPool(3);
创建线程池对象,规定最大数
创建一个线程池,根据需要创建新的线程,但在可用时将重用先前构建的线程
方式一
1. 创建一个任务对象
MyTarget myTarget = new MyTarget();
2. 提交任务给线程池
pool.submit(myTarget);
过去的方式 Thread t1 = new Thread(myTarget);
缺点:自己创建线程,执行完任务,消亡浪费资源,系统资源开销大
3. 如果不需要线程池,将其关闭
pool.shutdown();
方式二
匿名内部类
3.5.5 线程池的分类
3.5.6 线程池的核心参数
1. int corePoolSize
线程池核心线程大小,如果你的任务数量小于核心线程数,那么开启的线程数量还是三个
2.int maximumPoolSize
线程池最大线程数,基于KeepaliveTime会销毁
3.KeepaliveTime
空闲线程存活时间
4.TimeUnit 时间单位
时间单位,为 keepAliveTime 指定时间单位
5.workQueue 阻塞队列,用于保存任务的阻塞队列
1). ArrayBlockQueue 基于数组的有界队列,FIFO,防止资源耗尽问题
2). LinkedBlockQueue基于链表的无界阻塞队列,界值为Integer.MAX FIFO
Tips:maximumPoolSize参数是没有用的
3). SynchronousBlockQueue(不缓存任务队列)
Tips:maximumPoolSize参数是有用的,如果超过了会执行拒绝策略
4). ProprityBlockQueue() 几区Comparator实现,比较 无界阻塞队列
6.ThreadFactory
创建线程的工程类,例如守护线程daemon
7.RejectedExecutionHandler handler 拒绝策略
1). callerrunsPolicy(除非线程池shutdown,抛弃任务)
2). AbortPolicy(直接抛出异常)
3). DiscardPolicy(什么都不做,直接丢弃任务) //不建议用
4). DiscardOldestPolicy(什么都不做,直接丢弃队列里面最早的任务)
四、常用方法
用户线程:也叫工作线程,当线程的任务执行完或以通知方式结束
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
常见的守护线程:垃圾回收机制
五、多线程实现方案
六、线程终止
七、常用方法
八、线程的生命周期
8.1 线程的状态
8.2 wait()、notify()、notifyAll()类比
九、 线程安全
9.1 安全问题原因
解决方式
1.同步代码块
2.同步方法
3.Lock锁
9.2 synchronized
1、 Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2、每个对象都对应一个可称为互斥锁的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象。
3. 关键字sychronized来与对象的互斥锁联系,当某个对象用sychronized修饰时,表名该对象在任一时刻只能由一个线程访问。
4.同步的局限性:导致程序的执行效率要降低
5.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
6. 同步方法(静态的)的锁为当前类本身
Java中static作用及用法详解_CrazyCodeBoy的博客-CSDN博客_java static
被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。
被static关键字修饰的属性或方法不属于实例化的对象,而是属于类,而this属于当前操作的实例对象,所以同步static锁在了类上
public class demoSell{
//静态方法
public synchronized static void method1(){
}
//静态方法中的同步代码块
public static void mehtod2(){
synchronized(demoSell.class){
}
}
}
注意:
1.同步方法如果没有使用static修饰:默认所对象为this
2.如果方法使用static修饰,默认锁对象:当前类.class
3.实现的落地步骤
·需要先分析上锁的代码
·选择同步代码块或同步方法
·要求多个线程锁对象为同一个即可
1.同步代码块
synchronized(对象){//得到对象的锁,才能操作同步代码块
//需要被同步代码
}
2. sychronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void method(String name){
//需要被同步的代码
}
9.3 Lock
//创建可重复锁
Lock lock = new ReentrantLock();
//上锁
lock.lock();
//代码块
.............
//释放锁
lock.unlock();
9.4 线程锁
9.5 并发工具类 ConcurrentHashMap HashMap HashTable
9.6 线程死锁
概念:
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程一定要避免死锁的发生
9.7 释放锁
1. 当前线程的同步方法,同步代码块执行结束后会自动释放锁
2. 当前线程在同步代码块,同步方法中遇到break、return
3. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停并且释放锁
注意:下面的情况不会释放锁
1.线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁。
2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁,应该避免使用suspend()和resume()来控制线程,方法不再推荐使用。
十、思考
1.volatile关键字
在多线程的情况下保证了数据的可见性,加了volatile的属性,一个线程修改后,另一个线程也能看到
原理:一个线程修改了数据,就会把数据刷新到主存中,其他线程读取到的副本就会失效,重新读取主存中的新数据
2.count++ 是不是原子操作
不是,三步操作,:
1.从主存中count的值存到A线成独有的空间
2.在线程独有工作空间中执行+1操作
3.将101重新刷新relaod到主存中
随时可能丢失CPU的执行权,比如在执行+1操作还未刷新到主存,就被其他线程B抢占
3.怎么保证原子操作
1.同步代码块
2.原子类AtomicInteger
4.原子类原理--CAS原理
在修改主存中的值,先拿旧值和主存中的值判断
如果相等,则直接修改
如果不等,说明已经有其他线程修改过了,需要重新获取主存中的新值
这里涉及到三个变量
1. 旧值: 第一次获取主存中值时,作为旧值存放在该变量中, 比如100
2. 新值 该线程操作后的值,比如执行++操作后的101
3.要修改的值 主存里面的值
如果旧值和要修改的值相等,说明没有被修改过,可以直接赋值
5.ConcurrentHashMap HashMap 和HashTbale的区别
HashMap效率最高 但是不安全
HashTable 效率最低 安全 每个方法都加锁
ConcurrenetHashp 效率第二高 安全
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)