多线程
1.进程与线程
进程:操作系统中一个程序的执行周期称为一个进程。
线程:一个程序同时执行多个任务。通常,每一个任务就称为一个线程。与进程相比较,线程更"轻量级",创建、撤销一个线程比启动一个新进程开销要小的多。没有进程就没有线程,进程一旦终止,其内的线程也将不复在。
进程和线程的区别:本质区别在于,每个线程拥有自己的一整套变量,而线程则共享数据。共享变量使得线程之间的通信 更有效、更方便。
2.多线程实现
无论使用哪种方式创建线程,启动线程一律使用Thread类提供的start方法,run方法不能通过用户直接调用。
一个线程的start()只能被执行一次,执行多次会抛出IllegalThreadStateException。
2.1继承Thread类,覆写run()
java.lang.Thread是一个线程操作的核心类。新建一个线程最简单的方法就是直接继承Thread类,而后覆写该类中的run()方法。
class MyThread extends Thread{
private String title;
public MyThread(String title){
this.title = title;
}
public void run(){
for(int i = 0;i<5;i++){
System.out.println(this.title+",i="+i);
}
}
}
public class threadTest {
public static void main(String[] args) {
MyThread mt1 = new MyThread("A");
MyThread mt2 = new MyThread("B");
MyThread mt3 = new MyThread("C");
mt1.start();
mt2.start();
mt3.start();
}
}
2.2实现Runnable接口,覆写run()
Thread类的核心功能是进行线程的启动。如果一个类为了实现多线程直接去继承Thread类就会有单继承局限。在Java中又提供有另外一种实现模式:Runnable接口。
Java中Thread类本身也实现了Runnable接口,与用户自定义的线程类共同组成代理设计模式,其中Thread类实现辅助操作,包括线程的资源调度等任务,自定义线程类完成真实业务。
class MyThread implements Runnable{
private String title;
public MyThread(String title){
this.title = title;
}
public void run(){
for(int i = 0;i<5;i++){
System.out.println(this.title+",i="+i);
}
}
}
public class threadTest {
public static void main(String[] args) {
MyThread mt1 = new MyThread("A");
MyThread mt2 = new MyThread("B");
MyThread mt3 = new MyThread("C");
//Thread类提供的构造方法:
//pubilc Thread(Runnable target)
new Thread(mt1).start();
new Thread(mt2).start();
new Thread(mt3).start();
}
}
Runnable接口对象可以采用匿名内部类或者Lambda表达式来定义
使用匿名内部类进行Runnable对象创建:
public class threadTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类实现多线程");
}
}).start();
}
}
使用Lambda表达式进行Runnable对象创建
public class threadTest {
public static void main(String[] args) {
Runnable runnable = ()-> System.out.println("Lambda表达式实现多线程");
new Thread(runnable).start();
}
}
2.3继承Thread类与实现Runnable接口区别
1.继承Thread类有单继承局限,相对而言实现Runnable接口更加灵活,并且Thread类本身也实现了Runnable接口。
2.实现Runnable接口可以更好的实现程序共享的概念(继承Thread类也可以,较为复杂)
2.4实现Callable接口,覆写call()
(JDK1.5——java.util.concurrent)
Runnable中的run()方法没有返回值,它的设计也遵循了主方法的设计原则:线程开始了就别回头。但是很多时候需要一些返回值,例如某些线程执行完成后可能带来一些返回结果,这种情况下就只能利用Callable来实现多线程。
class MyThread implements Callable<String>{
private int ticket = 10;
@Override
public String call() throws Exception {
while (this.ticket>0){
System.out.println("票还有"+ticket--+"张");
}
return "票卖完了";
}
}
public class threadTest {
public static void main(String[] args) throws Exception {
MyThread myThread = new MyThread();//产生Callable对象
FutureTask<String> futureTask = new FutureTask<>(myThread);//产生FutureTask对象
new Thread(futureTask).start();//利用Thread类的构造方法调用start方法
System.out.println(myThread.call());
}
}
3.Java多线程常用方法
3.1取得当前JVM中正在执行的线程对象
public static native Thread currentThread();
3.2线程名称的命名与取得
3.2.1命名
1.public Thread(Runnable target,String name);
2.public final synchronized void setName(String name);
3.2.2取得名称
public final String getName();
3.3线程休眠sleep(long time)单位为毫秒
运行态——>阻塞态
指的是让线程暂缓执行一下,等到了预计时间之后再恢复执行。sleep方法会让当前线程立即交出cpu,但不会释放对象锁。
处理休眠操作
class MyThread implements Runnable{
@Override
public void run() {
for(int i = 0;i<10;i++){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程为:"+Thread.currentThread().getName()+",i="+i);
}
}
}
public class threadTest {
public static void main(String[] args) throws Exception {
MyThread myThread = new MyThread();
new Thread(myThread).start();
new Thread(myThread).start();
new Thread(myThread).start();
}
}
3.4线程让步yield()
运行态——>就绪态
暂停当前正在执行的线程对象,并执行其他线程(只能让拥有相同优先级的线程获取cpu),当前线程不会立即交出cpu,交出时间由系统调度。yield方法同样不会释放对象锁。
3.5线程等待join()
运行态——>阻塞态
若线程1需要等待线程2执行完毕后再恢复执行,可以在线程1中调用线程2的join方法
class MyThread implements Runnable{
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class threadTest {
public static void main(String[] args) throws Exception {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"线程A");
thread.start();
System.out.println(Thread.currentThread().getName());
thread.join();
System.out.println(Thread.currentThread().getName());
System.out.println("代码结束");
}
}
3.6线程停止
3.6.1设置标志位
class MyThread implements Runnable{
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
int i = 1;
while(flag){
try {
Thread.sleep(1000);
System.out.println("第"+i+"次执行,线程为:"+Thread.currentThread().getName());
i++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class threadTest {
public static void main(String[] args) throws Exception {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"线程A");
thread.start();
Thread.sleep(5000);
myThread.setFlag(false);
System.out.println("over~");
}
}
3.6.2调用Thread类提供的stop方法强行关闭线程(不推荐)
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(i + "....");
}
}
}
public class threadTest {
public static void main(String[] args) throws Exception {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"线程A");
thread.start();
Thread.sleep(10);
thread.stop();
System.out.println("over~");
}
}
stop会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,假如一个线程正在执行:
synchronized void { x = 3; y = 4;}
由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也会马上stop了,这样就产生了不完整的残废数据。
3.6.3调用Thread类提供的interrupt()
(1)若线程中没有类似sleep、wait、join方法时,调用此线程对象的interrupt方法并不会真正中断线程,只是简单地将线程的状态置为interrupt而已,我们可以根据此状态来进一步确定如何处理线程。Thread类提供的isInterrupt()可以检测当前线程状态是否为中断状态。
(2)若线程中调用了阻塞线程的方法:sleep、wait、join方法时,此时再调用线程的interrupt方法会抛出异常InterruptException,同时将线程的状态还原(isInterrupt = false)
class MyThread implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 1;
while (flag){
try {
Thread.sleep(1000);
boolean bool = Thread.currentThread().isInterrupted();
if(bool){
System.out.println("非阻塞情况下执行此操作,状态为"+bool);
break;
}
System.out.println("第"+i+"次执行,线程名称为"+Thread.currentThread().getName());
i++;
} catch (InterruptedException e) {
System.out.println("退出了");
boolean bool = Thread.currentThread().isInterrupted();
System.out.println(bool);
return;
}
}
}
}
public class threadTest {
public static void main(String[] args) throws Exception {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"线程A");
thread.start();
Thread.sleep(5000);
thread.interrupt();
System.out.println("over~");
}
}
3.7线程优先级
线程的优先级指的是优先级越高越有可能先执行而已。
设置优先级:setPriority(int priority)
取得优先级:int getPriority()
JDK内置了三种优先级:
MAX_PRIORITY = 10
NORM_PRIORITY = 5
MIN_PRIORITY = 1
线程的继承性:若在一个线程中继承了子线程,默认子线程与父线程优先级相同。
3.8守护线程
java中线程分为两类:守护线程和用户线程。
守护线程为陪伴线程,只要JVM中存在有任何一个用户线程没有终止,守护线程就一直在工作。默认创建的线程都是用户线程,包括主线程。
通过setDaemon(true)将线程对象设置为守护线程。
class MyThread implements Runnable {
private int i;
@Override
public void run() {
while(true){
i++;
System.out.println("当前线程为:"+Thread.currentThread().getName()+",i="+i+
"是否为守护线程:"+Thread.currentThread().isDaemon());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程名称"+Thread.currentThread().getName()+",线程中断了");
}
}
}
}
public class threadTest {
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(new MyThread(),"线程A");
thread1.setDaemon(true);
thread1.start();
Thread thread2 = new Thread(new MyThread(),"线程B");
thread2.start();
thread2.sleep(3000);
thread2.interrupt();
thread2.sleep(5000);
System.out.println("OVER....");
}
}
4.线程同步
4.1同步处理
所谓的同步指的是所有的线程不是一起进入到方法中执行,而是按照顺序一个一个进来。
4.2synchronized处理同步问题
4.2.1.同步代码块
如果要使用同步代码块必须设置一个要锁定的对象,所以一般可以锁定当前对象:this
synchronized(锁的对象){//此时代码块在任意一个时刻只能有一个线程进入}
class MySyncThread implements Runnable {
private int ticket = 200;
@Override
public void run() {
for (int i = 0; i < 200; i++) {
synchronized (this) {
if (this.ticket > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "还剩下" + ticket-- + "张票");
}
}
}
}
}
public class Test7_21 {
public static void main(String[] args) {
MySyncThread mySyncThread = new MySyncThread();
new Thread(mySyncThread).start();
new Thread(mySyncThread).start();
new Thread(mySyncThread).start();
}
}
4.2.2同步方法
直接在方法声明上使用synchronized,此时表示同步方法在任意时刻都只能有一个线程进入。
class MySyncThread implements Runnable {
private int ticket = 200;
@Override
public void run() {
for (int i = 0; i < 200; i++) {
this.sale();
}
}
public synchronized void sale(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"还剩下"+ticket--+"张票");
}
}
public class Test7_21 {
public static void main(String[] args) {
MySyncThread mySyncThread = new MySyncThread();
new Thread(mySyncThread).start();
new Thread(mySyncThread).start();
new Thread(mySyncThread).start();
}
}
注意
若synchronized修饰的是静态方法或synchronized(类.class),synchronized锁的都是当前类的反射对象(全局唯一)。
4.2.3synchronized底层实现
1.synchronized修饰代码块:
在使用Synchronized时必须保证锁定对象为Object以及其子类对象,synchronized使用的是JVM层级别的monitorenter与monitorexit实现,这两个指令都必须获取对象的同步监视器Monitor。一个monitorenter指令对应多个monitorexit指令,这是因为Java虚拟机需要确保所获得的锁在正常执行路径以及异常执行路径下都能够被解锁。
2.synchronized修饰同步方法
字节码中方法的访问标记包括ACC_SYNCHRONIZED,该标记表示在进入该方法时,java虚拟机需要进行moniterenter操作,而在退出该方法时,不管是正常返回,还是向调用者抛异常,java虚拟机均需要进行monitorexit操作。
对象锁机制
monitorenter
检查该对象的monitor计数器值是否为0,为0表示此监视器还未被任何一个线程获取,此时线程可以进入同步代码块并且将monitor值+1,将monitor的持有线程标记为当前线程。
当monitor计数器值不为0且持有线程不是当前线程,表示monitor已经被别的线程占用,当前线程只能阻塞等待。
monitorexit
monitor计数器值-1
可重入锁
当执行monitorenter时,对象的monitor计数器值不为0,但是持有线程恰好是当前线程,此时将monitor计数器值再次+1,当前线程继续进入同步方法或代码块。
4.2.4synchronized优化
优化让每个线程通过同步代码块时速度提高。
(1)CAS操作——无锁实现的同步–乐观锁–自旋
CompareAndSwap(O、V、N)
O:当前线程存储的变量值
V:内存中该变量的具体值
N:希望修改后的变量值
当O==V时,此时表示还没有线程修改共享变量的值,此时可以成功的将内存中的值修改为N;当O!=V时,表示此时内存中的共享变量已经被其他线程修改,此时返回内存中的最新值V,再次尝试修改变量。
问题:
ABA问题: 因为CAS会检查旧值有没有变化,这里存在这样一个有意思的问题。比如一个旧值A变为了成B,然后 再变成A,刚好在做CAS时检查发现旧值并没有变化依然为A,但是实际上的确发生了变化。解决方案可以沿袭数 据库中常用的乐观锁方式,添加一个版本号可以解决。
自旋会浪费大量的处理器资源: 与线程阻塞相比,自旋会浪费大量的处理器资源。这是因为当前线程仍处于运行状况,只不过跑的是无用指令。它 期望在运行无用指令的过程中,锁能够被释放出来。解决方案为自适应自旋(根据以往自旋等待时能否获取锁,来动态调整自旋的时间)。
公平性: 处于阻塞态线程可能会一直无法获取到锁。Lock锁实现公平性。
(2)偏向锁
最乐观的锁:进入同步块或同步方法的始终是一个线程。
当出现另一线程也尝试获取锁(在不同时刻),偏向锁会升级为轻量级锁
(3)轻量级锁&重量级锁
轻量级锁:不同时刻有不同的线程尝试获取锁,“亮黄灯策略”,同一时刻有不同线程尝试获取锁,会将偏向锁升级为重量级锁。
重量级锁:JDK1.6之前的synchronized都是重量级锁,将线程阻塞挂起。
锁只有升级没有降级。
(4)锁粗化
当出现多次连续的加锁与解锁过程,会将多次加减锁过程粗化为一次的加锁与解锁过程。
(5)锁消除
当对象不属于共享资源时,对象内部的同步方法或同步代码块的锁会被自动解除。
4.2.5死锁
同步的本质在于:一个线程等待另外一个线程执行完毕后才可以继续执行,但是如果现在相关的几个线程彼此之间都在等待着,那么就会造成死锁。
产生死锁的条件(以下四种条件同时满足才会导致死锁):
1.互斥:共享资源只能被一个线程占用;
2.占有且等待:拿到Worker锁不释放的同时去申请money锁;
3.不可抢占:线程Thread拿到对象锁X后,其他线程无法强行抢占X锁;
4.循环等待:线程1拿到了资源X的锁,去申请Y的锁
线程2拿到了资源Y的锁,去申请X的锁
class Pen{
private String pen = "笔";
public String getPen() {
return pen;
}
}
class Book{
private String book = "书";
public String getBook(){
return book;
}
}
public class DeadLock {
private static Pen pen = new Pen();
private static Book book = new Book();
public static void main(String[] args) {
new DeadLock().deadLock();
}
public void deadLock(){
Thread thread1 = new Thread(new Runnable() {//笔线程
@Override
public void run() {
synchronized (pen){
System.out.println(Thread.currentThread().getName()+"我有笔");
}
synchronized (book){
System.out.println(Thread.currentThread().getName()+"把你的本给我");
}
}
},"pen");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (book){
System.out.println(Thread.currentThread().getName()+"我有本");
}
synchronized (pen){
System.out.println(Thread.currentThread().getName()+"把你的笔给我");
}
}
},"book");
thread1.start();
thread2.start();
}
}
4.3ThreadLocal——线程本地变量
ThreadLocal用于提供线程局部变量,在多线程环境下可以保证各个线程里的变量独立于其他线程里的变量。也就是说ThreadLocal可以为每个线程创建一个单独的变量副本,相当于线程的private static类型变量。每个ThreadLocal对象内部都有一个ThreadLocal.ThreadLocalMap对象(存放元素),该对象属于每个线程自己,因此其保存的内容也是线程私有,在多线程场景下并不共享。
get():取得线程私有属性值;
set():设置线程私有属性值。
5.lock体系
产生背景:synchronized死锁
破坏死锁的不可抢占:
1.响应中断:void lockInterruptibly();
2.支持超时:boolean tryLock(long time ,TimeUnit unit);
3.非阻塞式获取锁(线程获取不到锁,直接退出):boolean tryLock();
Lock体系使用的格式:
try{
//加锁
lock.lock();
}finally{
//解锁
lock.unlock();
}
独占锁(synchronized):在任意一个时刻,只有一个线程可以拥有此锁。
共享锁:同一时刻,可以有多个线程拥有锁。
面试题:
java中实现“锁”的方式:synchronized与Lock
二者关系:
1.synchronized与lock都是对象锁,都支持可重入锁;
2.lock可以实现synchronized不具备的特性,如响应中断、支持超时、非阻塞的获取锁、公平锁、共享锁(读写锁)。
3.Lock体系的Condition队列可以有多个,区分于synchronized只有一个等待队列,lock.newCondition():产生一个新的Condition队列。
ReentrantReadWriteLock
读写锁——共享锁的一种实现
读锁共享:多个线程可以同时拿到读锁进行访问。
写锁独占:当写线程拿到写锁开始工作时,所有读线程全部阻塞。
Condition:Lock体系的线程通信方式,类比Object:wait()和notify()
——await():释放Lock锁,将线程置于等待队列阻塞
——signal():随机唤醒一个处于等待状态的线程
——signalAll():唤醒所有的等待线程
线程池——推荐使用线程池来创建线程
优点:
1.降低资源消耗:重复利用已创建的线程降低线程创建和销毁带来的消耗;
2.提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行;
3.提高线程可管理性:使用线程池可以统一进行线程分配、调度和监控。
两大核心接口
1.ExecuteSevice:普通线程池
-void execute(Runnable command);
- Future submit(Callable task||Runnable);
2.ScheduledExecuteSevice:定时线程池
一个核心类
ThreadPoolExecutor——ExecutorSevice的子类
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit ,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
线程池工作流程:
1.当一个任务交给线程池时,首先判断核心池的线程数量是否达到corePoolSize,若未达到,线程池创建新的线程执行任务并将其置入核心池中,否则,判断核心池是否有空闲线程,若有,分配任务执行,没有进入步骤2.
2.判断当前线程池中线程数量有没有达到线程池的最大数量(maximumPoolSize),若没有,创建新的线程执行任务并将其置入线程池,否则进入步骤3.
3.判断阻塞队列是否已满,若未满,将任务置入阻塞队列中等待调度,否则进入步骤4.
4.调用相应的拒绝策略打回任务[有四种拒绝策略,默认为抛出异常给用户A]bortPolicy]
6.AQS
6.1AQS的模板方法设计模式
1.同步组件的实现依赖于同步器AQS,在同步组件实现中,使用AQS的方式被推荐定义继承AQS的静态内存类;
2.AQS采用模板方法进行设计,AQS的protected修饰的方法需要由继承AQS的子类进行重写实现,当调用AQS的子类的方法时就会调用被重写的方法;
3.AQS负责同步状态的管理,线程的排队,等待和唤醒这些底层操作,而Lock等同步组件主要专注于实现同步语义;
4.在重写AQS的方式时,使用AQS提供的getState(),setState(),compareAndSetState()方法进行修改同步状态。
class Mutex implements Lock{
private Sync sync = new Sync();
static class Sync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
if(arg != 1){
throw new RuntimeException("信号量不为1");
}
if (compareAndSetState(0,1)){
//当前线程成功获取锁
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if(getState() == 0){
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState()==1;
}
Condition newCondition(){
return new ConditionObject();
}
}
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
sync.release(1)
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
public class AQS {
private static Mutex mutex = new Mutex();
public static void main(String[] args) {
for (int i = 0;i<10;i++){
Thread thread = new Thread(()->{
mutex.lock();
try {
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}finally {
mutex.unlock();
}
});
thread.start();
}
}
}
独占式锁:
void acquire(int arg):独占式获取同步状态,如果获取失败则插入同步队列进行等待。
void acquireInterruptibly(int arg):与acquire方法相同,但在同步队列中等待时可以响应中断。
boolean tryAcquireNanos(int arg,long nanosTimeout) : 在4的基础上增加了超时等待功能,在超时时间内没有获 得同步状态返回false
boolean tryAcquire(int arg) : 获取锁成功返回true,否则返回false
boolean release(int arg) : 释放同步状态,该方法会唤醒在同步队列中的下一个节点。
共享式锁
- void acquireShared(int arg) : 共享式获取同步状态,与独占锁的区别在于同一时刻有多个线程获取同步状态。
- void acquireSharedInterruptibly(int arg) : 增加了响应中断的功能
- boolean tryAcquireSharedNanos(int arg,long nanosTimeout) : 增加了超时等待功能
- boolean releaseShared(int arg) : 共享锁释放同步状态。