一、join()方法
join()方法:线程合并
暂停当前线程执行,等待子线程执行,也称之为线程合并 ,join方法是将并行执行的线程合并成串行执行
例:在线程ta中调用tb.join,会暂停ta的执行,先让tb执行完毕,ta才会执行。
• t.join():允许t线程在当前线程之前执行,待t线程执行结束当前线程再执行。
• t.join(final long millis)(时间单位:毫秒)允许t线程在当前线程之前执行,且最长时间我的millis毫秒之后,当前线程才能执行。
• t.join(long millis, int nanos)和t.join(final long millis)一样只不过可以提供纳米级的精度
方法特点:
- join方法是thread类中的方法,会抛出InterruptedException中断异常。
- 当线程ta中tb.join,tb会执行,ta线程会进入WAITING或TIMD_WAITING状态
- 当线程ta中tb.join,则ta线程会释放当前持有的锁,join方法的实现是通过wait/notify线程通信方式来实现的,wait方法的使用会释放锁
代码实现
public class JoinDemo{
//三个线程顺序执行
public static void main(String[] args) {
Work t1=new Work(null,"A");
Work t2=new Work(t1,"B");
Work t3 =new Work(t2,"C");
t1.start();
t2.start();
t3.start();
}
static class Work extends Thread{
private Thread Thread;
private String threadName;
public Work(Thread Thread,String threadName) {
this.Thread = Thread;
this.threadName=threadName;
}
@Override
public void run() {
if (Thread != null) {
try {
Thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(threadName);
}
}
}
运行结果
二、Synchronized锁、使用线程通信wait()、notifyAll()
Synchronized
关键字可以修饰方法或者代码块,确保多个线程在同一时刻,只能有一个线程处理方法或者是同步块,保证线程对访问变量的可见性,有序性,原子性
Synchronized特点
Synchronized修饰的方法或者代码块相当于并发中的临界区,在同一时刻JVM只允许一个线程进入执行。synchronized通过锁机制来达到同一时刻只允许一个线程进入执行的效果,在并发编程中,Synchronized锁机制可以做到线程并发的原子性,有序性,可见性
wait():
调用wait方法的线程,当前持有锁的该线程等待,直至该对象的另一个持锁线程调用notify/notifyAll操作
wait(long timeOut)、wait(long timeOut ,int nanos)
notify():
通知持有该对象锁的所有线程中的随意某一个线程被唤醒
notifyAll():
通知持有该对象锁的所有线程被同时唤醒
线程通信的方法:wait\notify\notifyAll操作都是属于Object类提供的方法,即所有的对象都具有该方法。
代码实现
public class ABCThread extends Thread{
String [] ABC=new String[]{"A","B","C"};
//当前线程编号
private Integer index;
//线程间通信对象
private Opt opt;
//统计次数
int number=0;
public ABCThread(Integer index, Opt opt) {
this.index = index;
this.opt = opt;
}
@Override
public void run() {
while (true) {
//通信锁的对象
synchronized (opt) {
//循环执行 判断是否是当前线程执行(当前线程编号和opt记录的线程编号一致则是要执行的线程)
while (opt.getNextIndex() != index) {
try {
//不是当前线程继续等待
opt.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(ABC[index] + " ");
//统计次数加一
number++;
//设置下一个执行的线程编号A(0)、B(1)、C(2);
opt.setNextIndex((index+1)%3);
//通知其他两个线程
opt.notifyAll();
//当前执行到10次,执行结束
if (number>9) break;
}
}
}
public static void main(String[] args) {
Opt opt=new Opt();
//设置的第一个执行线程编号
opt.setNextIndex(0);
new ABCThread(0,opt).start();//A
new ABCThread(1,opt).start();;//B
new ABCThread(2,opt).start();//C
}
}
运行结果
三、阻塞队列 BlockingQueue
BlockingQueue接口
BlockKingQueue是并发容器的一种,在J.U.C包路径下。BlockingQueue提供了线程安全的队列访问方式:当阻塞队列进行插入数据时,如果队列已满,线程将会阻塞等待直到队列非满;从阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空。
BlockingQueue是线程安全的版本实现,是一个阻塞队列实现,该接口提供两个新的操作put/take
put():将元素加入到队列尾部
take():移除队列头部元素
接口特点:
• BlockingQueue不接受null值,进行add/put/offer如果是一个null值,则会抛出NullPointerException
• BlockingQueue可以是指定容量的,如果超过了给定的容量就会阻塞
• BlockingQueue实现的是线程安全的,实现类内部可以使用内部锁或者是其他形式的并发控制来自动达到线程安全的目的
三种实现类
ArrayBlockingQueue:基于数组实现的有界阻塞队列
LinkedBlockingQueue:基于链表实现的无界阻塞队列
SynchronousQueue:同步阻塞队列
使用LinkedBlockingQueue代码实现:
public class BlockQueueDemo {
public static void main(String[] args) {
//创建LinkedBlockingQueue对象
BlockingQueue<Thread> blockingQueue=new LinkedBlockingQueue<>();
//创建三个线程
Thread thread1=new Thread(new Demo(),"A");
Thread thread2=new Thread(new Demo(),"B");
Thread thread3=new Thread(new Demo(),"C");
blockingQueue.add(thread1);
blockingQueue.add(thread2);
blockingQueue.add(thread3);
for (int i = 0; i <3; i++) {
Thread thread=null;
try {
thread=blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.start();
//检测线程是否还活着
while (thread.isAlive());
}
}
static class Demo implements Runnable{
@Override
public void run() {
System.out.print(Thread.currentThread().getName());
}
}
}
运行结果
打印十次ABC代码实现
public class BlockQueueDemo2 {
public static void main(String[] args) {
//创建ArrayBlockingQueue对象有界阻塞队列
final BlockingQueue blockingQueue=new ArrayBlockingQueue(30);
for (int i = 0; i < 10; i++) {
try {
blockingQueue.put("A");
blockingQueue.put("B");
blockingQueue.put("C");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
new Thread(new Runnable() {
@Override
public void run() {
//新建判断标志
boolean flag=true;
while (flag){
if (blockingQueue.size()!=0){
try {
System.out.print(blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
flag=false;
}
}
}
}).start();
}
}
运行结果
四、线程池
什么是线程池
线程池就是事先创建若干个可执行的线程放入一个池中(容器),有任务需要执行时就从池中获取空闲的线程,任务执行完成后将线程放回池,而不用关闭线程,可以减少创建和销毁线程对象的开销
避免了频繁的创建和销毁线程,让创建的线程达到复用的目的,线程池的内部维护一部分活跃的线程,如果有需要就从线程中取线程使用,用完归还到线程池,线程池对线程的数量是有一定的限制。
线程池的本质是对线程资源的复用
线程池的优势
• 降低资源的消耗:通过复用已经创建的线程降低线程创建和销毁的消耗
• 提高响应速度:当任务到达时,任务直接从线程池中获取到一个线程就能立即执行
• 提高线程的可管理性:线程资源的管理,提高系统的稳定性
• 线程池可以进行统一的分配、调度和监控等功能
代码实现
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建三个线程并重写run方法
final Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
System.out.print("A");
}
});
final Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
System.out.print("B");
try {
thread1.join(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
final Thread thread3=new Thread(new Runnable() {
@Override
public void run() {
System.out.print("C");
try {
thread2.join(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//使用 单个任务的线程池来实现。保证线程的依次执行
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(thread1);
executor.submit(thread2);
executor.submit(thread3);
executor.shutdown();
}
}