多线程相关知识

一,进程和线程

进程和线程的概念

并行和并发的概念

线程基本应用

进程:当一个程序被运行,就相当于开启了一个进程。

线程:一个进程内可以分到1到多个线程。Java中线程是最小的调度单位。

并发:同一时间应对多件事情的能力

并行:同一时间动手做多件事情的能力

Java线程

创建和运行线程

查看线程

线程API

线程状态

方法1,直接使用thread

Thread t1 = new Thread(){
   @Override
   public void run() {
      log.info("doing...");
   }
};
t1.start();

方法2,使用Runnable配合thread

thread代表线程

Runnable代表可运行的任务

Runnable running = new Runnable() {
    @Override
    public void run() {
       log.info("running");
    }
};
Thread t2 = new Thread(running);
t2.start();

java8后可用lamda表达式

new Thread(
()->{log.info("running");
}).start();

方法3,FutureTask配合Thread

FutureTask<Integer> futureTask = new FutureTask<Integer>(() -> {
log.info("running...");
return 10;
});
new Thread(futureTask, "t1").start();
int result = futureTask.get();
log.info("the result is {}", result);

线程上下文切换

因为以下一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码

线程的CPU时间片用完

垃圾回收

有更高优先级的线程要执行

线程自己调用了sleep,yield,wait,join,park,synchronized,lock等方法。

线程常见方法

start(),启动一个线程

run(),线程启动后调用该方法

join(), 等待某个线程结束

getId(),获取编号

getName(),获取线程名

setName(),设置线程名

getState(),获取线程状态

isIntersrupted(),判断是否被打断

isAlive(),线程是否存活

interrupt(),打断线程

currentThread(),获取当前执行的线程

sleep(long n),让当前执行的线程休眠n毫秒

yield(),提示线程调度器让出当前线程对CPU的使用

详细介绍:

start(), 启动一个线程需要调用start()方法,调用run方法只是在主线程启动run方法中的代码;

sleep(),调用该方法,会让线程从running进入time waiting状态,其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException异常。

睡眠后的线程未必会立即执行

yield(),调用该方法会让当前线程从running进入runnable就绪状态,然后执行其他线程

一,进程和线程

进程和线程的概念

并行和并发的概念

线程基本应用

进程:当一个程序被运行,就相当于开启了一个进程。

线程:一个进程内可以分到1到多个线程。Java中线程是最小的调度单位。

并发:同一时间应对多件事情的能力

并行:同一时间动手做多件事情的能力

Java线程

创建和运行线程

查看线程

线程API

线程状态

方法1,直接使用thread

Thread t1 = new Thread(){
   @Override
   public void run() {
      log.info("doing...");
   }
};
t1.start();

方法2,使用Runnable配合thread

thread代表线程

Runnable代表可运行的任务

Runnable running = new Runnable() {
    @Override
    public void run() {
       log.info("running");
    }
};
Thread t2 = new Thread(running);
t2.start();

java8后可用lamda表达式

new Thread(
()->{log.info("running");
}).start();

方法3,FutureTask配合Thread

FutureTask<Integer> futureTask = new FutureTask<Integer>(() -> {
log.info("running...");
return 10;
});
new Thread(futureTask, "t1").start();
int result = futureTask.get();
log.info("the result is {}", result);

线程上下文切换

因为以下一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码

线程的CPU时间片用完

垃圾回收

有更高优先级的线程要执行

线程自己调用了sleep,yield,wait,join,park,synchronized,lock等方法。

线程常见方法

start(),启动一个线程

run(),线程启动后调用该方法

join(), 等待某个线程结束

getId(),获取编号

getName(),获取线程名

setName(),设置线程名

getState(),获取线程状态

isIntersrupted(),判断是否被打断

isAlive(),线程是否存活

interrupt(),打断线程

currentThread(),获取当前执行的线程

sleep(long n),让当前执行的线程休眠n毫秒

yield(),提示线程调度器让出当前线程对CPU的使用

