Java 多线程常用方法以及关联设计模式

1. start( )和 run( )

测试代码:

/**
 * 多线程常用方法
 */
public class UsualMethods {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "...run");
        },"t1");

        t1.run();
        t1.start();
    }
}

在这里插入图片描述

1.1 run( )

被创建的Thread对象直接调用重写的run方法时, run方法是在主线程中被执行的,而不是在我们所创建的线程中执行。

    /* What will be run. */
    private Runnable target;

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
  • 实际就是调用Runnable接口的 run方法。
  • 也可以直接继承Thread类重写run方法

1.2 start( )

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

本地方法 start0 启动线程

2. sleep( ) 和 yield( )

2.1 sleep( ): 使线程阻塞

public static native void sleep(long millis) throws InterruptedException;
  • 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞),可通过state()方法查看
  • 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  • 睡眠结束后的线程未必会立刻得到执行
  • 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性 ,如:
    • TimeUnit.SECONDS.sleep(1); // 休眠一秒
    • TimeUnit.MINUTES.sleep(1); // 休眠一分钟

2.2 yield( ):让出当前线程

public static native void yield();
  • 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态(仍然有可能被执行),然后调度执行其它线程
  • 具体的实现依赖于操作系统的任务调度器

2.3 setPriority( ):设置线程优先级

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它

  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用

  • 设置方法:thread1.setPriority(Thread.MAX_PRIORITY); //设置为优先级最高

3. wait( ) 和 notify( )

3.1 原理概述

在这里插入图片描述

  • 锁对象调用wait方法(obj.wait),就会使当前线程进入WaitSet中,变为WAITING状态。
  • 处于BLOCKED和WAITING状态的线程都为阻塞状态,CPU都不会分给他们时间片。但是有所区别:
    • BLOCKED状态的线程是在竞争对象时,发现Monitor的Owner已经是别的线程了,此时就会进入EntryList中,并处于BLOCKED状态
    • WAITING状态的线程是获得了对象的锁,但是自身因为某些原因需要进入阻塞状态时,锁对象调用了wait方法而进入了WaitSet中,处于WAITING状态
  • BLOCKED状态的线程会在锁被释放的时候被唤醒,但是处于WAITING状态的线程只有被锁对象调用了notify方法(obj.notify/obj.notifyAll),才会被唤醒。

注:只有当对象被锁以后,才能调用wait和notify方法

3.2 Wait与Sleep的区别

不同点

  • Sleep是Thread类的静态方法,Wait是Object的方法,Object又是所有类的父类,所以所有类都有Wait方法。
  • Sleep在阻塞的时候不会释放锁,而Wait在阻塞的时候会释放锁
  • Sleep不需要与synchronized一起使用,而Wait需要与synchronized一起使用(对象被锁以后才能使用)

相同点

  • 阻塞状态都为TIMED_WAITING
public class Test1 {
	final static Object LOCK = new Object();
	public static void main(String[] args) throws InterruptedException {
        //只有在对象被锁住后才能调用wait方法
		synchronized (LOCK) {
			LOCK.wait();
		}
	}
}

3.3 优雅地使用wait/notify

什么时候适合使用wait

当线程不满足某些条件,需要暂停运行时,可以使用wait。这样会将对象的锁释放,让其他线程能够继续运行。如果此时使用sleep,会导致所有线程都进入阻塞,导致所有线程都没法运行,直到当前线程sleep结束后,运行完毕,才能得到执行。

使用wait/notify需要注意什么

当有多个线程在运行时,对象调用了wait方法,此时这些线程都会进入WaitSet中等待。如果这时使用了notify方法,可能会造成虚假唤醒(唤醒的不是满足条件的等待线程),这时就需要使用notifyAll方法

synchronized (LOCK) {
	while(//不满足条件,一直等待,避免虚假唤醒) {
		LOCK.wait();
	}
	//满足条件后再运行
}

//其他线程
synchronized (LOCK) {
	//唤醒所有等待线程
	LOCK.notifyAll();
}

