1.线程状态转换
2.使用线程
有三种使用线程的方法:
- 实现 Runnable 接口
- 实现 Callable 接口
- 继承 Thread 类
实现 Runnable 接口例子
public class MyRunnable implements Runnable {
public void run() {
// ...
}
}
public static void main(String[] args) {
MyRunnable instance = new MyRunnable();
Thread thread = new Thread(instance);
thread.start();
}
实现 Callable 接口例子
public class MyThread extends Thread {
public void run() {
// ...
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
继承 Thread 类例子
public class MyThread extends Thread {
public void run() {
// ...
}
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
通常而言,实现接口会比继承Thread好。因为
- Java不支持多重继承。继承了Thread类就无法继承其它类。但是可以实现多个接口
- 类可能只要求可执行就行,继承整个Thread类开销过大
3.互斥同步
Java提供了两种锁机制来控制多个线程对共享资源的互斥访问。第一个是JVM实现的synchronize,另一个是 JDK 实现的 ReentrantLock。
synchronize可以作用于代码块,方法,静态方法,类
public synchronized void func () {
// ...
}
ReentrantLock
public class LockExample {
private Lock lock = new ReentrantLock();
public void func() {
lock.lock();
try {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
} finally {
lock.unlock(); // 确保释放锁,从而避免发生死锁。
}
}
}
4.比较
1.锁的实现
synchronized 是JVM实现的,ReentrantLock 是JDK实现的。
2.性能
新版Java对synchronized进行了很多优化,例如自旋锁等,两者差异不大
3.暂停可中断
当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
ReetrantLock可中断,而synchronized不行
4.公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁
synchronized中的锁是非公平的,ReentrantLock 默认情况也是非公平的,但也可以是公平的。
5.锁绑定多个条件
一个ReentrantLock 可以同时绑定多个Condition对象
5.使用选择
除非需要使用ReentrantLock的高级功能,否则优先使用synchronized。因为synchronized是JVM实现的一种锁机制,JVM原生地支持它,而ReentrantLock不是所有的JDK版本都支持。并且使用synchronized不用担心没有释放锁而导致死锁的问题,因为JVM会确保锁的释放。
6.线程之间的协作
- jion
在线程中调用另一个线程的join()方法,会将当前线程挂起,直到目标线程结束
- wait() notify() notifyAll()
调用wait() 使得线程在等待某个条件满足时,线程在等待时会被挂起,当其他线程的运行使得某个条件满足时,其他线程会调用notify() 或者 notifyAll() 来唤醒被挂起的线程
使用wait挂起期间,线程会释放锁。这是因为,如果没有释放锁,那其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行notify,notifyAll来唤醒挂起的线程,造成死锁。
wait和sleep的区别:
1.wait是Object的方法,而sleep是Thread的静态方法
2.wait会释放锁,sleep不会
- await signal signalAll
java.util.concurrent 类库中提供了Condition类来实现线程之间的协调。可以在Condition上调用await方法使线程等待,其他线程调用signal或signalAll方法唤醒等待的线程
相比于wait这种等待方式,await可以指定等待的条件,更加灵活
使用Lock来获取一个Condition对象。
7.CountDownLatch
用来控制一个线程等待多个线程。
维护了一个计数器cnt,每次调用countDown方法会让计数器的值减1,减到0的时候,那些因为调用await方法而在等待的线程就会被唤醒。
8.CyclicBarrier
用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。
和CountdownLatch相似,都是通过维护计数器来实现的。线程执行await方法之后计数器会减1,并进行等待,直到计数器为0,所有调用await方法而在等待的线程才能继续执行。
CyclicBarrier和CountdownLatch的一个区别是,CycliBarrier的计数器通过reset方法可以循环使用,所以它才叫做循环屏障。
9.Semaphore
Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。