克隆与线程
克隆:
什么是克隆?
将一个对象复制一份形成一个新的个体
怎样实现克隆?
- Cloneable接口
2、重写clone方法
注意
- clone方法定义在Object类中
- 克隆会创建一个新对象-----字符串不能克隆
深拷贝/浅拷贝:
浅拷贝:
克隆一个对象,只克隆自身的内存数据
深拷贝:
克隆一个对象,也将该对象引用的对象同时克隆
原型设计模式:
线程:
实现线程的方式
方式一:
继承Thread
方式二:
实现Runnable,重写run方法再实例化一个线程运行该任务
实现Runnable更灵活;Runnable对象中的数据对其他所有线程对象是共享的
Thread启动两个线程,两个线程分别对自身属性进行操作,内存是独立的。
Runnable实例化一个Runnable对象,有两个线程去执行,两个线程都是操作Runnable对象中的内存,Runnable中的内存对t1和t2共享
线程的方法
·Threadsleep 静态
·join
·Threadyeild 静态
·run
·statr
·getName/setName
·Thread.currentThread 静态
·stop
·interropt
优先级:
设置优先级:
setPriority(int); 默认优先级 5 最大优先级 10 最小优先级 1
优先级越高获取cpu资源几率越高
守护线程:
·setDaemon(true);-----默认false
·只有所有的业务线程都结束,守护线程才结束
线程的生命周期
新建状态:
Thread t1 = new Thread(run);t1就处于新建状态
就绪状态:
·调用start方法,进入就绪状态
·yield(); 礼让cpu资源,让cpu重新分配资源(不一定能礼让出去)
作用:避免某一单独线程一直占用cpu资源,重新分配cpu资源
运行状态:
CPU分配资源给该线程,该线程就是运行状态
等待状态:
方式一:
sleep();可以让线程进入等待状态
sleep(0):等了!!!避免某一单独线程一直占用cpu资源,重新分配cpu资源
方式二:
join();可以让当前线程进入等待状态,让另一个线程加入进来,等待加入进来的线程先运行完毕
即:在B线程中调用A.join方法, A先运行,B等待A运行完毕才运行
堵塞状态:
方式一:
wait()方法来阻塞线程,使用notify()和notifyAll()方法来唤醒线程。
wait()方法将会释放当前持有的监视器锁(monitor),直到有线程调用notify/notifyAll()方法后方能继续执行。
notify/notifyAll()方法只是解除了等待线程的阻塞,并不会马上释放监视器锁,而是在相应的被synchronized关键字修饰的同步方法或同步代码块执行结束后才自动释放锁。
缺点:
1、使用几个方法时,必须处于被synchronized关键字修饰的同步方法或同步代码块中,否则程序运行时,会抛出IllegalMonitorStateException异常。
2、线程的唤醒必须在线程阻塞之后,否则,当前线程被阻塞之后,一直没有唤醒,线程将会一直等待下去(对比LockSupport)
方式二:
await()方法来阻塞线程,signal()/singnalAll()方法来唤醒线程。
需要使用lock对象的newCondition()方法获得Condition条件对象(可有多个)。
缺点:
必须被Lock包裹,否则会在运行时抛出IllegalMonitorStateException异常。
线程的唤醒必须在线程阻塞之后;Lock的实现是基于AQS,效率稍高于synchronized
方式三:
park()来阻塞线程,用unpark()方法来唤醒线程。
这里有一个许可证的概念,许可不能累积,并且最多只能有一个许可,只有1和0的区别。
特点:
使用灵活,效率高,可以直接使用,线程唤醒可在线程阻塞之前,因为调用unpark()方法后,线程已经获得了一个许可证(但也只能有一个许可证),之后阻塞时,可以直接使用这个许可证来通行。
终止状态:
线程运行完毕或终止
方式一:
stop----准备废弃---不推荐使用
方式二:
Interrupt -----只有在等待状态才会终止,终止方式是抛异常
线程安全
线程安全:
在多线程环境下,多个线程同时访问同一资源时,不会出现数据不一致或者程序崩溃的问题
线程不安全:
在多线程环境下,当多个线程同时访问同一资源时,可能会出现数据不一致或者程序崩溃的问题
同步&异步
同步:
定义:
多条线程作用于一个对象,同一时间只有一条线程进行方法处理,其余线程阻塞等待。
如何实现同步:
使用Synchronized关键字达到同步,
·确保同一时间只有一条线程进入方法运行,达到同步效果
·修饰方法:
锁对象:
·同一个锁对象管理或修饰同一个代码块或方法,同一时间这些方法只能由一条线程运行-----即一个锁对象同一时间只能放行一条线程,线程是否同步与方法对象是否是同一个没关系,只需要是一个锁就能达到。
·成员方法(非static)锁对象就是this
·静态的方法 锁对象就是该类的类对象
·修饰代码块:
this关键字的作用代表当前对象,如果this代表不同对象,就达不到同步效果
锁
分类:
悲观锁:
在业务代码中,总以为有别的线程会来修改当前线程正在处理的数据,添加锁对象进行同步处理。
有锁对象就是悲观锁
synchronized 是悲观锁
乐观锁:
在业务代码中,乐观地以为当前线程处理数据时,没有其他线程来干扰,不加锁对象,只是通过验证来实现线程安全
两种实现方式:
比较版本号:
CAS—比较并替换
偏向锁:
两个同步代码块锁对象相同,A线程进入第一个同步代码块时,已经获取锁对象的权限,在第二个同步代码块中就不需要再获取锁权限,这时候改锁对象对于该线程就是一个偏向锁。
根据重量级分类:synchronized锁对象转变的过程
轻量级锁/自旋锁:
当一个线程要进入A锁对象的同步代码块,发现已经有线程获取锁对象权限了,该线程就会自旋尝试获取锁对象权限,这时A对象对于该线程就是轻量级锁,也叫自旋锁(上限自旋15次)。
重量级锁:
如果一个线程自旋获取锁对象的权限,一直没获取到,该线程就会进入等待状态,这时,该锁对象对于该线程就是重量级锁。
根据重入性分:
可重入锁:----Java中都是可重入锁
允许线程多次获取使用同一把锁的机制,即可以递归调用定义好的锁,外层使用过后,内层可以继续使用不会引起死锁
非可重入锁:
一把锁不能被线程多次获取使用的机制,即有当一个线程再次获取它拥有或使用过的锁时它将无法再次获取
根据公平性分:
公平锁:
先来后到
非公平锁:-------synchronized
随机分配
锁对象的方法:
·wait:让线程等待
·notify/notifyAll()方法:唤醒线程/所有线程
问:sleep与wait有什么区别?
答:
sleep是Thread的静态方法 waite是定义在Object中锁定对象调用的方法
sleep等待时间后自醒 wate等待后需要使用notify/notifyAll()方法唤醒
sleep保持锁状态 wate会放弃锁,其他线程可以进来运行
单例模式
线程池
线程池是一种多线程处理形式,它包含一群等待执行的任务的线程,每个线程都使用其自有的堆栈。Java通过Executors工厂类提供了四种线程池,包括:
1. newCachedThreadPool:创建一个可缓存的无界线程池,如果线程池长度超过处理需要,可以灵活回收空线程,若无可回收,则新建线程。
2. newFixedThreadPool(int n):创建一个固定大小的线程池,以完成指定数量的执行任务。
3. newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。
4. newScheduledThreadPool(int corePoolSize):创建一个定时调度线程池,它可以在给定延迟后运行命令,或者定期执行命令。
采用线程池来管理线程有以下好处:提升性能,因为线程池能独立负责线程的创建、维护和分配。频繁地创建和销毁线程会大大降低系统的运行效率,使用线程池可以有效地控制线程的最大并发数,避免过多资源竞争。