4. join( )

4.1 设计模式之保护性暂停

4.1.1 概述

在这里插入图片描述

4.1.2 举例

没有时间限制的暂停

  1. 线程 1 进入等待 wait 状态 想要获得response
  2. 线程2 设置response 并且唤醒线程1 notify
package top.nnzi;

/**
 * @program: Java Concurrent
 * @description:
 * @author: A.iguodala
 * @create: 2021-04-04 22:23
 **/
public class Demo1 {
    public static void main(String[] args) {
        String hello = "hello world";
        Guarded guarded = new Guarded();

        new Thread(()->{
            System.out.println("想要得到结果");
            synchronized (guarded) {
                System.out.println("结果是:"+guarded.getResponse());
            }
            System.out.println("得到结果");
        },"线程1").start();

        new Thread(()->{
            System.out.println("设置结果");
            synchronized (guarded) {
                guarded.setResponse(hello);
            }
        },"线程2").start();
    }
}

class Guarded {
    /**
     * 要返回的结果
     */
    private Object response;

    //优雅地使用wait/notify
    public Object getResponse() {
        //如果返回结果为空就一直等待,避免虚假唤醒
        while(response == null) {
            synchronized (this) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return response;
    }

    public void setResponse(Object response) {
        this.response = response;
        synchronized (this) {
            //唤醒休眠的线程
            this.notifyAll();
        }
    }

    @Override
    public String toString() {
        return "Guarded{" +
                "response=" + response +
                '}';
    }
}

带超时判断的暂停

  • 只改动getResponse ( ) 方法
  • 先获得当前时间 进入循环
  • 通过过去的时间和总时间计算出需要等待的时间(因为如果别的线程调用notifyAll () 方法时该对象会被唤醒,然而等待并没有得到response 并且 等待时间不足,而下次也不需要等待最初的完整时间)
  • 获得系统时间和开始时间相减获得度过时间
  • 如果没有获得response就继续循环, 如果等待时间为0了则跳出循环
public Object getResponse(long time) {
		synchronized (this) {
			//获取开始时间
			long currentTime = System.currentTimeMillis();
			//用于保存已经等待了的时间
			long passedTime = 0;
			while(response == null) {
				//看经过的时间-开始时间是否超过了指定时间
				long waitTime = time -passedTime;
				if(waitTime <= 0) {
					break;
				}
				try {
                   	//等待剩余时间
					this.wait(waitTime);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//获取当前时间
				passedTime = System.currentTimeMillis()-currentTime		
            }
		}
		return response;
	}

4.2 join ( ) 方法分析

用于等待某个线程结束。哪个线程内调用join()方法,就等待哪个线程结束,然后再去执行其他线程。

如在主线程中调用t1.join(),则需要主线程等待t1线程结束再继续执行

public class UsualMethods {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "...run");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");
        t1.start();
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程");
    }
}
Thread thread = new Thread();
//等待thread线程执行结束
thread.join();
//最多等待1000ms,如果1000ms内线程执行完毕,则会直接执行下面的语句,不会等够1000ms
thread.join(1000);

源码分析

  • 就是上文第二种情况的具体实现
  • 如果是 join(0) 则 如果线程存活( isAlive( ) ) 则一直wait
  • delay 用来存储wait的剩余时间 等于 0 则退出循环
  • 不直接使用wait (time)是因为防止被其他线程notifyAll( ) 虚假唤醒
 public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
 }

4.3 设计模式扩展(1) : 解耦等待和生产

举例
在这里插入图片描述

  • 邮递员和居民送信的关系 简单实现一个邮递员对应一个居民
  • 居民开始等待收信( wait( ) )
  • 邮递员根据id寄信后 唤醒等待的居民
package top.nnzi;

import java.util.Hashtable;
import java.util.Map;
import java.util.Set;

/**
 * @program: Java Concurrent
 * @description:
 * @author: A.iguodala
 * @create: 2021-04-04 22:23
 **/
