java实现多线程的方式
- 继承thred接口
- 实现runnable接口
- 实现Callable接口
创建线程的方式
- 通过thread类直接创建线程
- 利用线程池内部类创建线程
启动线程的方式
- 调用线程的start()方法
- 利用线程池内部创建线程 那啥是线程池呢
上下文切换
回忆起了操作系统中的pcb,tcb。操作系统在切换线程的时候也会保留下pcb和tcb的信息
cpu执行线程的任务时,会为线程分配时间片,以下几种情况会发生上下文切换。
- 线程的cpu时间片用完
- 垃圾回收
- 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当发生上下文切换时,操作系统会保存当前线程的状态,并恢复另一个线程的状态,jvm中有块内存地址叫程序计数器,用于记录线程执行到哪一行代码,是线程私有的。
JVM程序计数器 VS OS程序计数器
守护线程
java进程需要等待所有线程都运行结束,才会结束,有一种特殊线程叫守护线程,当所有的非守护线程都结束后,即使它没有执行完,也会强制结束。
垃圾回收线程就是典型的守护线程
线程的阻塞
join是指调用该方法的线程进入阻塞状态,等待某线程执行完成后恢复运行
线程的打断
时间片的切换
线程的状态

线程的五种状态

线程通信
线程间通信可以通过共享变量+wait()¬ify()来实现
- Thread-0先获取到对象的锁,关联到monitor的owner,同步代码块内调用了锁对象的wait()方法,调用后会进入waitSet等待,Thread-1同样如此,此时Thread-0的状态为Waitting
- Thread2、3、4、5同时竞争,2获取到锁后,关联了monitor的owner,3、4、5只能进入EntryList中等待,此时2线程状态为 Runnable,3、4、5状态为Blocked
- 2执行后,唤醒entryList中的线程,3、4、5进行竞争锁,获取到的线程即会关联monitor的owner
- 3、4、5线程在执行过程中,调用了锁对象的notify()或notifyAll()时,会唤醒waitSet的线程,唤醒的线程进入entryList等待重新竞争锁
注意:
- Blocked状态和Waitting状态都是阻塞状态
- Blocked线程会在owner线程释放锁时唤醒
- wait和notify使用场景是必须要有同步,且必须获得对象的锁才能调用,使用锁对象去调用,否则会抛异常
- wait() 释放锁 进入 waitSet 可传入时间,如果指定时间内未被唤醒 则自动唤醒
- notify()随机唤醒一个waitSet里的线程
- notifyAll()唤醒waitSet中所有的线程

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public class Threaded_communication { static final Objectlock= new Object(); public static void main(String ...arg){ new Thread(()->{ synchronized (lock){ System.out.println("开始执行1"); try{ lock.wait(); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("继续执行核心逻辑1"); } },"t1").start();
new Thread(()->{ synchronized (lock){ System.out.println("开始执行2"); try{ lock.wait(); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("继续执行核心逻辑2"); } },"t2").start();
try{ Thread.sleep(2000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println("开始唤醒"); synchronized (lock){ lock.notifyAll(); } } }
|
wait 和 sleep的区别
wait是Object的方法,sleep是Thread的方法
wait会立即释放锁,sleep不会释放锁
生产者消费者模型
包含一点java内部类的概念
https://cloud.tencent.com/developer/article/1675402
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| import java.util.LinkedList;
public class Productor_consummer { public static void main(String... args) throws InterruptedException { MessageQueue queue = new MessageQueue(2);
for (int i = 0; i < 3; i++) { int id = i; new Thread( () -> { queue.put(new Message(id, "值" + id)); }, "生产者" + i) .start(); }
new Thread( () -> { while (true) { queue.take(); } }, "消费者") .start(); }
static class MessageQueue { private LinkedList<Message> list = new LinkedList<>();
private int capacity;
public MessageQueue(int capacity) { this.capacity = capacity; } public void put(Message message) { synchronized (list) { while (list.size() == capacity) { System.out.println("队列已满"); try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } list.addLast(message); System.out.println("生产消息:{}"); list.notifyAll(); } }
public Message take() { synchronized (list) { while (list.isEmpty()) { System.out.println("队列空了等待消费者"); try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } Message message = list.removeFirst(); System.out.println("消费者消息:{}"); list.notifyAll(); return message; } } }
static class Message { private int id; private Object value;
public Message(int id, Object value) { this.id = id; this.value = value; } } }
|
ReentrantLock 可重入锁
实例:
一个线程输出a,一个线程输出b,一个线程输出c,abc按照顺序输出,连续输出5次
可重入锁的原理:
重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock;
public class demo_reentrantLock_2 { public static void main(String... arg) { AwaitSignal awaitSignal = new AwaitSignal(5); Condition a = awaitSignal.newCondition(); Condition b = awaitSignal.newCondition(); Condition c = awaitSignal.newCondition();
new Thread( () -> { awaitSignal.print("a", a, b); }, "a") .start();
new Thread( () -> { awaitSignal.print("b", b, c); }, "b") .start();
new Thread( () -> { awaitSignal.print("c", c, a); }, "c") .start(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } awaitSignal.lock(); try { a.signal(); } finally { awaitSignal.unlock(); } }
static class AwaitSignal extends ReentrantLock { private int loopNumber;
public AwaitSignal(int loopNumber) { this.loopNumber = loopNumber; }
public void print(String print, Condition current, Condition next) { for (int i = 0; i < loopNumber; i++) { lock(); try { try { current.await(); System.out.println(print); } catch (InterruptedException e) { System.out.println(e); } next.signal(); } finally { unlock(); } } } } }
|
condition
死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class DeadLock { public static void main(String... arg) { Beer beer = new Beer(); Story story = new Story(); new Thread( () -> { synchronized (beer) { System.out.println("我有酒,给我故事"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (story) { System.out.println("小王开始喝酒讲故事"); } } }) .start(); new Thread( () -> { synchronized (story) { System.out.println("我有故事,给我酒"); try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println(e); } synchronized (beer) { System.out.println("老王开始喝酒讲故事"); } } }) .start(); }
static class Beer {}
static class Story {} }
|
java内存模型(JMM)
volital 关键字
线程池
线程的资源很宝贵,不可能无限的创建,必须要有管理线程的工具,线程池就是一种管理线程的工具,java开发中经常有池化的思想,如 数据库连接池、Redis连接池等。
预先创建好一些线程,任务提交时直接执行,既可以节约创建线程的时间,又可以控制线程的数量。