只知道并发编程原理实在不太够。为了不在接触实际代码(甚至自己写它们)时感到陌生,常见的一些必须要有所了解。
Thread类
Thread就是一个“线程”,它们是最基础的线程包装器。声明Thread需要一个实现Runnable接口的对象。匿名函数会在编译环节被特殊处理,转换为Runnable。
Runnable对象内部可以很复杂,有自己的状态,但Thread只会运行r.run()这个函数。这是一个约定。
直接调用t.run()当然是可以的,但这相当于在当前线程执行一个普通函数,并不会创建新线程。使用t.start()才是正确方式。
sleep()可以让当前线程休眠一段时间。它不会释放锁,如果在占有某个锁时sleep,其他线程在这段时间里只能等待。它可能抛出受检异常InterruptedException,在睡眠期间被中断会触发。
join()会一直等待,直到目标线程执行结束。如果目标线程是当前进程的前驱,它会很有用。
interrupt会中断线程。但它是一个协作性的软中断,而不是强制中断。线程需要自己处理它,例如使用:
while (!Thread.currentThread().isInterrupted()) {
...
}
如果线程没有处理逻辑,interrupt()就对它无效。不过,处理interrupt()是一个规范。
isInterrupted()判断线程是否被中断。线程需要对其做出反应。
Thread.interrupted()是一个静态方法。它会检查中断并清除中断标志,如果有中断返回true,否则返回false。这是一个原子操作。
yield()会让出CPU。在很多操作系统,这是让线程进入就绪态(而非等待态)。它只是告诉操作系统“此处我不需要连续运行”,操作系统不一定会切换到其他线程。
setDaemon(true)将线程设置为守护线程。守护线程的特点是:在所有其他线程结束后,它会自动结束。守护线程会被强行终止(在JVM终止时),所以某些任务,例如事务性操作,不适合放在这里。
Thread.currentThread()是一个静态方法,可以获取当前线程的Thread对象。
Object类
Object是所有Java类的父类。这意味着几乎所有对象都有这些方法。
以下方法和并发编程有关。
o.wait()会释放当前占有对象o的锁,前提是程序已经拥有它(synchronized)。直到被唤醒前,它都会一直等待。
o.notify()会让一个等待的线程被唤醒。JVM并不保证被唤醒进程的顺序,可以当作随机看待。同样需要拥有对象。
o.notifyAll()会唤醒所有等待这个对象的进程。
这三个方法对于生产者/消费者模型的情景非常好用。
synchronized
Java经典的内置锁,和对象是绑定的。
在一个线程运行到synchronized代码块时,任何具有相同锁对象的代码块都会等待。这让同一时间里只有单一线程可以操作对象。
synchronized只保护遵守这一规则的代码。如果有非synchronized的代码块尝试访问被锁住的对象,它们可以无视锁。
synchronized关键字有很多用法。
修饰实例方法
锁定:当前对象(this)
public class Demo {
...
public synchronized void test() {
this.foo += 1;
}
}
修饰静态方法
锁定:Class对象
这使得同一个类里,这种静态方法最多只有一个在运行。
同步代码块
实际开发最常见的方式。
synchronized (obj) {
obj.num += 1;
}
可以自己指定锁对象。
发表回复