public class Demo1 {
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new People().start();
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (Integer id : MailBox.getIds()) {
            new PostMan(id,"信件" + id).start();
        }

    }
}
class People extends Thread{
    @Override
    public void run() {
        Guarded guarded = MailBox.createGuarded();
        System.out.println("开始收信");
        Object response = guarded.getResponse();
        System.out.println("收到" + response);

    }
}
class PostMan extends Thread{
    private int id;
    private String mail;

    public PostMan(int id, String mail) {
        this.id = id;
        this.mail = mail;
    }

    @Override
    public void run() {
        Guarded guarded = MailBox.getGuarded(id);
        System.out.println("开始送信");
        guarded.setResponse(mail);
        System.out.println("送到信了");
    }
}

class MailBox {
    private static Map<Integer, Guarded> boxs = new Hashtable<>();

    private static int id;

    private static synchronized int generateId () {
        return id++;
    }

    public static Guarded getGuarded (int id) {
        return boxs.remove(id);
    }

    public static Guarded createGuarded () {
        Guarded guarded = new Guarded(generateId());
        boxs.put(guarded.getId(), guarded);
        return guarded;
    }

    public static Set<Integer> getIds() {
        return boxs.keySet();
    }
}

class Guarded {

    /**
     * 标识 Guarded
     */
    private int id;


    /**
     * 要返回的结果
     */
    private Object response;


    public int getId() {
        return id;
    }

    public Guarded(int id) {
        this.id = id;
    }

    /**
     * 获得响应
     * @return
     */
    public Object getResponse() {
        //如果返回结果为空就一直等待,避免虚假唤醒
        while(response == null) {
            synchronized (this) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return response;
    }

    /**
     * 生产响应
     * @param response
     */
    public void setResponse(Object response) {
        this.response = response;
        synchronized (this) {
            //唤醒休眠的线程
            this.notifyAll();
        }
    }

    @Override
    public String toString() {
        return "Guarded{" +
                "response=" + response +
                '}';
    }
}

4.4 设计模式扩展(2) : 生产者 / 消费者

  • 与前面的保护性暂停中的GuardObject 不同,不需要产生结果和消费结果的线程一一对应
  • 消费队列可以用来平衡生产和消费的线程资源
  • 生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据
  • 消息队列是有容量限制的,满时不会再加入数据,空时不会再消耗数据
  • JDK中各种阻塞队列,采用的就是这种模式

在这里插入图片描述
举例

  • 注意 该情况时 线程之间的消息队列,与 RabbitMQ等进程间通信有一定区别
package top.nnzi;

import java.util.Deque;
import java.util.LinkedList;

/**
 * @Description:
 * @Author: Aiguodala
 * @CreateDate: 2021/4/5 13:55
 */

public class Demo2 {

    public static void main(String[] args) {
        MessageQueue mq = new MessageQueue(2);

        for (int i = 0; i < 3; i++) {
            final int id = i;
            new Thread(() -> {
                mq.put(new Message(id, "消息" + id));
            },"生产者" + id + "号").start();
        }

        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mq.take();
            }
        },"消费者").start();
    }

}

class MessageQueue {
    private int capcity;

    public MessageQueue(int capcity) {
        this.capcity = capcity;
    }

    private static Deque<Message> queue = new LinkedList<>();

