Java入门12(多线程)
多线程
线程的实现方式
- 继承 Thread 类:一旦继承了 Thread 类,就不能再继承其他类了,可拓展性差
- 实现 Runnable 接口:仍然可以继承其他类,可拓展性较好
- 使用线程池
继承Thread 类
不能通过线程对象调用 run() 方法,需要通过 t1.start() 方法,使线程进入到就绪状态,只要进入到就绪状态的线程才有机会被JVM调度选中
// 这是一个简单的栗子
public class StudentThread extends Thread{
public StudentThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println("This is a test thread!");
System.out.println(this.getName());
}
}
}
// 启动类
public static void main(String[] args) {
Thread t1 = new StudentThread();
// 不能通过线程对象调用run()方法
// 通过 t1.start() 方法,使线程进入到就绪状态,只要进入到就绪状态的线程才有机会被JVM调度选中
t1.start();
}
实现 Runable 接口
实现方式需要借助 Thread 类的构造函数,才能完成线程对象的实例化
// 介还是一个简单的栗子
public class StudentThreadRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println("This is a test thread!");
System.out.println(Thread.currentThread().getName());
}
}
}
// 启动类
public static void main(String[] args) {
// 实现方式需要借助 Thread 类的构造函数,才能完成线程对象的实例化
StudentThreadRunnable studentThreadRunnable = new StudentThreadRunnable();
Thread t01 = new Thread(studentThreadRunnable);
t01.setName("robot010");
t01.start();
}
匿名内部类实现
在类中直接书写一个当前类的子类,这个类默认不需要提供名称,类名由JVM临时分配
public static void main(String[] args) {
Thread t01 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println("This is a test thread!");
}
System.out.println(Thread.currentThread().getName()); // 线程名
System.out.println(this.getClass().getName()); // 匿名线程类类名
}
};
t01.start();
}
线程的休眠(sleep方法)
sleep方法,会使当前线程暂停运行指定时间,单位为毫秒(ms),其他线程可以在sleep时间内,获取JVM的调度资源
// 这是一个计时器
public class TimeCount implements Runnable{
@Override
public void run() {
int count = 0;
while(true){
System.out.println(count);
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
// 测试类
public static void main(String[] args) {
System.out.println("这是main方法运行的时候,开启的主线程~~~");
TimeCount timeCount = new TimeCount();
Thread timeThread = new Thread(timeCount);
System.out.println("开启计时器");
timeThread.start();
System.out.println("主线程即将休眠>>>>>>>>>>>");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(">>>>>>>>>>>主线程休眠结束~~~~~");
}
线程的加入(join方法)
被 join 的线程会等待 join 的线程运行结束之后,才能继续运行自己的代码
public static void main(String[] args) {
Thread thread01 = new Thread(){
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println("This is thread-01!");
}
}
};
thread01.start();
try {
thread01.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread02 = new Thread(){
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println("This is thread-02!");
}
}
};
thread02.start();
}
// thread02 会等待 thread01 完全跑完,才会开始自己的线程
线程的优先级(priority方法)
优先级高的线程会有更大的几率竞争到JVM的调度资源,但是高优先级并不代表绝对,充满玄学✨
public static void main(String[] args) {
Thread thread01 = new Thread(){
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println("This is thread-01! " + Thread.currentThread().getPriority());
}
}
};
Thread thread02 = new Thread(){
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println("This is thread-02! " + Thread.currentThread().getPriority());
}
}
};
thread01.setPriority(1);
thread02.setPriority(10);
// 尽管thread02优先级高于thread01,但是也有可能
thread01.start();
thread02.start();
}
线程的让步(yield方法)
立刻让出JVM的调度资源,并且重新参与到竞争中
public static void main(String[] args) {
Thread thread01 = new Thread(){
@Override
public void run(){
for (int i = 1; i <= 10; i++) {
System.out.println("This is thread-01! " + i);
}
}
};
Thread thread02 = new Thread(){
@Override
public void run(){
for (int i = 1; i <= 10; i++) {
System.out.println("This is thread-02! " + i);
Thread.yield();
}
}
};
thread01.start();
thread02.start();
}
守护线程(Deamon)
会在其他非守护线程都运行结束之后,自身停止运行,(GC垃圾回收机制就是一个典型的守护线程)
public static void main(String[] args) {
Thread thread01 = new Thread(){
@Override
public void run(){
int times = 0;
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("time pass " + ++times + "second");
}
}
};
Thread thread02 = new Thread(){
@Override
public void run(){
for (int i = 1; i <= 10; i++) {
System.out.println("This is thread-02! " + i);
}
}
};
// 将t1设置为守护线程
thread01.setDaemon(true);
thread01.start();
thread02.start();
// 延长主线程运行,便于观察结果
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("main thread end \\(-_-)/");
}
线程同步
数据操作的原子性
具有原子性的操作,不会被其他线程打断,类似(a++)的操作是不具备原子性的,因此很容易在多线程场景中出现误差
synchronized 悲观锁(互斥性)
优缺点:保证了数据在多线程场景下的安全(保证线程安全),牺牲的是效率,锁的获取和释放,其他线程被阻塞都会额外消耗性能
同步对象:被多个线程所竞争的资源对象叫做同步对象
核心作用: 确保线程在持有锁的期间内,其他线程无法操作和修改指定数据(同步对象)
每一个同步对象都会持有一把线程锁,当线程运行到synchronized 修饰的方法或代码时,线程会自动获取当前同步对象的线程锁,在synchronized 修饰的方法或代码块运行结束后,该线程会自动释放此线程锁,在持有线程锁的这段时间里,其他线程是无法执行synchronized 所修饰的代码块的,其他线程会被阻塞在synchronized 代码块之外,直到这把锁被释放。。。
// synchronized 的两种写法:
// 1. 写在方法之前,修饰整个方法
public synchronized Ticket getTicket(){
// 取票
Ticket ticketTmp = null;
if(!tickets.isEmpty()){
ticketTmp = tickets.removeLast();
}
return ticketTmp;
}
// 2. 代码块,修饰代码块所包含的部分
public Ticket getTicket(){
// 取票
synchronized(this){
Ticket ticketTmp = null;
if(!tickets.isEmpty()){
ticketTmp = tickets.removeLast();
}
return ticketTmp;
}
}
线程死锁
💥一个线程可以持有多个不同的同步对象的锁!!!当两个线程同时想要持有相同一把锁时,就会产生死锁现象
wait notify notifyAll
这三个方法并不是通过线程调用,而是通过同步对象第哦啊用,所有对象都可以当作是同步对象,这三个方法是在Object类中定义的
方法 | 作用 |
---|---|
wait | 让占用当前同步对象锁的线程进行等待,并且释放锁,直到被唤醒时才会被恢复 |
notify | 通知一个在当前同步对象上等待的线程 进行唤醒,让其重新竞争锁,并运行代码 |
notifyAll | 通知在当前同步对象上等待的所有线程,进行唤醒 |
生产者消费者模型
- 生产者线程负责提供用户请求
- 消费者线程负责处理用户请求
// 模拟生产者消费者
// 球(ball) => 数据
public class Ball {
private int ballNum;
}
// 篮子(basket) => 数据容器
public class Basket {
// 指针,指向最新放入的球
private int index = 0;
// 容器
private Ball[] balls = new Ball[5];
// 存操作
public synchronized void ballPut(Ball ballTmp){
// 不断的判断容器是否放满,如果线程被唤醒,需要再此判断,如果此时容器还是满的,应该继续等待
while(index == balls.length){
System.out.println("The basket is full~~");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 进程能推进到这里,说明容器并没有满,可以进行存操作
balls[index++] = ballTmp;
System.out.println("ballPut : " + index);
// 此时可以唤醒等待的取操作线程
this.notifyAll();
}
// 取操作
public synchronized void ballGet(){
// 不断的判断容器是否为空,如果线程被唤醒,需要再此判断,如果此时容器还是空的,应该继续等待
while(index == 0){
System.out.println("The basket is empty~~");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 进程能推进到这里,说明容器没有被取空,可以进行取操作
System.out.println("ballGet : " + (index - 1));
balls[--index] = null;
// 此时可以唤醒等待的存操作线程
this.notifyAll();
}
}
// 放球(ballPut) => 生产者
public class Producer implements Runnable{
// 为了保证生产者和消费者绑定的是同一个容器
Basket basket;
// 构造方法,方便传参
public Producer(Basket basket){
this.basket = basket;
}
// 存操作
@Override
public void run() {
// 模拟一共放10个球
for (int i = 0; i < 20; i++) {
Ball ballTmp = new Ball(i);
basket.ballPut(ballTmp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
// 拿球(ballOut) => 消费者
public class Consumer implements Runnable{
// 为了保证生产者和消费者绑定的是同一个容器
Basket basket;
// 构造方法,方便传参
public Consumer(Basket basket){
this.basket = basket;
}
// 取操作
@Override
public void run() {
for (int i = 0; i < 20; i++) {
basket.ballGet();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
// 测试启动类
public static void main(String[] args) {
// 实例化容器
Basket basket = new Basket();
// 实例化生产者,消费者
Producer producer = new Producer(basket);
Consumer consumer = new Consumer(basket);
// 生产者线程
new Thread(producer).start();
// 等待,容器被装满
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 消费者线程
new Thread(consumer).start();
}
线程池和自定义线程池
优点:节约线程资源,让线程池中的消费者线程不断的去执行任务
- 需要准备一个存放业务的容器
- 只启动固定数量的消费线程
- 生产者线程负责向任务容器中提交任务
- 程序开始时,任务容器为空,所有消费者线程都处于 wait 状态
- 直到有一个生产者向任务容器中投入了一个任务,那么就会有一个消费者线程被 notify 唤醒,并执行此任务
- 任务执行完毕之后,该消费者线程会重新处于 wait 状态,等待下一次任务到来
- ⭐(线程复用)整个任务流程,都不需要创建新的线程,而是对已经存在的线程循环使用
模拟线程池基础功能实现(没有实现自动扩容)
public class ThreadPool {
// 线程池的大小
int threadPoolSize;
// 任务容器中的任务 --> 线程
LinkedList<Runnable> tasks = new LinkedList<>();
// 提供一个 add 方法,用于向任务容器中添加任务
public void add(Runnable r){
synchronized (tasks){
tasks.add(r);
// 唤醒消费者线程,取走任务容器中的任务
tasks.notifyAll();
}
}
//构造函数--初始化线程池
public ThreadPool() {
// 初始化线程池大小
threadPoolSize = 10;
// 定义10个消费者线程,并且将其实例化后start
synchronized (tasks){
for (int i = 0; i < threadPoolSize; i++) {
new TaskConsumeThread("ThreadRobot-0" + i+1).start();
}
}
}
// 内部类--消费者线程
class TaskConsumeThread extends Thread{
// 重写 Thread 类构造方法,方便给线程定义 name 属性
public TaskConsumeThread(String name) {
super(name);
}
// 需要被执行的任务
Runnable task;
// 重写 run 方法
@Override
public void run() {
System.out.println(this.getName() + " running!");
while (true){
// 获取任务容器 tasks 的锁,避免多个消费者线程同时操作任务容器,保证任务容器的线程安全
synchronized (tasks){
// 只要任务容器为空,需要 wait 方法,让所有消费者线程等待
while (tasks.isEmpty()){
try {
tasks.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 脱离循环能运行到这里,说明任务容器不为空!
// 当前消费者从任务容器中取出任务,并存再自己的 task 引用中
task = tasks.removeLast();
// 唤醒添加任务的线程
tasks.notifyAll();
System.out.println(this.getName() + " get task!");
// 启动任务线程
new Thread(task).start();
}
}
}
}
}
// 启动测试类01
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool();
Scanner sc = new Scanner(System.in);
String tmp;
while (true) {
tmp = sc.nextLine();
Thread task = new Thread(){
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
threadPool.add(task);
}
}
// 启动测试类02
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool();
int sleepTime = 1000;
// 任务计数
int[] count = {0};
while (true) {
Runnable runnableTmp = new Runnable() {
@Override
public void run() {
// 通过访问外部数组地址,来实现控制
System.out.println("执行任务" + (++count[0]));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
threadPool.add(runnableTmp);
try {
Thread.sleep(sleepTime);
sleepTime = sleepTime > 100 ? sleepTime - 100 : sleepTime;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Java自带的线程池使用
public static void main(String[] args) {
// 参数含义:
// corePoolSize : 线程池中初始线程数量
// maximumPoolSize : 线程池中最大线程数量
// keepAliveTime : 临时线程的最大存活时间
// TimeUnit.SECONDS : 存活时间的单位
// new LinkedBlockingDeque<Runnable>() : 存放任务的任务容器
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
// 创建测试任务线程
Runnable runnableTmp = new Runnable() {
@Override
public void run() {
System.out.println("This is a testRunnable!");
}
};
// 将测试任务抛入线程池即可
threadPoolExecutor.execute(runnableTmp);
}
数据库连接池
如果每有一个用户使用连接,就新建一个连接的话,创建连接和关闭连接的过程是非常消耗性能的,且单一数据库支持的连接总数是有上限的,如果短时间内并发量过大,数据库的连接总数就会被消耗光,后续线程发起的数据库连接就会失败,那么连接池的作用就是:它会在使用之前,就创建好一定数量的连接,如果有线程需要使用连接,就从连接池中借用,而不是重新创建连接,使用完此连接之后,再将此连接归还给连接池,整个过程中,连接池中的连接都不会被关闭,而是被重复使用。
模拟数据库连接池实现
public class ConnectionPool {
List<Connection> connections = new ArrayList<>();
int size;
// 准备构造函数
public ConnectionPool(int size) {
this.size = size;
init();
}
// 初始化连接池
public void init(){
try {
Class.forName("com.mysql.jdbc.Driver");
for (int i = 0; i < size; i++) {
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/iweb?characterEncoding=utf8","root","123456");
connections.add(c);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取一个数据库连接
public synchronized Connection getConnection(){
while (connections.isEmpty()){
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return connections.remove(0);
}
// 归还一个数据库连接
public synchronized void returnConnection(Connection connectionTmp){
connections.add(connectionTmp);
this.notifyAll();
}
}
// 测试启动类
public class Application {
public static void main(String[] args) {
ConnectionPool connectionPool = new ConnectionPool(3);
// 创建100个线程用于测试
for (int i = 0; i < 100; i++) {
new WorkingThread("Thread0" + i,connectionPool).start();
}
}
static class WorkingThread extends Thread{
private ConnectionPool connectionPool;
public WorkingThread(String name,ConnectionPool cp){
super(name);
this.connectionPool = cp;
}
@Override
public void run() {
Connection connection = connectionPool.getConnection();
System.out.println(this.getName() + " get the mysql connection!");
try(Statement statement = connection.createStatement()){
Thread.sleep(1000);
statement.execute("select * from user");
}catch (Exception e){
e.printStackTrace();
}
connectionPool.returnConnection(connection);
}
}
}
druid 德鲁伊数据库连接池(明天再加)
Reentrantlock 悲观锁的另一种实现方式(明天再加)
volatile 乐观锁 Java内存模型(明天再加)
内存私有公有 指令重排序 可见性(明天再加)
一致性协议(明天再加)
热门相关:倾心之恋:总裁的妻子 妈妈的朋友6 修真界败类 上神来了 修真界败类