1. 线程方法
1.1 方法概览
类.方法 | 简介 |
---|---|
Thread.sleep() | 线程休眠,不释放锁 |
Thread.join() | 等待其它线程执行完毕 |
Thread.yield() | 放弃已经获取到的CPU资源 |
Thread.currentThread() | 获取当前线程的引用 |
Thread.start()/Thread.run() | 启动线程和线程执行内容 |
Thread.interrupt() | 中断线程 |
Thread.stop()/Thread.suspend()/Thread.resume() | 已废弃 |
Object.wait()/Object.notify()/Object.notifyAll() | 线程等待和唤醒 |
1.2 wait()/notify()/notifyAll()
- 阻塞阶段
执行了wait方法意味着当前线程进入阻塞阶段,直到发生以下四种情况之一,该线程才会被唤醒:- 另外一个线程调用这个对象的notify方法且刚好被唤醒的是本线程;
- 另外一个线程调用这个对象的notifyAll方法;
- 过了wait规定的超时时间,如果传入0,就是永久等待;
- 线程自身调用了interrupt方法,wait方法因为响应中断被唤醒,进而抛出异常;
- 唤醒阶段
- notify方法会唤醒单个等待线程,如果有多个等待线程,则随机选取一个;
- notifyAll方法会唤醒所有等待线程;
1.3 wait()/notify()/notifyAll()方法详解
下面展示wait和notify的基本用法:
public class WaitNotify {
private static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "重新获取到锁");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
object.notify();
System.out.println(Thread.currentThread().getName() + "调用了notify()");
}
}
});
t1.start();
Thread.sleep(200);
t2.start();
}
}
Thread-0开始执行
Thread-1调用了notify()
Thread-0重新获取到锁
可以看出,t1线程先启动并执行了wait方法,释放了锁挂起,然后t2线程启动,获取到了锁并执行了notify重新唤醒了t1线程,t1重新获取到锁,在挂起的位置继续执行。
下面是wait和notifyAll的代码演示:
public class WaitNotifyAll implements Runnable {
private static Object object = new Object();
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "获取到锁");
try {
object.wait();
System.out.println(Thread.currentThread().getName() + "重新获取到锁");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
WaitNotifyAll waitNotifyAll = new WaitNotifyAll();
Thread t1 = new Thread(waitNotifyAll);
Thread t2 = new Thread(waitNotifyAll);
t1.start();
t2.start();
Thread.sleep(200);
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
object.notifyAll();
// object.notify();
System.out.println(Thread.currentThread().getName() + "拿到了锁并执行了notifyAll");
}
}
});
t3.start();
}
}
Thread-0获取到锁
Thread-1获取到锁
Thread-2拿到了锁并执行了notifyAll
Thread-1重新获取到锁
Thread-0重新获取到锁
首先t1拿到了锁并执行了wait方法挂起t1线程,然后t2拿到了锁并执行了wait方法挂起t2线程,然后t3拿到了锁并执行了notifyAll唤醒t1和t2线程,t2线程先重新获取到了锁,待执行完毕之后释放了锁,t2重新获取到了锁。
如果改用notify方法则随机唤醒t1和t2线程其中的一个,如果t1被唤醒,则t2陷入了无尽的等待,整个进程永远不会结束。
下面展示wait方法只能释放调用wait方法的对象的锁,不能释放其他对象的锁:
public class ReleaseOwnMonitor {
private static final Object lockA = new Object();
private static final Object lockB = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "获取到lockA");
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "获取到lockB");
try {
System.out.println(Thread.currentThread().getName() + "释放了lockA");
lockA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "获取到lockA");
System.out.println(Thread.currentThread().getName() + "正在尝试获取lockB...");
synchronized (lockB) {
//看看t2能不能获取到lockB
System.out.println(Thread.currentThread().getName() + "获取到lockB");
try {
lockA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
t1.start();
Thread.sleep(500);
t2.start();
}
}
Thread-0获取到lockA
Thread-0获取到lockB
Thread-0释放了lockA
Thread-1获取到lockA
Thread-1正在尝试获取lockB...
结果表明,t2一直在尝试获取lockB,但是由于在t1中只有lockA执行了wait方法,释放的也只是lockA的锁,t1仍然持有lockB的锁,所以t2获取不到lockB的锁。
1.4 wait()/notify()/notifyAll()特点和性质
- 用这些方法之前,当前线程必须首先获取到monitor锁
- 如果使用notify方法只会唤醒一个等待线程
- 都属于Object类,所有的类继承与Object,所有的对象都可以调用这些方法
- 类似功能的Condition
- 线程同时持有多个锁,使用wait方法只释放调用wait方法的对象的锁
- 线程执行object.wait后,进入到WAITING状态,而线程被唤醒后,因为是另外一个线程持有相同的锁才能执行object.notify方法进行唤醒,另外一个线程可能还没有执行完,所以当前线程通常没有立即获取到monitor锁,那么当前线程会从WAIT状态进入到BLOCKED状态,抢到锁后会进入到RUNNABLE状态
- 发生异常,可以直接到TERMINATED状态
1.5 sleep()方法详解
sleep可以让线程进入Waiting状态,并且不占用CPU资源,但是不释放锁,直到规定时间后再执行,休眠期间如果被中断,会抛异常并清除中断状态。
public class SleepDontReleaseMonitor implements Runnable {
@Override
public void run() {
syn();
}
private synchronized void syn() {
System.out.println(Thread.currentThread().getName() + "获取锁");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "退出同步代码块");
}
public static void main(String[] args) {
SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
new Thread(sleepDontReleaseMonitor).start();
new Thread(sleepDontReleaseMonitor).start();
}
}
public class SleepDontReleaseLock implements Runnable {
private static final Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获取到了锁");
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "苏醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
new Thread(sleepDontReleaseLock).start();
new Thread(sleepDontReleaseLock).start();
}
}
可以看出,无论是synchronized和lock,执行sleep方法都不释放锁。
1.6 join()方法详解
- 作用:因为新的线程加入了我们,我们要等他执行完再出发
- 用法:main等待t1执行完毕,如下所示:
public class JoinBasic implements Runnable {
@Override
public void run() {
System.out.println("子线程开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程执行结束");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new JoinBasic());
thread.start();
thread.join();
System.out.println("主线程执行结束");
}
}
子线程开始执行
子线程执行结束
主线程执行结束
- join执行期间,是主线程等待子线程,所以我们看看join期间主线程是什么状态:
public class JoinMainState implements Runnable {
Thread mainThread = Thread.currentThread();
@Override
public void run() {
System.out.println("子线程开始执行");
try {
Thread.sleep(1000);
System.out.println(mainThread.getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程执行结束");
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new JoinMainState());
t1.start();
t1.join();
}
}
子线程开始执行
WAITING
子线程执行结束
结果表明,主线程等待子线程join期间,主线程是WAITING状态
- 因为线程执行完毕之后会自动调用notifyAll方法,所以我们可以让主线程等待,然后等子线程执行完后自动唤醒主线程,所以替代方法如下:
public class JoinPrinciple {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "子线程执行完毕");
}
});
thread.start();
System.out.println("等待所有子线程运行完毕");
// thread.join();
//给thread对象上锁,thread对象执行完run方法后会自动调用thread.notifyAll()方法唤醒其他线程,即唤醒主线程
synchronized (thread) {
thread.wait();
}
System.out.println("所有子线程运行完毕");
}
}
等待所有子线程运行完毕
Thread-0子线程执行完毕
所有子线程运行完毕
synchronized (thread) {
thread.wait();
}
上面这段代码也是join方法中的核心方法。
1.7 yield方法详解
- 作用:释放我的CPU时间片,线程状态仍然是Runnable状态,不会释放锁,不会阻塞,即便释放了CPU时间片,下一个可能还是我
- yield和sleep区别:sleep期间,线程调度器不会把这个线程调度起来,而yield期间,立刻又可以被调度起来。
2. 线程属性
2.1 属性概览
属性名称 | 用途 |
---|---|
ID | 标识不同的线程 |
Name | 自定义线程名称 |
isDaemon | 是否是守护线程 |
Priority | 告诉线程调度器,用户希望哪些线程多运行、哪些少运行,共10个等级,默认值是5 |
2.2 ID
从1开始,我们自己创建线程的ID早已不是2,因为JVM在后开启了很多其它子线程。
public class threadId {
public static void main(String[] args) {
Thread thread = new Thread();
System.out.println("子线程:" + thread.getId());
System.out.println("主线程:" + Thread.currentThread().getId());
}
}
子线程:11
主线程:1
2.3 Name
对于没有指定名字的线程,会有默认名称
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
2.2 isDaemon
给用户线程提供服务,没有用户线程就没有守护线程
-
线程类型默认继承自父线程,守护线程创建的线程自动是守护线程,用户线程同理
-
被谁启动,通常,所有守护线程由JVM启动,JVM启动时,会有一个用户线程,main函数
-
不影响JVM退出,所有用户线程都结束,即便有守护线程,JVM也会退出
整体无区别,唯一的区别在于是否影响JVM的退出,守护线程不会影响。
2.2 Priority
10个级别,默认5
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
程序设计不应依赖于优先级,跟操作系统息息相关,不可靠
- 因为不同操作系统不一样
- 优先级会被操作系统改变