三种使用线程方法
有三种使用线程的方法:
- 实现 Runnable 接口;
- 实现 Callable 接口;
- 继承 Thread 类。
实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以理解为任务是通过线程驱动从而执行的。
实现 Runnable 接口
需要实现接口中的 run() 方法。
public class MyRunnable implements Runnable {
@Override
public void run() {
// ...
}
}
使用 Runnable 实例再创建一个 Thread 实例,然后调用 Thread 实例的 start() 方法来启动线程。
public static void main(String[] args) {
MyRunnable instance = new MyRunnable();
Thread thread = new Thread(instance);
thread.start();
}
实现 Callable 接口
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
继承 Thread 类
同样也是需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。
当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。
public class MyThread extends Thread {
public void run() {
// ...
}
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
选哪种
实现接口会更好一些,因为:
- Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
- 类可能只要求可执行就行,继承整个 Thread 类开销过大。
线程之间的通信、协作
线程暂停
sleep()
Thread.sleep(millisec)
方法会休眠当前正在执行的线程,millisec 单位为毫秒- 使当前线程(即调用该方法的线程)暂停执行一段时间,但它并不释放对象锁,也不影响其他线程。注意该方法要捕捉异常。
yield()
- 当前线程执行
yield()
后,声明了当前线程已经完成了生命周期中最重要的部分,并且释放线程所占有的CPU资源 - 让其他线程优先级更高的线程或者优先级和相同优先级的线程有机会运行,但是并不能保证某个特定的线程能够获得CPU资源,没有就继续执行
- 谁能获得CPU完全取决于调度器,在有些情况下调用yield方法的线程甚至会再次得到CPU资源
wait()
wait()方法使当前线程暂停执行并释放对象锁,让其他线程可以进入synchronized同步块,将当前线程被放入对象等待池中,等到被唤醒
线程协作
join()
在线程中调用另一个线程的 join() 方法,会将当前线程挂起,等到调用join()方法的线程执行结束,当前线程再继续运行
对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。
public class JoinExample {
private class A extends Thread {
@Override
public void run() {
System.out.println("A");
}
}
private class B extends Thread {
private A a;
B(A a) {
this.a = a;
}
@Override
public void run() {
try {
a.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B");
}
}
public void test() {
A a = new A();
B b = new B(a);
b.start();
a.start();
}
}
public static void main(String[] args) {
JoinExample example = new JoinExample();
example.test();
}
//执行结果
A
B
wait() notify() notifyAll()
这三个方法用于协调多个线程对共享数据的存取,它们都属于 Object 类的方法
只能用在同步方法或者同步控制块中使用
wait()方法:
使当前线程暂停执行并释放对象锁,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁
notify()方法
当调用notify()方法后,将从对象的等待池中拿走任意一个线程并放到锁池中,只有锁池中的线程能够获取锁;如果锁池中没有线程,则notify()不起作用
notifyAll():
notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争
await() signal() signalAll()
java.util.concurrent
类库中提供了 Condition
类来实现线程之间的协调,可以在 Condition
上调用 await()
方法使线程等待,其它线程调用 signal()
或 signalAll()
方法精准唤醒等待的线程。
相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。
使用 Lock 来获取一个 Condition 对象。
public class AwaitSignalExample {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void before() {
lock.lock();
try {
System.out.println("before");
condition.signalAll();
} finally {
lock.unlock();
}
}
public void after() {
lock.lock();
try {
condition.await();
System.out.println("after");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
AwaitSignalExample example = new AwaitSignalExample();
executorService.execute(() -> example.after());
executorService.execute(() -> example.before());
}
before
after
生产者消费者
public class 精准唤醒顺序访问 {
public static void main(String[] args) {
Resources resources=new Resources();
new Thread(()->{
resources.print5();
},"AA").start();
new Thread(()->{
resources.print10();
},"BB").start();
new Thread(()->{
resources.print15();
},"CC").start();
}
}
class Resources{
private int number=1;
Lock lock=new ReentrantLock();
Condition condition1= lock.newCondition();
Condition condition2= lock.newCondition();
Condition condition3= lock.newCondition();
public void print5(){
lock.lock();
while (number!=1){
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName());
}
number=2;
condition2.signal();
lock.unlock();
}
public void print10(){
lock.lock();
while(number!=2){
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
}
number=3;
condition3.signal();
lock.unlock();
}
public void print15(){
lock.lock();
while (number!=3){
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName());
}
number=1;
condition1.signal();
lock.unlock();
}
}