上次写了一些一些线程相关的基础知识,这次稍微深入些。
一、 线程之间的通信
1.为什么要通信?
1. 多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
2.当然如果我们没有使用线程通信来使用多线程共同操作同一份数据的话,虽然可以实现,但是在很大程度会造成多线程之间对同一共享变量的争夺,那样的话势必为造成很多错误和损失!
所以,才引出了线程之间的通信,多线程之间的通信能够避免对同一共享变量的争夺。
2.什么是通信
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作, 避免对同一共享变量的争夺。
继而引出了等待唤醒机制:(wait()、notify())
在一个线程进行了规定操作后,就进入等待状态(wait), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify);
3.wait和notify方法:
java.lang.object类提供类两类用于操作线程通信的方法。
多个线程只有使用相同的一个对象的时候,多线程之间才有互斥效果。 这个用来做互斥的对象称为:同步监听对象/同步锁/互斥锁
但Lock没有同步监听对象,则没有自动释放和获取锁的概念,此时我们用 Lock的Condition接口。
- 用Lock机制 取代 synchronized修饰的代码块 / 方法。
- 用Condition接口对象的 .await() / .signal() / .signalAll()方法取代Object类中 .wait() / .notify() / .notifyAll().
condition接口详解:
在java中,对于任意一个java对象,它都拥有一组定义在java.lang.Object上监视器方法,包括 wait(),wait(long timeout),notify(),notifyAll(),
这些方法配合synchronized关键字一起使用可以实现等待/通知模式。
同样,Condition接口也提供了类似Object监视器的方法,通过与Lock配合来实现等待/通知模式。
为了更好的了解Condition的特性,我们来对比一下两者的使用方式以及功能特性:
对比项 | Object监视器 | Condition |
前置条件 | 获取对象的锁 | 调用Lock.lock获取锁,调用Lock.newCondition获取Condition对象 |
调用方式 | 直接调用,比如 object.notify() |
直接调用, 比如condition.await() |
等待队列的个数 | 一个 | 多个 |
当前线程释放锁进入等待状态 | 支持 | 支持 |
当前线程释放锁进入等待状态,在等待状态中不断响中断 | 不支持 | 支持 |
当前线程释放锁并进入超时等待状态 | 支持 | 支持 |
当前线程释放锁并进入等待状态直到将来的某个时间 | 不支持 | 支持 |
唤醒等待队列中的一个线程 | 支持 | 支持 |
唤醒等待队列中的全部线程 | 支持 | 支持 |
二、死锁
JVM不检测也不试图避免这种情况。所以程序员必须保证不导致死锁。
三、线程的操作
线程休眠:让执行的线程暂停一段时间,进入计时等待状态
class Join extends Thread{
@Override
public void run() {
for(int i=-0; i<0 ;i++){
System.out.println("join" + i);
}
}
}
//联合线程
public class JoinDemo{
public static void main(String[] args) {
System.out.println("bdgin");
Join joinThread = new Join();
for(int i=0; i<300; i++){
System.out.println("main:" + i);
if(i==20){ //启动该线程
joinThread.start();
}
if(i==60){
joinThread.join(); //强制运行该线程
}
}
}
}
后台线程(守护线程):
四、线程组合定时器
TimerTask类表示定时器执行的某一项任务。
schedule(TimerTask task,long delay,long period);
schedule (TimerTask task,long delay):
Thread(ThreadGroup group, String name);
五、线程池
概述:
- 程序创建一个新的线程成本较高,因为它涉及到要与操作系统进行交互。频繁的线程创建和销毁,大大消耗时间和降低系统的效率。
- 线程池的使用解决了这个问题,它使得多个线程能够一次创建完,放在线程池中,执行完后并不会被销毁,而是再次回到线程池中变成空闲状态,等待下一个对象来使用。并且即拿即用,不用每次都创建,大大提高了线程的复用性,提高系统效率。
- JDK1.5开始,Java有了内置的线程池。Executors工厂类
内置线程池:
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:
public static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
参数: nThreads – 池中的线程数
返回: 新创建的线程池
public static ExecutorService newSingleThreadExecutor()
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程
Executors.newCachedThreadPool()
创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。
示例:
ExecutorService pool = Executors.newFixedThreadPool(2);
//可以执行Runnable对象
//或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();
线程池和线程组的区别:
线程组:
线程组存在的意义,首要原因是安全。
java默认创建的线程都是属于系统线程组,而同一个线程组的线程是可以相互修改对方的数据的。
但如果在不同的线程组中,那么就不能“跨线程组”修改数据,可以从一定程度上保证数据安全.
线程池:
线程池存在的意义,首要作用是效率。
线程的创建和结束都需要耗费一定的系统时间(特别是创建),不停创建和删除线程会浪费大量的时间。所以,在创建出一条线程并使其在执行完任务后不结束,而是使其进入休眠状态,在需要用时再唤醒,那么 就可以节省一定的时间。
如果这样的线程比较多,那么就可以使用线程池来进行管理。保证效率。
线程组和线程池共有的特点:
1、都是管理一定数量的线程
2、都可以对线程进行控制—包括休眠,唤醒,结束,创建,中断(暂停)–但并不一定包含全部这些操作。
感谢:
https://blog.csdn.net/qq578473688/article/details/54561907
https://blog.csdn.net/hxhaaj/article/details/81087954