线程相关方法
线程等待与唤醒机制
注意事项
线程等待和唤醒的方法必须在有synchronized
保护的代码块或方法中执行,并且是使用该保护代码块的锁对象进行方法的调用,也就是必须先拥有monitor锁,否则会抛出IllegalMonitorStateException
非法monitor锁状态异常;之所以这样设计是因为,若不是在同步代码块中的话,由于线程的随机性可能会产生死锁,比如说两个线程,一个是要唤醒,另一个是要等待,若先等待才能被唤醒,反之就会陷入永久等待,所以该操作就需要安全的进行控制
线程等待和唤醒方法是定义在Object对象上的final native
方法,调用等待或唤醒方法的是同一个对象,否则不是同一把锁会无法唤醒,之所以这样设计是因为,这些方法是锁级别的操作,而锁是属于某个对象的(绑定在对象头中),而不是线程中,若将锁定义在线程中则没个线程只能有一把锁,并且也不要使用线程对象的等待方法,这是因为在每个线程执行结束时该线程对象都会都会自动调用notifyAll()
方法
线程等待与唤醒
wait()
:调用该方法的线程会处于等待状态,同时释放调用该方法对象的monitor锁notify()
:随机唤醒一个等待线程notifyAll()
:唤醒全部等待线程,那个优先级高那个先执行
处于等待状态的线程,只有发生以下情况才会被唤醒
- 另一个线程调用这个对象的
notify()
方法,且刚好被唤醒的是该线程 - 另一个线程调用这个对象的
notifyAll()
方法 - 使用了可设置了等待时间的
wait()
方法,并且已经等待超时(若等待时间设置为0则永久等待) - 该线程调用了
interrupt()
方法
public static void main(String[] args) throws InterruptedException {
Object lock = new Object(); //用来当锁的对象
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
synchronized (lock) {
try {
lock.wait(); //当前线程会进入等待状态,同时释放锁
//lock.wait(500); //等待指定时间,超时后自动唤醒,同时获取锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程获取到了锁");
}
});
thread.start();
Thread.sleep(1000); //为了让thread线程先执行
synchronized (lock) {
lock.notify(); //线程唤醒
//lock.notifyAll(); //全部线程唤醒
}
//thread.interrupt(); //线程中断
System.out.println(Thread.currentThread().getName() + "线程执行了唤醒操作");
}
生产者消费者模式
使用两个线程或进程对数据操作进行解耦(不局限两个),共用一个块数据,一个负责消费数据,一个负责生产数据,同时解决了两者生产消费速度的不一致问题:当生产者看到仓库满了时不再生产,而是通知消费者消费,当消费者看到仓库空了时不再消费,而是通知生产者生产
自己实现阻塞队列实现生产者消费者模式(未考虑到线程中断)
public class Storage<V> {
private int maxSize; //仓库最大容量
private LinkedList<V> list; //仓库容器
public Storage() {
this(10); //默认容量是10
}
public Storage(int maxSize) {
this.maxSize = maxSize;
this.list = new LinkedList<V>();
}
public synchronized void put(V element) {
while (list.size() == maxSize) { //当仓库满了就进入等待状态
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(element);
notify();
}
public synchronized V take() {
while (list.size() == 0) { //当仓库空了就恢复可运行状态
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
V element = list.poll();
notify();
return element;
}
public static void main(String[] args) { //测试代码
Storage<Integer> storage = new Storage<>();
Thread producer = new Thread(() -> {
for (int i = 0; i < 50; i++) {
System.out.println("Producer生产了一个" + i);
storage.put(i);
}
});
Thread consumer = new Thread(() -> {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(500); //模拟生产消费速度不一致
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Consumer消费了一个" + storage.take());
}
});
producer.start();
consumer.start();
}
}
使用Java中自带的阻塞队列实现生产者消费者模式(考虑到了线程中断)
public static void main(String[] args) {
ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
Thread producer = new Thread(() -> {
for (int i = 0; i < 50; i++) {
try {
System.out.println("Producer生产了一个" + i);
storage.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread consumer = new Thread(() -> {
for (int i = 0; i < 50; i++) {
try {
System.out.println("Consumer消费了一个" + storage.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
consumer.start();
}
线程交替运行
同步代码块空转
该方式会导致若某个线程一直会拿到锁,也无法进行业务的执行,而是在做空操作,效率较低
public static int count;
public static void main(String[] args) {
Object lock = new Object();
new Thread(() -> {
while (count < 100) {
synchronized (lock) {
if ((count & 1) == 0) { //与运算高效判断奇偶数
System.out.println(Thread.currentThread().getName() + count++);
}
}
}
}, "偶数").start();
new Thread(() -> {
while (count < 100) {
synchronized (lock) {
if ((count & 1) == 1) { //与运算高效判断奇偶数
System.out.println(Thread.currentThread().getName() + count++);
}
}
}
}, "奇数").start();
}
交替唤醒与休眠
拿到锁,执行业务代码,唤醒其他线程,自己进入等待状态;不存在空操作,效率高,但是交替执行的两个线程不保证一定谁先谁后
public static int count = 0;
public static int max = 100;
public static void main(String[] args) {
Object lock = new Object();
Runnable runnable = () -> {
synchronized (lock) {
while (count < max) {
System.out.println(Thread.currentThread().getName() + count++);
lock.notify();
if (count == max) return; //由于锁在两个线程之间互相传递,需要被唤醒后再次进行判断退出线程,而不是等待
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
new Thread(runnable, "奇数").start();
new Thread(runnable, "偶数").start();
}
线程join等待
- 当前线程等待,让出当前线程资源,交由另一个调用该方法的线程执行
- 无参则会等待另一个线程执行结束后执行
- 有参则会先让另一个线程执行指定时间后在争抢资源
- 若另一个线程已经结束但时间还未结束时,当前线程在另一个线程执行结束后也会直接会执行
- 当前线程等待期间被中断时抛出
InterruptedException
中断异常,同时消除当前线程的等待状态
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
};
Thread thread1 = new Thread(runnable);
thread1.start();
thread1.join(); //主线程会在该线程执行结束后执行
//thread1.join(2500); //主线程会在该线程执行2.5秒后执行
//thread1.join(10000); //超过该线程执行时后,会在该线程执行结束后就执行
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
join()
方法的原理是在当前线程执行wait()
方法,让当前线程处于等待状态;任何线程执行结束后都,会执行都会使用这个任意线程对象来执行notifyAll()
方法,所以join()
方法相当于下面代码
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
//this.notifyAll(); //自动执行该代码
}
};
Thread thread = new Thread(runnable);
thread.start();
synchronized (thread) { //主线程进入同步代码块,并且使用thread线程对象作为锁
thread.wait(); //当thread线程执行结束时,会自动执行thread.notifyAll()方法唤醒主线程
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
线程休眠
- 线程不再占用CPU资源
- 休眠期间被中断时不仅抛出
InterruptedException
中断异常,而且会清除中断标记 - 不释放
synchronized
和lock
锁 - 使用
TimeUnit.XXX.sleep()
方法可指定休眠时间单位,编码上可减少对时间的换算,并且对于非法参数并不会抛出异常而是直接忽略
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock(); //为了多个线程使用同一把锁
Runnable runnable = () -> {
synchronized (lock) { //可通过注释切换不同的锁进行查看
//lock.lock();
System.out.println(Thread.currentThread().getName() + "获得到了锁");
try { //锁并不会在休眠中释放掉
TimeUnit.SECONDS.sleep(1); //使用SECONDS单位,休眠一秒
System.out.println(Thread.currentThread().getName() + "线程苏醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//lock.unlock(); //释放锁应该包裹在finally块中
System.out.println(Thread.currentThread().getName() + "释放掉了锁");
}
}
};
//下面启动两个线程
new Thread(runnable).start();
new Thread(runnable).start();
}
线程礼让
让当前线程释放当前CPU时间片,也就是当前线程礼让一次,线程仍处于可运行状态,随后继续按照抢占式继续执行;但是Java标准中规定JVM并不一定保证遵循线程礼让原则,也就是说当前CPU资源不紧张时,并不会一定线程礼让,所以一般开发中不适用线程礼让
Comments NOTHING