多线程dasdsad
2021-11-30 01:18:03 13 举报
AI智能生成
asdasda
作者其他创作
大纲/内容
线程安全问题(线程同步)
为什么出现安全问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)<br>多线程操作共享数据
如何解决多线程安全问题呢?<br>基本思想:让程序没有安全问题的环境
怎么实现呢?<br>把多条语句操作共享数据的代码给锁起来,<br>让任意时刻只能有一个线程执行即可Java提供了同步代码块的方式来解决
同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现<br>格式:<br>synchronized(任意对象){<br>多条语句操作共享数据的代码<br>)<br>默认情况是打开的,只要有一个线程进去执行代码了,<br>锁就会关闭当线程执行完出来了,锁才会自动办打开
<font color="#ff0000">锁对象唯一</font>
同步代码块不能使用this作为锁对象,因为如果出现创建两个对象,他们进入synchronized时会各自持有一把锁<br>也就是说<font color="#ff0000">对于同步块,由于其锁是可以选择的,所以只有使用同一把锁的同步块之间才有着竞态条件</font>
同步方法
非静态同步方法
同步方法∶就是把synchronized关键字加到方法上·格式∶<br>修饰符synchronized返回值类型方法名(方法参数){}
同步代码块和同步方法的区别:<br>●同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码<br>●同步代码块可以指定锁对象,同步方法不能指定锁对象
同步方法的锁对象是什么呢?<br><font color="#ff0000">this<br>所有的非静态同步方法用的都是同一把锁——实例对象本身<br></font>
静态同步方法
同步静态方法∶就是把synchronized关键字加到静态方法上<br>格式∶<br>修饰符static synchronized返回值类型方法名(方法参数){}
同步静态方法的锁对象是什么呢?<br><font color="#ff0000">类名.class<br>而所有的静态同步方法用的也是同一把锁——类对象本身<br></font>
同步的好处和弊端
同步的好处和弊端<br>好处:解决了多线程的数据安全问题<br>弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率<br>多条语句操作共享数据的代码<br>)<br>默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭当线程执行完出来了,锁才会自动办打开
<br>关于锁和同步,有以下几个要点:<br>1)、只能同步方法,而不能同步变量和类;<br>2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?<br>3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。<br>4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。<br>5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。<br>6)、线程睡眠时,它所持的任何锁都不会释放。<br>7)、线程可以获得多个重进入(synchronized )锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。<br>8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。<br>9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。
Lock锁
Lock
void lock():获得锁
void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法:<br>ReentrantLock():创建一个ReentrantLock的实例<br>
死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。<br><br>通俗点说:死锁就是两个线程同时占用两个资源,但又在彼此等待对方释放锁。<br>
等待和唤醒的方法
是锁对象调用的方法,<br>所可以是任意对象,<br>方法在Object类中
void wait()
void notify()
void notifyall()
两种睡眠
sleep是抱着锁睡的
wait是释放锁睡的
原子性
所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的千扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。<br>
原子性问题
volatile关键字
并不能保证原子性
Atomic类-乐观锁
自旋+CAS
CAS算法:有3个操作数(内存值V,I旧的预期值A,要修改的值B)<br>当旧的预期值A==内存值此时修改成功,将V改为B<br>当日的预期值A!=内存值此时修改失败,不做任何操作<br>并重新获取现在的最新值(这个重新获取的动作就是自旋)<br>
加锁(synchronized等)-悲观锁
并发工具类
集合
Hashtable
1 ,Hashtable采取悲观锁synchronized的形式保证数据的安全性<br>2,只要有线程访问,会将整张表全部锁起来,所以Hashtable的效率低下。<br>
ConcurrentHashMap
1.HashMap是线程不安全的。多线程环境下会有数据安全问题。<br>2.Hashtable是线程安全的,但是会将整张表锁起来,效率低下。<br>3 ,ConcurrentHashMap也是线程安全的,效率较高。<br>在JDK7和JDK8中,底层原理不一样。<br>
JDK7
JDK8
总结<br>1,如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。<br>在第一次添加元素的时候创建哈希表<br>2,计算当前元素应存入的索引。<br>3,如果该索引位置为null,则利用cas算法,将本结点添加到数组中。<br>4,如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,<br>他下面,变成链表。<br>5,当链表的长度大于等于8时,自动转换成红黑树<br>6,以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性。<br>
CountDownLatch<br>
public CountDownLatch(int count)<br>参数传递线程数,表示等待线程数量<br>
public void await()<br>让线程等待<br>
public void countDown()<br>当前线程执行完毕
Semaphore<br>
常用方法
并发与并行
并发
在同一时刻,有多个指令在单个CPU上<font color="#ff0000">交替</font>进行
并行
在同一时刻,有多个指令在多个CPU上<font color="#ff0000">同时</font>执行。
线程与进程
线程
是进程中的单个顺序控制流,是一条执行路径。
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
进程
是正在运行的软件
独立性:进程是一个能独立运行的基本单位,同时也是系统资源分配和调度的独立单位。
动态性:进程的实质是程序的一次执行过程,进程是动态产生的,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行。
多线程实现方案
继承Thread类的方式进行实现
步骤
1.定义一个类MyThread继承Thread类
2.在MyThread类中重写run()方法
3.创建MyThread类的对象
4.启动线程
三个小问题
为什么要重写run()方法?<br> 答:因为run()是用来封装被线程执行的代码
run()方法和start()方法的区别?<br> 答:run():封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程。<br> start():启动线程。然后由JVN调用此线程的run()方法。
void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。<br> 结果是两个线程并发地运行;当前线程(main线程)和另一个线程(创建的新线程,执行其 run 方法)。<br> 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。<br> java程序属于抢占式调度,那个线程的优先级高,那个线程优先执行;同一个优先级,随机选择一个执行
实现Runnable接口的方式进行实现
步骤
1.创建一个Runnable接口的实现类
Runnable是@FunctionalInterface
2.在实现类中重写Runnable接口的run方法,设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法
Runnable接口与Thread类
java.lang.Runnable<br> Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
Thread(Runnable target) 分配新的 Thread 对象。<br>Thread(Runnable target, String name) 分配新的 Thread 对象,并命名该线程
利用Callable和Future接口方式实现
步骤
1.写一个Callable接口的实现类
2.复写call方法。有返回值
3.创建Callable的实现类对象
Callable是@FunctionalInterface
4.创建FutureTask对象,把Callable的子类对象作为参数传递
5.创建Thread对象,把FuTureTask对象作为参数传递
6.调用start方法,开启线程
注意:如果想的到线程的执行结果,在start方法调用之后,调用FuTureTask对象的get方法
Callable接口与Future接口
FutureTask实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值,那么这个组合的使用有什么好处呢?
考虑这样一个情况,使用者可能快速翻页浏览文件中,而图片档案很大,如此在浏览到有图片的页数时,就会导致图片的载入,因而造成使用者浏览文件时会有停顿 的现象,所以我们希望在文件开启之后,仍有一个背景作业持续载入图片,如此使用者在快速浏览页面时,所造成的停顿可以获得改善。<br><br>Future模式在请求发生时,会先产生一个Future物件给发出请求的客户,而同时间,真正的目标物件之生成,由一个 新的执行绪持续进行(即 Worker Thread),真正的目标物件生成之后,将之设定至Future之中,而当客户端真正需要目标物件时, 目标物件也已经准备好,可以让客户提取使用。
三种方式对比
Thread常用方法
方法作用分为三种
获取线程属性
设置线程属性
控制线程状态
设置获取名字
获取
线程是有默认名的,格式:Thread-编号
String getName():返回此线程名称
设置
void setName(String name):将此线程名称更改为参数name
也可以通过过构造方法设置线程名称,但需先在Thread继承子类中重写父类的带参构造
获得当前线程的对象<br>
作用:如果类实现Runnable。调用线程开始后,可以用该方法获取/设置其线程信息
String<br>
public static void sleep(long time)<br> 让线程休眠指定的毫秒值。
线程优先级
线程调度
主线程:执行主(main)方法的线程<br><br> 单线程程序:java程序中只有一个线程<br> 执行从main方法开始,从上到下依次执行<br><br> JVM执行main方法,main方法会进入到栈内存<br> JVM会找操作系统开辟一条main方法通向cpu的执行路径<br> cpu就可以通过这个路径来执行main方法<br> 而这个路径有一个名字,叫main(主)线程
多线程并发运行
分时调度模型∶所有线程<font color="#ff0000">轮流</font>使用CPU的使用权,平均分配每个线程占用CPU的时间片<br>
抢占式调度模型︰优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些<br>
public int getPriority() <br> 获取线程的优先级。
默认优先级是5
public void setPriority(int newPriority) <br> 更改线程的优先级。优先级越高,执行到的几率越大<br> 范围:[1,10]
守护线程
public final void setDaemon(boolean on)将此线程标记为daemon线程或用户线程。
当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。 <br>线程启动前必须调用此方法。 <br><br>参数 <br>on - 如果是 true ,将此线程标记为守护线程
线程池创建
API提供工具类Executors
public static ExecutorService newCachedThreadPool() <br> 创建一个可以根据需要创建新线程的线程池。最多可以创建int最大值的线程数量(默认线程池)
public static ExecutorService newFixedThreadPool(int nThreads) <br> 创建一个固定长度的线程池
public static ExecutorService newSingleThreadExecutor() <br> 创建单个线程的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) <br> 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
自定义线程池
public ThreadPoolExecutor(int corePoolSize,<br> int maximumPoolSize,<br> long keepAliveTime,<br> TimeUnit unit,<br> BlockingQueue<Runnable> workQueue,<br> ThreadFactory threadFactory,<br> RejectedExecutionHandler handler)<br>参数解释:<br> corePoolSize - 核心线程数。<br> maximumPoolSize - 最大线程数。<br> keepAliveTime - 临时线程存活的时间。<br> unit - keepAliveTime 时间单位。<br> workQueue - 阻塞队列。<br> threadFactory - 创建线程的工厂。<br> handler - 如果超过最大线程数,的拒绝策略。
unit - keepAliveTime 时间单位。
//Enum TimeUnit<br> //DAYS 时间单位代表二十四小时。<br> //HOURS 时间单位代表六十分钟。<br> //MICROSECONDS 时间单位代表千分之一毫秒。<br> //MILLISECONDS 时间单位为千分之一秒。<br> //MINUTES 时间单位代表60秒。<br> //NANOSECONDS 时间单位代表千分之一千分之一。<br> //SECONDS 时间单位代表一秒。
阻塞队列
LinkedBlockingQueue<br>
ArrayBlockingQueue
拒绝策略
static class ThreadPoolExecutor.AbortPolicy <br> 用于被拒绝任务的处理程序,它将抛出 RejectedExecutionException. <br><br>
static class ThreadPoolExecutor.CallerRunsPolicy <br> 用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;<br>如果执行程序已关闭,则会丢弃该任务。 (让main线程执行多余的任务)
static class ThreadPoolExecutor.DiscardOldestPolicy <br> 用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试 execute;如果执行程序已关闭,则会丢弃该任务。
static class ThreadPoolExecutor.DiscardPolicy <br> 用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
收藏
0 条评论
下一页