Java线程2 – 线程池和线程组

Java线程2 – 线程池和线程组

上次写了一些一些线程相关的基础知识,这次稍微深入些。

一、 线程之间的通信

1.为什么要通信?

1. 多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。

2.当然如果我们没有使用线程通信来使用多线程共同操作同一份数据的话,虽然可以实现,但是在很大程度会造成多线程之间对同一共享变量的争夺,那样的话势必为造成很多错误和损失!

所以,才引出了线程之间的通信,多线程之间的通信能够避免对同一共享变量的争夺。

2.什么是通信

多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作, 避免对同一共享变量的争夺。

继而引出了等待唤醒机制:(wait()、notify())

在一个线程进行了规定操作后,就进入等待状态(wait), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify);

3.wait和notify方法:

java.lang.object类提供类两类用于操作线程通信的方法。
多个线程只有使用相同的一个对象的时候,多线程之间才有互斥效果。 这个用来做互斥的对象称为:同步监听对象/同步锁/互斥锁

wait : 执行该方法的线程对象释放同步锁JVM把该线程存放到等待池中,等待其他的线程唤醒该线程。
notify : 执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待
notifyAll : 执行该方法的线程唤醒在等待池中等待的所有的线程把线程转到锁池中等待.
注意 : 上述方法只能被同步监听锁对象来调用,否则报错IllegalMonitorState Exception.
实际开发建议使用Lock机制。
但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()
等待队列的个数 一个 多个
当前线程释放锁进入等待状态 支持 支持
当前线程释放锁进入等待状态,在等待状态中不断响中断 不支持 支持
当前线程释放锁并进入超时等待状态 支持 支持
当前线程释放锁并进入等待状态直到将来的某个时间 不支持 支持
唤醒等待队列中的一个线程 支持 支持
唤醒等待队列中的全部线程 支持 支持

二、死锁

多线程通信的时候很容易造成死锁死锁无法解决,只能避免。
当A线程等待由B线程持有的锁,而B线程正在等待A线程持有的锁时,则发生死锁现象。
JVM不检测也不试图避免这种情况。所以程序员必须保证不导致死锁。
避免死锁:当多个线程都要访问共享的资源ABC时,保证每一个线程都按照相同的顺序去访问他们比如都先访问A,接着B,最后C。

三、线程的操作

线程休眠:让执行的线程暂停一段时间,进入计时等待状态

方法: static void sleep(long millis);
调用sleep后,当前线程放弃CPU,在指定时间段之内, sleep所在线程不会获得执行的机会。此状态下的线程不会释放同步锁/同步监听器
联合线程:
线程的join方法表示一个线程等待另一个线程完成后才执行。join方法被调用之后,线程对象处于阻塞状态。
有人也把这种方式称为联合线程,就说把当程和当前线程所在的线程联合成一个线程.
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(); //强制运行该线程
            }
        }
    }
}

 

后台线程(守护线程):

在后台运行的线程,其目的是为其他线程提供服务,也称为”守护线程”。JVM的垃圾回收线程就是典型的后台线程。
特点:若所有的前台线程都死亡,后台线程自动死亡,前台线程没有结束,后台线程是不会结束的。
测试线程对象是否为后台线程 : 使用thread.isDaemon()。
新创建的线程默认是前台线程,可以通过 .setDaenon(true) 方法将线程设置为后台线程,并且当且仅当后台线程创建的新线程时,新线程是后台线程。
设置后台线程: thread.setDaemon(true),该方法必须在start方法调用前,否则出现legalThreadStateException异常.

四、线程组合定时器

在JDK的java.util包中提供了Timer类,可以定时执行特定的任务
TimerTask类表示定时器执行的某一项任务。
常用方法 :
schedule(TimerTask task,long delay,long period);
schedule (TimerTask task,long delay):
 
java.lang.ThreadGroup类表示线程组,可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
用户在创建线程对象时, 可以通过构造器指定其所属的线程组.
Thread(ThreadGroup group, String name);
 
如果A线程创建了B线程,如果没有设置B线程的分组,那么B线程加入到A线程的线程组。
一旦线程加入某个线程组,该线程就一直存在于该线程组中直到线程死亡,不能在中途修改线程的分组。
当Java程序运行时,JVM会创建名为main的线程组。在默认情况下,所有的线程都该改线程组下。
注意:线程组也可以包含其他线程组。

五、线程池

概述:
  • 程序创建一个新的线程成本较高,因为它涉及到要与操作系统进行交互。频繁的线程创建和销毁,大大消耗时间和降低系统的效率。
  • 线程池的使用解决了这个问题,它使得多个线程能够一次创建完,放在线程池中,执行完后并不会被销毁,而是再次回到线程池中变成空闲状态,等待下一个对象来使用。并且即拿即用,不用每次都创建,大大提高了线程的复用性,提高系统效率。
  • 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

Comments

No comments yet. Why don’t you start the discussion?

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注