Java多线程基础
线程的基本操作
新建线程
使用start()
方法,可以新创建一个线程并让这个线程执行其run()
方法。
Thread t1 = new Thread();
t1.start();
注意直接使用run()
方法也是可以的,但却不能创建一个新线程,而是作为一个普通的方法调用,串行执行run()
中的代码。
Thread t1 = new Thread();
t1.run();
Thread类中有一个非常重要的构造方法:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
它传入一个Runnable接口的实例,在start()方法调用时,新的线程会执行Runnable.run()方法。
终止线程
Thread提供了一个stop()
方法,可以立即将一个线程终止,但是stop()
方法已经被JDK标注为废弃的方法。
为什么stop()
方法不被推荐使用,因为stop()
强行把执行到一半的线程终止,可能会引起数据不一致的问题。
不一致的原理:Thread.stop()
方法在结束线程时,会直接终止线程,并且会释放这个线程所持有的锁,而这些锁恰恰是来维护对象一致性的。如果此时,写线程写入数据正写到一半,并强行被终止,那么对象就会被写入数据了。此时由于锁已经被释放了,此时另一个等待该锁的读线程就会顺理成章的读到了这个不一致的对象。
线程中断
严格来说,线程中断并不会使线程立即退出,而是给线程发送一个通知,告知线程要退出了,而目标线程何时要退出则完全取决于自己。如果是完全无条件退出,那么又会遇到stop()
的数据不一致问题了。
与线程相关的3个方法:
public void Thread.interrupt(); // 中断线程
public boolean Thread.isInterrupted(); // 判断是否被中断
public static boolean Thread.interrupted(); // 判断是否被中断,并清除当前中断状态
下面的代码只设置了中断标记,不会中断线程:
public class Main {
public static void main(String... args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
while (true) {
Thread.yield();
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
}
如果希望在t1在中断后退出,就必须为它增加相应的中断处理代码:
public class Main {
public static void main(String...args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("isInterrupted!");
break;
}
Thread.yield();
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
}
Thread.sleep()
方法会让当前线程休眠若干时间,它会抛出一个InterruptedException
中断异常。InterruptedException
不是运行时异常,也就是说程序必须捕获并且处理它,当线程在sleep()
休眠时,如果被中断这个异常就会发生。
等待(wait)和通知(notify)
wait()
和notify()
是Object
类的,不是Thread
类的,所以任何对象都可以调用这2个方法。
这两个方法的签名如下:
public final void wait() throws InterruptedException
public final native void notify()
当一个对象实例上调用wait()
方法后,当前线程就会在这个对象上等待。比如,线程A中,调用了obj.wait()
方法,那么线程A会停止继续执行,转为等待状态。当其他线程调用了obj.notify()
方法后,线程A就结束等待了。obj对象成了多线程之间的有效通信手段。
wait()和notify()的工作原理:
Object.wait()
方法并不是可以随意调用的,必须包含在对应的synchronized
语句中。
public class SimpleWN {
final static Object object = new Object();
// wait方法会释放目标对象的锁
public static class T1 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ": T1 start!");
try {
System.out.println(System.currentTimeMillis() + ": T1 wait for object ");
object.wait(); // 释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + " : T1 end!");
}
}
}
public static class T2 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ": T2 start! notify one thread");
object.notify();
System.out.println(System.currentTimeMillis() + " : T2 end!");
// T1在得到notify()通知后,需要重新获得object的对象锁
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
}
}
public static void main(String...args) {
Thread t1 = new T1();
Thread t2 = new T2();
t1.start();
t2.start();
}
}
Object.wait()
和Thread.sleep()
的异同点:
- 都可以让线程等待若干时间。
wait()
可以被唤醒。wait()
方法会释放目标对象的锁,而Thread.sleep()
不会释放任何资源
挂起(suspend)和继续执行(resume)线程
等待线程结束(join)和谦让(yield)
一个线程可能非常依赖于另外一个或者多个线程的输出,此时就需要等待线程执行完毕才能继续执行。
2个join()
方法:
public final void join() throws InterruptedException
public final synchronized void join(long millis)
第一个join()
表示无限等待,会一直阻塞线程,直到目标线程执行完毕。
第二个join()
给出了最大等待时间,如果超过时间,线程会继续执行。
public class JoinMain {
public volatile static int i = 0;
public static class AddThread extends Thread {
@Override
public void run() {
for (i = 0; i < 10000000; i++);
}
}
public static void main(String...args) throws InterruptedException {
AddThread at = new AddThread();
at.start();
at.join(); // 表示主线程愿意等待AddThread执行完毕
System.out.println(i);
}
}
join()
的本质是让调用线程wait()
在当前线程对象实例上。
Thread.yield()
方法定义:
public static native void yield();
这是Thread
的一个静态方法,它会使当前线程让出CPU。让出CPU不意味着线程不执行了。当前线程在让出CPU之后,还会进行CPU资源的竞争,但是能否分配到CPU就不一定了。
JDK并发包
重入锁:ReentrantLock
下列是一段简单的重入锁使用例子,说明了使用`ReentrantLock的时候要进行手动加锁和释放锁:
import java.util.concurrent.locks.ReentrantLock;
public class ReenterLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
for (int j = 0; j < 10000000; j++) {
lock.lock();
try {
i++;
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLock tl = new ReenterLock();
Thread t1 = new Thread(tl);
Thread t2 = new Thread(tl);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
可重入锁是可以反复进入的:
lock.lock();
lock.lock();
try {
i++;
} finally {
lock.unlock();
lock.unlock();
}
这个情况下一个线程可以连续获得同一把锁,如果不允许这么操作,那么同一线程在第二次获得锁时,将会和自己产生死锁。如果一个线程多次获得锁,在释放锁时也必须释放相同的次数。
重入锁的搭档:Condition条件
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ReenterLockCondition implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
@Override
public void run() {
try {
lock.lock();
condition.await();
System.out.println("Thread is going on");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLockCondition tl = new ReenterLockCondition();
Thread t1 = new Thread(tl);
t1.start();
Thread.sleep(2000);
lock.lock();
condition.signal();
lock.unlock();
}
}
允许多个线程同时访问:信号量(Semaphore)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemapDemo implements Runnable {
final Semaphore semaphore = new Semaphore(5);
@Override
public void run() {
try {
semaphore.acquire();
// 模拟耗时操作
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId() + " : done!");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(20);
final SemapDemo demo = new SemapDemo();
for (int i = 0; i < 20; i++) {
exec.submit(demo);
}
}
}
ReadWriteLock 读写锁
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
private int value;
public Object handleRead(Lock lock) throws InterruptedException {
try {
lock.lock();
Thread.sleep(1000);
return value;
} finally {
lock.unlock();
}
}
public void handleWrite(Lock lock, int index) throws InterruptedException {
try {
lock.lock();
Thread.sleep(1000);
value = index;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final ReadWriteLockDemo demo = new ReadWriteLockDemo();
Runnable readRunnable = new Runnable() {
@Override
public void run() {
try {
demo.handleRead(readLock);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable writeRunnable = new Runnable() {
@Override
public void run() {
try {
demo.handleWrite(writeLock, new Random().nextInt());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 18; i++) {
new Thread(readRunnable).start();
}
for (int i = 0; i < 20; i++) {
new Thread(writeRunnable).start();
}
}
}
CountDownLatch
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemo implements Runnable {
static final CountDownLatch end = new CountDownLatch(10);
static final CountDownLatchDemo demo = new CountDownLatchDemo();
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(10) * 1000);
System.out.println("check complete");
end.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
exec.submit(demo);
}
end.await();
System.out.println("Fire!");
exec.shutdown();
}
}
CyclicBarrier
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static class Soldier implements Runnable {
private String soldier;
private final CyclicBarrier cyclic;
Soldier(CyclicBarrier cyclic, String soldierName) {
this.cyclic = cyclic;
this.soldier = soldierName;
}
@Override
public void run() {
try {
cyclic.await();
doWork();
cyclic.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
void doWork() {
try {
Thread.sleep(Math.abs(new Random().nextInt() % 100000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(soldier + ":任务完成");
}
}
public static class BarrierRun implements Runnable {
boolean flag;
int N;
public BarrierRun(boolean flag, int N) {
this.flag = flag;
this.N = N;
}
@Override
public void run() {
if (flag) {
System.out.println("司令: [士兵" + N + "个, 任务完成!]");
} else {
System.out.println("司令: [士兵" + N + "个, 集合完毕!]");
flag = true;
}
}
}
public static void main(String[] args) {
final int N = 10;
Thread[] allSoldier = new Thread[N];
boolean flag = false;
CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
System.out.println("集合队伍!");
for (int i = 0; i < N; i++) {
System.out.println("士兵" + i + "报道!");
allSoldier[i] = new Thread(new Soldier(cyclic, "士兵" + i));
allSoldier[i].start();
}
}
}