1、join
一个线程在执行过程中,有一个方法或者一行代码需要新开的线程执行完成后才继续往下执行时,用join,比如:
Thread o1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i =0;i<1000;i++){
System.out.println("A"+i);
}
}
});
Thread o2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("111");
o1.start();
try {
o1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("o1执行完毕");
for(int i =0;i<1000;i++){
System.out.println("B"+i);
}
}
});
o2.start()
在o2线程中,用了o1.join(),所以,线程执行到这里,需要等待o1执行完成之后,才继续往下走
2、wait/notifyAll
wait必须在同步代码块中使用,也就是synchronized(s){},s.wait(),s.notify(), 当wait()时,会释放当前获得的锁,且让出cpu,直到notify()调用之后,才重新尝试获取锁,执行后面代码。比如:
Student s = new Student();
Thread t6 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (s){
for(int i =1;i<11;i++){
System.out.println(Thread.currentThread().getName()+"正在下载图片:"+i+"0%");
}
System.out.println("图片下载完毕");
s.setAge(14);
s.notify();
}
}
});
Thread t7 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (s){
System.out.println("等待图片下载完成");
s.setName("zhangsan");
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("显示图片");
}
}
});
注意:多线程中,尽量使用while 而不是使用if,原因是当wait()的线程,被notify唤醒之后,如果使用if,是不会再次进行if判断,而是直接执行wait()后面的方法,如果使用while,会先进行判断,再执行。下面是一个例子:
public class ListAddRemove {
public static final Object object = new Object();
public static List<Integer> list = new ArrayList<>();
public static void main(String[] args) {
AddThread addThread = new AddThread();
RemoveThread removeThread = new RemoveThread();
Thread t1 = new Thread(addThread);
Thread t2 = new Thread(removeThread);
Thread t3 = new Thread(removeThread);
t2.start();
t3.start();
t1.start();
}
}
class RemoveThread implements Runnable {
private final Object object = ListAddRemove.object;
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "删除线程开始执行");
if (ListAddRemove.list.size() == 0) {//此处如果改成while,则不会报错
try {
System.out.println(Thread.currentThread().getName() + "删除线程发现lit为空,调用wait()方法");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ListAddRemove.list.remove(0);
System.out.println(Thread.currentThread().getName()+"删除完成");
}
}
}
class AddThread implements Runnable {
private final Object object = ListAddRemove.object;
@Override
public void run() {
synchronized (object) {
System.out.println("add线程执行");
ListAddRemove.list.add(new Random().nextInt());
System.out.println("add线程数据添加完成,调用notifyAll()");
object.notifyAll();
System.out.println("add线程调用notifyAll()后");
}
}
}
上述代码是一个典型的生产者消费者模式,一个生产者向数组添加一个元素,两个消费者从数据中删除元素,生产者添加元素后,通知两个消费者删除元素,如果使用if (ListAddRemove.list.size() == 0),则会出现角标越界异常,因为如果使用if,消费者被唤醒之后会直接执行wait()后面的代码,而如果使用while,则会先对while进行判断,再执行后面的代码。所以上述代码将if (ListAddRemove.list.size() == 0) 改为while (ListAddRemove.list.size() == 0)之后,就不会出现角标越界异常,删除线程就会继续等待。
当方法wait()被执行后,锁自动被释放,但执行完notify()方法后,锁不会自动释放。必须执行完notify()方法所在的synchronized代码块后才释放。
3、CountDownLatch(计数器)
CountDownLatch的构造方法传入一个int值,指定计数器的个数。计数器的作用类似于join,当调用计数器的await()方法之后,当前线程阻塞,直到计数器的个数为0后(调用countDown()方法,计数器的个数会减1),才继续执行下面的代码。示例如下:
/**
* 计数器
* 构造方法传入一个参数,指定计数器的计数个数
*/
public class CountDownLatchDemo implements Runnable {
private static CountDownLatch end = new CountDownLatch(10);
@Override
public void run() {
try{
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"已完成检查");
//通知线程已经完成任务,计数器可以减1了
end.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
CountDownLatchDemo demo = new CountDownLatchDemo();
ExecutorService service = Executors.newFixedThreadPool(10);
for(int i =0;i<10;i++){
service.submit(demo);
}
end.await();
System.out.println("全部检查完成");
service.shutdown();
}
}
4、Semaphore (信号量)
信号量semaphore ,用于指定某几个线程可以同时访问某一个临界资源,下面的案例是线程池维护了30个线程,但是只有5个线程可以同时访问临界资源,如果将参数改为1,则可以起到互斥锁的作用,第二个boolean参数表示是否是线程公平的,所谓的公平锁就是先等待的线程先获得锁。
/**
* 信号量semaphore
* acquire() 类似lock()
* tryAcquire(long timeout,TmeUnit unit) 类似tryLock()
* tryAcquire()
* release() 释放锁,类似unlock()
*/
public class SemaphoreThread implements Runnable {
private Semaphore semaphore = new Semaphore(5,true);
@Override
public void run() {
try{
semaphore.acquire();
Thread.sleep(1000);
System.out.println("done");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SemaphoreThread semaphoreThread = new SemaphoreThread();
ExecutorService executorService = Executors.newFixedThreadPool(30);
for(int i =0;i<30;i++){
executorService.submit(semaphoreThread);
}
}
}
Semaphore可以用来做接口限流(延伸使用Semaphore和RateLimiter做接口限流)
5、Interrupt 线程中断
线程中断可以使用Thread.stop()方法,但是该方法会强制中断当前线程,如果当前线程正在写入数据,强制中断后,就会破坏数据,JDK目前已经废弃了该方法,建议使用Thread.Interrupt()方法来中断线程。该方法执行后,会通知当前线程,你要中断了,当前线程收到通知后,不会立即停止,会将代码走完,走完之后,可以使用Thread.isInterrupt()方法来判断当前线程是否已经收到中断通知,如果返回true,则表示已经收到中断通知,下面是示例:
public class InterruptThread implements Runnable {
@Override
public void run() {
while (true){
System.out.println("1");
if(Thread.currentThread().isInterrupted()){
System.out.println("线程收到停止通知,线程停止");
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
InterruptThread i = new InterruptThread();
Thread t = new Thread(i);
t.start();
t.interrupt();
}
}
注意:如果线程在sleep的时候收到了中断通知,那么sleep会抛出异常,同时清除中断状态。所以在catch里,要再进行interrupt操作。
暂时只想到这么多,后面有想到的就补充上