详细介绍:

start(), 启动一个线程需要调用start()方法,调用run方法只是在主线程启动run方法中的代码;

sleep(),调用该方法,会让线程从running进入time waiting状态,其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException异常。

睡眠后的线程未必会立即执行

yield(),调用该方法会让当前线程从running进入runnable就绪状态,然后执行其他线程

setpriority()线程优先级,从1到10,10优先级最高;

线程优先级会提示调度器优先调用该线程,但他仅仅是一个提示,调度器可以忽略。

sleep防止CPU占用100%;

 while (true) {
       try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
}

避免空转浪费CPU。

join()方法

线程调用,等待当前调用线程执行结束。

join(long n)

最多等待n豪秒

interrupt方法详解

1.打断sleep,wait,join的线程,会清空打断标记。

2.打断正常的线程,打断标记是true

两阶段终止模式:在一个线程怎样停止另一个线程

复制代码
public class Test3 {
    public static void main(String[] args) {
        TwoPhaseTermination abc = new TwoPhaseTermination();
        abc.start();
        try {
            Thread.sleep(3500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        abc.stop();
    }

}

@Slf4j
class TwoPhaseTermination {
    private Thread monitor;

    public void start() {
        monitor = new Thread(() -> {
            while (true) {
                Thread current = Thread.currentThread();
                if (current.isInterrupted()) {
                    log.info("料理后事");
                    break;
                }
                try {
                    Thread.sleep(1000);//情况1
                    log.info("执行监控记录");//情况2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    current.interrupt();
                }
            }
        });
        monitor.start();
    }
    public void stop() {
        monitor.interrupt();
    }
}
复制代码

不推荐方法

stop()停止线程,suspend()暂停线程,resume()恢复线程 

主线程和守护线程

垃圾回收器就是守护线程,设置为守护线程monitor.setDaemon(true);

线程状态

初始状态,可运行状态,运行状态,阻塞状态,终止状态。

new - runnable - blocked - waiting - timed_waiting - terminated

共享模型之管程

多个线程访问共享资源

多个线程对共享资源读写操作时发生指令交错,就会出现问题。

一段代码内如果存在对共享资源的多线程读写操作,称这段代码块为临界区。

为了避免临界区的竞争条件发生,有多种手段可以达到目的。

阻塞式的解决方案

synchronized,Lock

非阻塞式的解决方案

原子变量

synchronized(对象){

}

synchronized加在方法上:

1.加在成员方法上

2.加在静态方法上

 

 

 成员变量和静态变量是否线程安全

如果他们没有共享,那么线程安全

如果他们被共享了根据他们是否能被改变又分为两种情况

只有读操作,线程安全

如果有读写操作,则这块代码是临界区,需要考虑线程安全

局部变量是否线程安全

局部变量是线程安全的

常见线程安全类

String,Integer,StringBuffer,Random,Vector,HashTable,java.util.concurrent包下的类。

他们的每个方法都是原子的。

经典卖票问题

Monitor监视器

轻量级锁使用场景:如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。语法仍然是synchronized

锁膨胀:如果在尝试加轻量级锁的过程中,cas操作无法成功,这时一种情况就是有其他线程为此对象加了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

自旋优化:重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。

在Java6之后自旋锁是自适应的,自旋会占用CPU时间,单核CPU自旋就是浪费,多核CPU自旋才能发挥优势。

Java7之后不能控制是否开启自旋功能。

偏向锁:轻量级锁在没有竞争时(就自己这个线程),每次冲入仍然需要执行cas操作。

Java6中引入了偏向锁来进一步优化:只有第一次使用cas将线程ID设置到对象的Mark word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新cas,以后只要不发生竞争,这个对象就归该线程所有。

一个对象创建时

如果开启了偏向锁(默认开启),那么对象创建后Mark word最后三位是101,这时它的thread,epoch,age都为0

偏向锁默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加vm参数来禁用延迟

如果没有开启偏向锁,那么对象创建后Mark word的最后三位是001,这时它的hashcode,age都为0,第一次用到hashcode时才会赋值。

偏向锁调用hashcode方法时会撤销偏向锁,

当另一线程竞争锁时,偏向锁升级为轻量级锁

批量重偏向

锁消除

wait,notify

wait()方法

notify()方法

notifyAll()方法

都属于object方法,必须获得此对象的锁才能调用这几个方法。

sleep(long n)与wait(long n)的区别

1.sleep是thread的方法,wait是object方法

2.sleep不需要强制和synchronized一起使用,但wait需要

3.sleep在睡眠的同时不会释放锁,但wait在等待的时候会释放锁。

synchronized(lock){

while(条件不成立){

lock.wait();

}

//干活

}

//另一个线程

synchronized(lock){

lock.notifyAll();
}

同步模式之保护性暂停

 

 

 异步模式之生产者消费者

 

 

 

1.new-->runnable

当调用t.start(),由new-->runnable

2.runnable->waiting

t线程用synchronized(obj)获取了对象锁后  

调用了obj.wait()方法时,runnable-》waiting

调用obj.notify(),obj.notifyAll(),t.interrupt()时

竞争锁成功waiting-〉runnable

竞争锁失败waiting-》blocked

3.runnable<->waiting

当调用t.join()方法时,当前线程runnable->waiting

t线程运行结束,或调用了当前线程的interrupt方法时,当前线程waiting->runnable

4.runnable<-->waiting

当前线程调用LockSupport.park()方法时,会让当前线程从runnable-》waiting

调用unpack()方法时,或调用了线程的interrupt方法,waiting--》runnable

5.runnable《-》time_waiting

t线程用synchronized(obj)获取了对象锁后  

调用wait(long n),runnable-〉time_waiting

当前线程等待时间超过了n秒,或调用obj.notify(),obj.notifyAll(),t.interrupt()方法时,

竞争锁成功time_waiting---》runnable

竞争锁失败time_waiting---〉blocked

6.runnable《-》time_waiting

调用wait(long n),runnable-〉time_waiting

当前线程等待时间超过了n秒,或当t运行结束,或调用了当前线程interrupt方法,time_waiting-》runnable

7.runnable《-》time_waiting

调用thread.sleep(long n),time_waiting---》runnable

等待时间超过n毫秒,runnable-〉time_waiting

8.

9.runnable〈- 〉blocked

t线程用synchronized(obj),获取对象锁如果竞争失败,runnable ---〉blocked

持锁的线程执行完后会唤醒所有blocked的线程重新竞争,如果t线程竞争成功,blocked---》runnable,其他失败的线程仍然blocked

10.runnable-〉terminated

当前线程所有代码执行完毕,进去terminated

活跃性

死锁

t1线程已经获得A对象锁,接下来想获得B对象锁

t2线程已经获得B对象锁,接下来想获得A对象锁

复制代码
package com.example.mall2.ticket;

import lombok.extern.slf4j.Slf4j;

/**
 * Author: zhangbicheng
 * Date: 2022/5/7
 */
@Slf4j
public class DeadLock {
    public static void main(String[] args) {
        Object a = new Object();
        Object b = new Object();

        new Thread(() -> {
            synchronized (a) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    log.info("do something...");
                }
            }
        }, "t1").start();
        new Thread(() -> {
            synchronized (b) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a) {
                    log.info("do something...");
                }
            }
        }, "t2").start();

    }
}
复制代码

哲学家就餐问题

活锁

活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束。

解决:执行顺序交错开

饥饿

得不到机会执行

reentrantLock

相对于synchronized,具备如下特点

可中断

可以设置超时时间

可以设置为公平锁,一般没有必要。

支持多个条件变量

与synchronized一样,支持可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁,如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。

基本语法

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
        //临界区
} finally {
        //释放锁
        lock.unlock();
}    

可见性和有序性

volatile

可用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取变量的值,线程操作volatile变量都是直接操作主存。

 

posted @   张碧晨  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示