    public Message take () {
        synchronized (queue) {
            while (queue.isEmpty()) {
                try {
                    System.out.println(Thread.currentThread().getName() + "队列为空,等待消费");
                    queue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "消费消息");
            Message message = queue.removeFirst();
            queue.notifyAll();
            return message;
        }
    }

    public void put (Message message) {
        synchronized (queue) {
            while (queue.size() == capcity) {
                try {
                    System.out.println(Thread.currentThread().getName() + "队列满了 无法生产");
                    queue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "生产消息");
            queue.add(message);
            queue.notifyAll();
        }
    }

}
class Message {
    private int id;
    private Object data;

    public Message(int id, Object data) {
        this.id = id;
        this.data = data;
    }

    public int getId() {
        return id;
    }

    public Object getData() {
        return data;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", data=" + data +
                '}';
    }
}

5. interrupt( ):打断线程

  • 如果一个线程在在运行中被打断,打断标记会被置为true。
  • 如果是打断因sleep wait join方法而被阻塞的线程,会将打断标记置为false 并抛出InterruptedException异常
  • 正常运行的线程在被打断后,不会停止,会继续执行。如果要让线程在被打断后停下来,需要使用打断标记来判断。
  • isInterrupted() 用于查看是否被打断

5.1 两阶段终止模式

当我们在执行线程一时,想要终止线程二,这是就需要使用interrupt方法来优雅的停止线程二。

在这里插入图片描述

public class Test7 {
	public static void main(String[] args) throws InterruptedException {
		Monitor monitor = new Monitor();
		monitor.start();
		Thread.sleep(3500);
		monitor.stop();
	}
}

class Monitor {

	Thread monitor;

	/**
	 * 启动监控器线程
	 */
	public void start() {
		//设置线控器线程,用于监控线程状态
		monitor = new Thread() {
			@Override
			public void run() {
				//开始不停的监控
				while (true) {
                    //判断当前线程是否被打断了
					if(Thread.currentThread().isInterrupted()) {
						System.out.println("处理后续任务");
                        //终止线程执行
						break;
					}
					System.out.println("监控器运行中...");
					try {
						//线程休眠
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
						//如果是在休眠的时候被打断,不会将打断标记设置为true,这时要重新设置打断标记
						Thread.currentThread().interrupt();
					}
				}
			}
		};
		monitor.start();
	}

	/**
	 * 	用于停止监控器线程
	 */
	public void stop() {
		//打断线程
		monitor.interrupt();
	}
}

6. setDaemon( ):设置守护线程

当JAVA进程中有多个线程在执行时,只有当所有非守护线程都执行完毕后,JAVA进程才会结束。但当非守护线程全部执行完毕后,守护线程无论是否执行完毕,也会一同结束。

//将线程设置为守护线程, 默认为false
monitor.setDaemon(true);

守护线程的应用

  • 垃圾回收器线程就是一种守护线程
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等 待它们处理完当前请求

7. park( ) / unpark( )

7.1 基本使用

park/unpark都是LockSupport类中的的方法

//暂停线程运行
LockSupport.park;

//恢复线程运行
LockSupport.unpark(thread);
public static void main(String[] args) throws InterruptedException {
		Thread thread = new Thread(()-> {
			System.out.println("park");
            //暂停线程运行
			LockSupport.park();
			System.out.println("resume");
		}, "t1");
		thread.start();

		Thread.sleep(1000);
		System.out.println("unpark");
    	//恢复线程运行
		LockSupport.unpark(thread);
	}

7.2 与wait/notify的区别

  • wait,notify 和 notifyAll 必须配合Object Monitor一起使用,而park,unpark不必
  • park ,unpark 是以线程为单位来阻塞和唤醒线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么精确
  • park & unpark 可以先 unpark,而 wait & notify 不能先 notify
  • park不会释放锁,而wait会释放锁

7.3 原理

每个线程都有一个自己的Park对象,并且该对象_counter, _cond,__mutex组成 (c 编写)

先调用park再调用unpark时

  • 先调用park
    • 线程运行时,会将Park对象中的**_counter的值设为0**;
    • 调用park时,会先查看counter的值是否为0,如果为0,则将线程放入阻塞队列cond中
    • 放入阻塞队列中后,会再次将counter设置为0
      在这里插入图片描述
  • 然后调用unpark
    • 调用unpark方法后,会将counter的值设置为1
    • 去唤醒阻塞队列cond中的线程
    • 线程继续运行并将counter的值设为0
      在这里插入图片描述

先调用unpark,再调用park

  • 调用unpark
    • 会将counter设置为1(运行时0)
  • 调用park方法
    • 查看counter是否为0
    • 因为unpark已经把counter设置为1,所以此时将counter设置为0,但不放入阻塞队列cond中

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值