多线程基础知识
所谓教条,就是生搬硬套,以开放的心态接受新概念,理解上下文,注重实践和经验。
Java的并发模型是传统的基于内存共享的并发模型
如何查看线程的状态(Jstack)
线程的生命周期
将线程比喻成人,是一种很好的理解方式,比喻成人的生老病死,人与人之间的协作,等等。
4. 线程安全问题 (原子性 可见性 有序性)
原子性 实例
public class Demo extends Thread {
private static int num=100000;
@Override
public void run() {
addNumber();
}
// 如果不加synchronized 由于cpu的时间片的切换,有的减法操作丢失了他的执行权力,再次执行时数据产生偏差
[synchronized] static public void addNumber() {
for (int i1 = 0; i1 < 5000; i1++) {
num--;
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[20];
for (int i = 0; i < threads.length; i++) {
new Thread(new Demo()).start();
}
System.out.println( "结果:" + num);
}
}
可见性 实例
public class Demo {
// volatile 使flag 可以被其他线程可见
static volatile boolean flag =true;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
int i =0;
while (flag) {
i++;
}
}
).start();
Thread.sleep(1000); //主线程不成睡,会直接执行结束,不会显示答案
flag=false;
}
有序性 实例
CPU 的编译,指令优化会破坏语句的执行顺序
synchronized 对象锁用法
-
同步方法
-
同步静态方法
synchronized static (this) {//对象 }
-
同步方法块
synchronized (this) { }
synchronized 对象锁实例
public class Demo3 implements Runnable{
static int num;
synchronized static void add() { // 若果不是静态的方法,每一个对象一个方法,就不是争用资源,锁不住。
for (int i = 0; i < 20000; i++) {
num++;
}
}
@Override
public void run() {
add();
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Demo3());
thread.start();
}
Thread.sleep(2000);
System.out.println(num);
}
}
线程的基本操作 (线程通信)
线程的三种创建方式- Callable 接口实现
public class CallableDemo implements Callable {
@Override
public Object call() throws Exception {
return null;
}
@Override
public void run() {
System.out.println("runnable 实现的线程");
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(2);
Callable callable = new CallableDemo();
Future<String> t1 = executor.submit(callable);
String s = t1.get();
System.out.println("开始消费");
}
}
线程的三种创建方式- 继承Thread 类
public class Threaddemo extends Thread {
@Override
public void run() {
System.out.println("hello");
}
public static void main(String[] args) {
new Threaddemo().start();
}
}
线程的三种创建方式- 实现runnable 接口
public class CallableDemo implements Runnable {
@Override
public void run() {
System.out.println("runnable 实现的线程");
}
public static void main(String[] args) {
Thread thread = new Thread(new CallableDemo());
thread.start();
}
}
join阻塞主线程实例
public class Demo3 implements Runnable{
static int num;
@Override
public void run() {
for (int i = 0; i < 20000; i++) {
num++;
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Demo3());
thread.start();
// thread.join(); //阻塞主线程,线程卓个执行 //阻塞主线程,卓个创建子线程执行,减少并发
}
Thread.sleep(2000);
System.out.println(num);
}
}
1. join 阻塞的是主线程
2. 因为多线程的执行是异步的,如果写代码时候,如果不阻塞主线程,有时候数据操作的值输出并不完整
wait notify 实例
main
public class Demo {
public static void main(String[] args) throws InterruptedException {
Queue<Integer> queue = new LinkedList<>();
new Thread(new Puroduce(queue,100)).start();
new Thread(new Consumer(queue,100)).start();
Thread.sleep(1000);
// 生产者一直生产, 直到到达100个,停止生产,阻塞 ---》 通知消费者消费
// 消费者一直消费,没有可以消费的, 就停止消费, 阻塞;---》 通知生产者生产
}
}
join 实例
interrupter 实例
yeild 实例
生产者
public class Puroduce implements Runnable {
Queue<Integer> queue;
private int size;
public Puroduce(Queue<Integer> queue, int size) {
this.queue = queue;
this.size = size;
}
private void produce() throws Exception {
int i = 0;
while (true) {
i++;
synchronized (queue) {
while (queue.size() == size) {
System.out.println("已经满了");
queue.wait();
}
queue.add(i);
System.out.println("生产者开始生产: " + i);
Thread.sleep(100);
queue.notifyAll();
}
}
}
@Override
public void run() {
try {
produce();
} catch (Exception e) {
e.printStackTrace();
}
}
}
消费者
public class Consumer implements Runnable {
Queue<Integer> queue;
private int size = 0;
public Consumer(Queue<Integer> queue, int size) {
this.queue = queue;
this.size = size;
}
private void sonsume() throws Exception {
int i = 0;
synchronized (queue) {
while (true) {
i++;
while (queue.isEmpty()){
System.out.println("已经消费完了");
queue.wait();
}
Integer remove = queue.remove();
System.out.println("消费者开始消费: " + remove);
Thread.sleep(100);
queue.notifyAll();
}
}
}
@Override
public void run() {
try {
sonsume();
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结
synchronized 对象锁 锁住同样一个对象 让两个线程相互通信
JUC 包和AQS
什么是AQS
AQS(AbstractQueuedSynchronizer)是Java中的一个用于构建锁和同步器的框架,它是java.util.concurrent包中的核心组件之一。Doug Lea开发了AQS,它为实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关的同步器(信号量、事件等)提供了一个基础框架。
AQS利用了一个整型的状态(state)变量来表示同步状态,通过内部的方法来改变这个状态,并且提供了一种高效的方式来管理线程的阻塞和唤醒。它是许多并发工具的基础,例如ReentrantLock、CountDownLatch、Semaphore、ReentrantReadWriteLock等。
AQS的工作原理:
1.状态(State): AQS使用一个整数(state)来表示同步状态,根据具体的同步器的需求,这个状态可以代表不同的意义。例如,在ReentrantLock中,状态为0表示未锁定状态,而大于0表示锁定状态,并且计数表示重入的次数。
2.等待队列: 当线程尝试获取同步状态失败时,AQS会将该线程包装成一个节点(Node)加入到等待队列中。等待队列是一个类型为Node的双向链表,用来管理所有等待获取同步状态的线程。
3.获取和释放同步状态: AQS定义了两种资源获取的方式:独占和共享。独占方式下,一个时间点只能有一个线程获取到资源。共享方式下,允许多个线程同时获取到资源。具体的同步器可以根据需求选择实现这两种方式中的一种或两种。
AQS的重要方法:
- protected boolean tryAcquire(int arg):尝试获取资源,成功则返回true,失败则返回false。
- protected boolean tryRelease(int arg):尝试释放资源,成功则返回true,失败则返回false。
- protected int tryAcquireShared(int arg):尝试获取共享资源。
- protected boolean tryReleaseShared(int arg):尝试释放共享资源。
- public final void acquire(int arg)、public final void acquireShared(int arg):调用这些方法的线程将会在获取不到资源时,被加入到等待队列中,并在条件满足时被唤醒。
AQS通过内部维护的等待队列来管理所有竞争资源的线程,当一个线程获取资源时,AQS会根据资源的状态和获取模式(独占/共享)来决定是否需要阻塞线程,当资源被释放时,会从等待队列中唤醒一个或多个线程来尝试获取资源。这个机制是构建锁和其他同步器的基础,使得开发者可以较为容易地实现安全的并发控制。
重入锁ReentrantLock
public class Demo3 implements Runnable{
static int num;
static ReentrantLock lock = new ReentrantLock(); // 可以锁住非静态方法
void add() {
lock.lock();// 加锁
for (int i = 0; i < 20000; i++) {
num++;
}
lock.unlock();// 解锁
}
@Override
public void run() {
add();
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Demo3());
thread.start();
}
Thread.sleep(2000);
System.out.println(num);
}
}
重入锁的意思,就是一个对象在访问多个争用资源时候,拿到了相同的锁之后,就不用再去获得锁
如果是共享锁,对象在访问多个争用资源的时候,获得了一个锁,别的对象没有锁不能访问该资源,而访问第二个争用资源时,又没有先释放锁,造成了死锁现象。
java 中如何使用线程池
线程池的类别
线程池的使用方法 Executors.newFixedThreadPool 实例
public class Demo4 implements Runnable{
static int num;
synchronized static void add() {
for (int i = 0; i < 20000; i++) {
num++;
}
}
@Override
public void run() {
add();
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
for (int i = 0; i < 3; i++) {//启动3个任务
executorService.execute(new Demo4());
}
Thread.sleep(200);
System.out.println("最终结果: "+ num);
executorService.shutdownNow();
}
}