一、sleep()方法
sleep方法是Thread的静态方法,它只作用于当前线程(它知道当前线程是哪个),调用sleep后,线程会直接进入到等待状态,直到时间结束
public static void main(String[] args) {
Thread t = new Thread(() -> {
try {
System.out.println("l");
Thread.sleep(1000); //sleep方法是Thread的静态方法,它只作用于当前线程(它知道当前线程是哪个)
System.out.println("b"); //调用sleep后,线程会直接进入到等待状态,直到时间结束
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
二、interrupt()方法
每一个Thread对象中,都有一个interrupt()
方法,调用此方法后,会给指定线程添加一个中断标记以告知线程需要立即停止运行或是进行其他操作,由线程来响应此中断并进行相应的处理,我们前面提到的stop()
方法是强制终止线程,这样的做法虽然简单粗暴,但是很有可能导致资源不能完全释放,而类似这样的发送通知来告知线程需要中断,让线程自行处理后续,会更加合理一些,也是更加推荐的做法。我们来看看interrupt的用法:
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("线程开始运行!");
while (true){ //无限循环
if(Thread.currentThread().isInterrupted()){ //判断是否存在中断标志
break; //响应中断
}
}
System.out.println("线程被中断了!");
});
t.start();
try {
Thread.sleep(3000); //休眠3秒,一定比线程t先醒来
t.interrupt(); //调用t的interrupt方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
三、yield()方法
我们还可以在当前线程的工作不重要时,将CPU资源让位给其他线程,通过使用yield()
方法来将当前资源让位给其他同优先级线程:
代码
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("线程1开始运行!");
for (int i = 0; i < 50; i++) {
if(i % 5 == 0) {
System.out.println("让位!");
Thread.yield();
}
System.out.println("1打印:"+i);
}
System.out.println("线程1结束!");
});
Thread t2 = new Thread(() -> {
System.out.println("线程2开始运行!");
for (int i = 0; i < 50; i++) {
System.out.println("2打印:"+i);
}
});
t1.start();
t2.start();
}
四、join()方法
一个线程等待另一个线程执行完成后再继续进行,我们可以使用join()
方法来实现线程的加入:
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"开始运行!");
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"打印:"+i);
}
System.out.println("线程1结束!");
});
Thread t2 = new Thread(() -> {
System.out.println("线程2开始运行!");
for (int i = 0; i < 50; i++) {
System.out.println("2打印:"+i);
if(i == 10){
try {
System.out.println("线程1加入到此线程!");
t1.join(); //在i==10时,让线程1加入,先完成线程1的内容,在继续当前内容
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
t2.start();
}
其中,join之后,不是合并为一个线程,而是,线程的加入只是等待另一个线程的完成,并不是将另一个线程和当前线程合并,而是让位给join进来的线程先执行完成。
五、synchronized()方法
5.1 多线程情况下Java的内存管理
线程之间的共享变量(比如存在主线程的静态的value变量)存储在主内存(main memory)中,每个线程都有一个私有的工作内存(本地内存),工作内存中存储了该线程以读/写共享变量的副本。
类似于,cpu中高速缓存通过保存内存中数据的副本来提供更加快速的数据访问,但是如果多个处理器的运算任务都涉及同一块内存区域,就可能导致各自的高速缓存数据不一致,在写回主内存时就会发生冲突,这就是引入高速缓存引发的新问题,称之为:缓存一致性。
Java的内存模型也是这样类似设计的,当我们同时去操作一个共享变量时,如果仅仅是读取还好,但是如果同时写入内容,就会出现问题!好比说一个银行,如果我和我的朋友同时在银行取我账户里面的钱,难道取1000还可能吐2000出来吗?
5.2线程锁
我们以一个例子来看,通过synchronized关键字来创造一个线程锁,首先我们来认识一下synchronized代码块,它需要在括号中填入一个内容,必须是一个对象或是一个类,我们在value自增操作外套上同步代码块:
private static int value = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (Main.class){ //使用synchronized关键字创建同步代码块
value++;
}
}
System.out.println("线程1完成");
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (Main.class){
value++;
}
}
System.out.println("线程2完成");
});
t1.start();
t2.start();
Thread.sleep(1000); //主线程停止1秒,保证两个线程执行完成
System.out.println(value);
}
但是注意两个线程必须使用同一把锁!
当一个线程进入到同步代码块时,会获取到当前的锁,而这时如果其他使用同样的锁的同步代码块也想执行内容,就必须等待当前同步代码块的内容执行完毕,在执行完毕后会自动释放这把锁,而其他的线程才能拿到这把锁并开始执行同步代码块里面的内容(实际上synchronized是一种悲观锁)
实战:生产者与消费者
所谓的生产者消费者模型,是通过一个容器来解决生产者和消费者的强耦合问题。通俗的讲,就是生产者在不断的生产,消费者也在不断的消费,可是消费者消费的产品是生产者生产的,这就必然存在一个中间容器,我们可以把这个容器想象成是一个货架,当货架空的时候,生产者要生产产品,此时消费者在等待生产者往货架上生产产品,而当货架有货物的时候,消费者可以从货架上拿走商品,生产者此时等待货架出现空位,进而补货,这样不断的循环。
通过多线程编程,来模拟一个餐厅的2个厨师和3个顾客,假设厨师炒出一个菜的时间为3秒,顾客吃掉菜品的时间为4秒。
代码demo
package com.publisher;
import java.util.Date;
import java.util.LinkedList;
import java.util.Queue;
public class FoodRoom {
/**
* 用一个队列模拟自助餐食堂窗口的流水线
*/
private static Queue<Object> queue = new LinkedList<>();
public static void main(String[] args) {
Thread t1 = new Thread(FoodRoom::add, "厨师1");
Thread t2 = new Thread(FoodRoom::add, "厨师2");
Thread c1 = new Thread(FoodRoom::take ,"顾客1");
Thread c2 = new Thread(FoodRoom::take ,"顾客2");
Thread c3 = new Thread(FoodRoom::take ,"顾客3");
t1.start();
t2.start();
c1.start();
c2.start();
c3.start();
}
/**
* 厨师炒菜ing
*
* @throws InterruptedException
*/
private static void add() {
while (true) {
//做菜时长 3min
try {
Object o = new Object();
Thread.sleep(3000);
//厨师们不能同时往食堂窗口丢菜品,要排队
synchronized (queue) {
System.out.println(new Date() + " " + Thread.currentThread().getName() + "做好菜品");
//往窗口提供菜品
queue.offer(o);
//厨师出餐,通知所有顾客
queue.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 顾客拿菜吃饭的行为
*/
private static void take() {
//一直吃
while (true) {
//顾客要吃4min的食物
try {
//顾客们不能同时拿一个同一个食物
synchronized (queue) {
//窗口流水线还没有新餐品,顾客就要等待,没饭可以吃
while (queue.isEmpty()) {
queue.wait();
}
queue.poll();
System.out.println(new Date() + " " + Thread.currentThread().getName() + "拿到菜品");
}
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}