java多线程基础与线程池

java多线程基础与线程池

1.进程与线程

【进程】

1.进程一般由程序,数据集合和进程控制快三部分组成。

(1)程序用于描述进程要完成的功能,是控制进程执行的指令集;

(2)数据集合是程序在执行时所需要的数据和工作区;

(3)程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志

2.进程具有的特征:

动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;

并发性:任何进程都可以同其他进行一起并发执行;

独立性:进程是系统进行资源分配和调度的一个独立单位;

结构性:进程由程序,数据和进程控制块三部分组成.

【线程】

1.由来:

早期的操作系统中,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。而进程之间切换的开销很大,导致了线程了产生。

2.什么是线程?

**线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。**一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成。

【进程与线程的区别】

1. 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位(进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程));

2.一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线

3.进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;

4.调度和切换:线程上下文切换比进程上下文切换要快得多

5.线程和进程都是一种抽象的概念,线程是一种比进程还小的抽象,线程和进程都可用于实现并发。在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位,它相当于一个进程里只有一个线程,进程本身就是线程。所以线程有时被称为轻量级进程。后来,随着计算机的发展,对多个任务之间上下文切换的效率要求越来越高,就抽象出一个更小的概念-线程,一般一个进程会有多个(也可以是一个)线程。

6.不同进程之间也可以通信,不过代价比较大。现在一般叫单线程与多线程,都是指在一个进程内的单和多

【任务调度】

大部分操作系统的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态,等待下一个属于它的时间片的到来。这样每个任务都能得到执行,由于CPU的执行效率非常高,时间片非常短,在各个任务之间快速地切换,给人的感觉就是多个任务在“同时进行”,这也就是我们所说的并发

【为何不使用多进程而是使用多线程?】

线程廉价,线程启动比较快,退出比较快,对系统资源的冲击也比较小。而且线程彼此分享了大部分核心对象(File Handle)的拥有权如果使用多重进程,但是不可预期,且测试困难。

2.线程的创建与启动

Java中启用多线程有两种方式:①继承Thread类;②实现Runnable接口

2.1 继承Thread类

/**
 * Dog类继承Thread类
 */
public class Dog extends Thread{
    /**
     * 覆写run()方法,定义该线程需要执行的代码
     */
    @Override
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println("Dog线程:"+Thread.currentThread().getName()+":"+i);
        }
    }
}
public class DogMain {
    public static void main(String[] args) {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        Dog dog = new Dog();
        Dog dog1 = new Dog();
        dog.start();
        dog1.start();
    }
}

2.2 实现Runnable接口

/**
 * Runable 方式创建线程
 */
public class RunnableThread implements Runnable {
    @Override
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }

    public static void main(String[] args) {
        RunnableThread runnableThread = new RunnableThread();
        Thread rt = new Thread(runnableThread);
        rt.start();
    }
}

2.3 Callable

实现 Runnable 接口与实现 Callable 接口的方式基本相同,只是 Callable 接口里定义的方法有返回值,可以声明抛出异常而已。因此可以将实现 Runnable 接口和实现 Callable 接口归为一种方式。

/**
 * Callable 方式创建线程
 */
public class TestCall {
    public static void main(String[] args) {
        FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
            int i=0;
            for(;i<100;i++){
                System.out.println(Thread.currentThread().getName()+"循环变量i:" + i);
            }
            return i;
        });

        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"循环变量i:" + i);
            if(i == 20){
                new Thread(task,"有返回值的线程").start();
            }
        }

        try {
            System.out.println("子线程的返回值:"+task.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

2.4如何停止线程

2.4.1判断线程是否终止

JDK提供了一些方法来判断线程是否终止 —— isInterrupted()和interrupted()

2.4.2停止线程的方式

这个是得到线程信息中比较重要的一个方法了,因为这个和终止线程的方法相关联。先说一下终止线程的几种方式:

  1. 等待run方法执行完
  2. 线程对象调用stop()
  3. 线程对象调用interrupt(),在该线程的run方法中判断是否终止,抛出一个终止异常终止。
  4. 线程对象调用interrupt(),在该线程的run方法中判断是否终止,以return语句结束。

第一种就不说了,第二种stop()方法已经废弃了,因为可能会产生如下原因:

  1. 强制结束线程,该线程应该做的清理工作,无法完成。
  2. 强制结束线程,该线程已操作的加锁对象强制解锁,造成数据不一致。

第三种,是目前推荐的终止方法,调用interrupt,然后在run方法中判断是否终止。判断终止的方式有两种,一种是Thread类的静态方法interrupted(),另一种是Thread的成员方法isInterrupted()。这两个方法是有所区别的,第一个方法是会自动重置状态的,如果连续两次调用interrupted(),第一次如果是false,第二次一定是true。而isInterrupted()是不会的。 例子如下:

public class ExampleInterruptThread extends Thread{

    @Override
    public void run() {
        super.run();
        try{
            for(int i = 0 ; i < 50000000 ; i++){
                if (interrupted()){
                    System.out.println("已经是停止状态,我要退出了");
                    throw new InterruptedException("停止.......");
                }
                System.out.println("i=" + (i + 1));
            }
        }catch (InterruptedException e){
            System.out.println("顺利停止");
        }


    }
}

测试的代码如下:

public class ExampleInterruptThreadTest extends TestCase {
    public void testRun() throws Exception {
        ExampleInterruptThread thread = new ExampleInterruptThread();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

第四种方法和第三种一样,唯一的区别就是将上面的代码中的抛出异常换成return,个人还是喜欢抛出异常,这里处理的形式就比较多,比如打印信息,处理资源关闭或者捕捉之后再重新向上层抛出。 注意一点,我们上面抛出的异常是InterruptedException,这里简单说一下可能产生这个异常的原因,在原有线程sleep的情况下,调用interrupt终止线程,或者先终止线程,再让线程sleep。

3线程的生命周期(6种状态)

  1. 初始态(NEW)

    创建一个Thread对象,但还未调用start() 启动线程,线程处于初始状态。

  2. 运行态(RUNNABLE)

    ​ 就绪态

    • 该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行;

    • 所有就绪态的线程存方在就绪队列中;

    运行态

    • 获得CPU执行权,正在执行的线程;

    • 由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程

  3. 阻塞态(BLOCKED)

    • 当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。
    • 而在java中,阻塞态专指请求锁失败时进入的状态。
    • 由一个阻塞队列存放所有阻塞态的线程。
    • 处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。
  4. 等待态(WAITING)

    https://www.jianshu.com/p/b95f59351237 ( park )

    • 当前线程中调用wait、join、park函数时,当前线程就会进入等待态。

    • 也有一个等待队列放所有等待的线程。

    • 线程处于等待态表示它需要等待其他线程的指示才能继续运行。

    • 进入等待状态的线程会释放CPU执行权,并释放资源(如:锁)

  5. 超时等待态(TIMED_WAITING)

    • 当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil 时,就会进入该状态。
    • 它和等待状态一样,并不是因为请求不到资源,而是主动进入,并且进入之后需要其他线程唤醒。
    • 进入该状态后释放CPU执行权和 占有的资源
    • 与等待状态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
  6. 终止态(TERMINATED)

    • 线程执行结束后的状态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tsKiXg37-1591967888358)(D:\zwh\notes\picture\1589161052975.png)]

/**
 * 线程状态转换
 * 切换到java的bin目录
 * jps
 * jstack
 */
public class ThreadState {
    public static void main(String[] args) {
        new Thread(new TimeWaiting(),"TimeWaitingThread").start();
        new Thread(new Waiting(),"WaitingThread").start();
        //使用两个Blocker线程,一个获取锁成功,另一个被阻塞
        new Thread(new Blocked(),"BlockedThread-1").start();
        new Thread(new Blocked(),"BlockedThread-2").start();
    }

    //该线程不断的进行睡眠
    static class TimeWaiting implements Runnable{
        @Override
        public void run(){
            while(true){
                SleepUtils.second(100);
            }
        }
    }
    //该线程在Waiting.class 实例上等待
    static class Waiting implements Runnable{
        @Override
        public void run() {
            while(true){
                synchronized (Waiting.class){
                    try {
                        Waiting.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    //该线程在Blocked.class实例上加锁后不会释放该锁
    static class Blocked implements Runnable{
        @Override
        public void run(){
            synchronized (Blocked.class){
                while (true){
                    SleepUtils.second(100);
                }
            }
        }
    }

}

4.线程同步

同步是指程序中用于控制不同线程间操作发生相对顺序的机制。

在共享内存并发模型里,同步是显示进行的。必须显式指定某个方法或者代码段需要在线程之间互斥执行。

在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此是隐式进行的。

4.0 并发编程中的三个概念

image-20200601085740045

在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题。我们先看具体看一下这三个概念:

原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

有序性:即程序执行的顺序按照代码的先后顺序执行。

  • 指令重排序一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

4.1 synchronized 关键字

  • 为什么要使用synchronized

在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性)。

  • 实现原理

synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性

  • synchronized的三种应用方式

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

  • synchronized的作用

Synchronized是Java中解决并发问题的一种最常用最简单的方法 ,他可以确保线程互斥的访问同步代码

eg:

/**
 * synchronized 实现线程同步
 * volatile 关键字实现线程同步
 */
public class SynchronizedThread {

    class Bank {
        private int account = 100;
        //private volatile int account = 100;
        public int getAccount() {
            return account;
        }

        /**
         * 用同步方法实现
         *
         * @param money
         */
        public synchronized void save(int money) {
            account += money;
            System.out.println(Thread.currentThread().getName()+ "账户余额为:" + account);
        }

        /**
         * 用同步代码块实现
         *
         * @param money
         */
        public void save1(int money) {
            synchronized (this) {
                account += money;
            }
        }
    }

    class NewThread implements Runnable {
        private Bank bank;
        public NewThread(Bank bank) {
            this.bank = bank;
        }
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                //bank.save1(10);
                bank.save(10);
                /*
                在这里去使用bank.getAccount() 也会有线程安全问题,因为getAccount并没有加锁
                 */
                //System.out.println(Thread.currentThread().getName()+ "账户余额为:" + bank.getAccount());
                System.out.println("----------");//不加这一行线程一会一直占用锁,直到执行完
            }
        }

    }

    /**
     * 建立线程,调用内部类
     */
    public void useThread() {
        Bank bank = new Bank();
        NewThread new_thread = new NewThread(bank);
        Thread thread1 = new Thread(new_thread,"线程1");
        thread1.start();
        Thread thread2 = new Thread(new_thread,"线程2");
        thread2.start();
    }

    public static void main(String[] args) {
        SynchronizedThread st = new SynchronizedThread();
        st.useThread();
    }

}

4.2 Lock

Lock完全用Java写成,在java这个层面是无关JVM实现的。在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReentrantLock、ReadWriteLock(实现类ReentrantReadWriteLock),其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类,实现思路都大同小异。

如果Lock类只有lock和unlock方法也太简单了,Lock类提供了丰富的加锁的方法和对加锁的情况判断。主要有

  • 实现锁的公平
  • 获取当前线程调用lock的次数,也就是获取当前线程锁定的个数
  • 获取等待锁的线程数
  • 查询指定的线程是否等待获取此锁定
  • 查询是否有线程等待获取此锁定
  • 查询当前线程是否持有锁定
  • 判断一个锁是不是被线程持有
  • 加锁时如果中断则不加锁,进入异常处理
  • 尝试加锁,如果该锁未被其他线程持有的情况下成功
实现公平锁

在实例化锁对象的时候,构造方法有2个,一个是无参构造方法,一个是传入一个boolean变量的构造方法。当传入值为true的时候,该锁为公平锁。默认不传参数是非公平锁。

公平锁:按照线程加锁的顺序来获取锁 非公平锁:随机竞争来得到锁 此外,JAVA还提供isFair()来判断一个锁是不是公平锁。

获取当前线程锁定的个数

Java提供了getHoldCount()方法来获取当前线程的锁定个数。所谓锁定个数就是当前线程调用lock方法的次数。一般一个方法只会调用一个lock方法,但是有可能在同步代码中还有调用了别的方法,那个方法内部有同步代码。这样,getHoldCount()返回值就是大于1。

下面的方法用来判断等待锁的情况

获取等待锁的线程数

Java提供了getQueueLength()方法来得到等待锁释放的线程的个数。

查询指定的线程是否等待获取此锁定

Java提供了hasQueuedThread(Thread thread)查询该Thread是否等待该lock对象的释放。

查询是否有线程等待获取此锁定

同样,Java提供了一个简单判断是否有线程在等待锁释放即hasQueuedThreads()

下面的方法用来判断持有锁的情况

查询当前线程是否持有锁定

Java不仅提供了判断是否有线程在等待锁释放的方法,还提供了是否当前线程持有锁即isHeldByCurrentThread(),判断当前线程是否有此锁定。

判断一个锁是不是被线程持有

同样,Java提供了简单判断一个锁是不是被一个线程持有,即isLocked()

下面的方法用来实现多种方式加锁

加锁时如果中断则不加锁,进入异常处理

Lock类提供了多种选择的加锁方法,lockInterruptibly()也可以实现加锁,但是当线程被中断的时候,就会加锁失败,进行异常处理阶段。一般这种情况出现在该线程已经被打上interrupted的标记了。

尝试加锁,如果该锁未被其他线程持有的情况下成功

Java提供了tryLock()方法来进行尝试加锁,只有该锁未被其他线程持有的基础上,才会成功加锁。

eg:

/**
 * 使用可重入锁实现线程同步
 */
public class LockThread {
    class Bank {
        private int account = 100;
        private Lock lock = new ReentrantLock();
        public int getAccount() {
            return account;
        }
        /**
         * 用同步方法实现
         *
         * @param money
         */
        public void save(int money) {
            lock.lock();
            try{
                account += money;
                System.out.println(Thread.currentThread().getName()+ "账户余额为:" + account);
            }finally{
                lock.unlock();
            }
        }
    }

    class NewThread implements Runnable {
        private Bank bank;
        public NewThread(Bank bank) {
            this.bank = bank;
        }
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                bank.save(10);
                System.out.println("----------");
            }
        }

    }

    /**
     * 建立线程,调用内部类
     */
    public void useThread() {
        Bank bank = new Bank();
        NewThread new_thread = new NewThread(bank);
        Thread thread1 = new Thread(new_thread,"线程1");
        thread1.start();
        Thread thread2 = new Thread(new_thread,"线程2");
        thread2.start();
    }

    public static void main(String[] args) {
        LockThread lt = new LockThread();
        lt.useThread();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yYCQONV8-1591967888361)(D:\zwh\notes\picture\1841232-20191026150717165-1256103415.png)]

4.3 volatile 关键字

https://mp.weixin.qq.com/s/5V187MD9gW_zKVLSIOn2sQ

volatile的特性

理解volatile特性的一个好方法是把对volatile变量的单个读/写,看成是使用同一个锁对这 些单个读/写操作做了同步

简而言之,volatile变量自身具有下列特性:

  • 可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写
    入。
  • 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不
    具有原子性。
volatile的使用场景

synchronized 关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而 volatile 关键字在某些情况下性能要优于 synchronized,但是要注意 volatile 关键字是无法替代 synchronized 关键字的,因为 volatile 关键字无法保证操作的原子性。通常来说,使用 volatile 必须具备以下 2 个条件:

  • 对变量的写操作不依赖于当前值

  • 该变量没有包含在具有其他变量的不变式中

volatile 变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。实现正确的操作需要使 x 的值在操作期间保持不变,而 volatile 变量无法实现这点。

1.状态标记量
volatile boolean flag = false;
 
while(!flag){
    doSomething();
}
 
public void setFlag() {
    flag = true;
}
----------------------------------------
2.双重检测
class Singleton{
    private volatile static Singleton instance = null;
    private Singleton() {    
    }
     
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

4.4 ThreadLocal 本地变量

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这 个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。

public class MyBank {
    private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue(){
            return 100;
        }
    };
    public void save(int money){
        account.set(account.get()+money);
        System.out.println(Thread.currentThread().getName()+ "账户余额为:" + account.get());
    }
}
/**
 * 使用局部变量实现线程通信
 */
public class TestThreadLocal {

    class NewThread implements Runnable {
        public MyBank bank = new MyBank();
        public NewThread(MyBank bank) {
            this.bank = bank;
        }
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                bank.save(10);
                System.out.println("----------");
            }
        }
    }

    /**
     * 建立线程,调用内部类
     */
    public void useThread() {
        MyBank bank = new MyBank();
        NewThread new_thread = new NewThread(bank);
        Thread thread1 = new Thread(new_thread,"线程1");
        thread1.start();
        Thread thread2 = new Thread(new_thread,"线程2");
        thread2.start();
    }

    public static void main(String[] args) {
        TestThreadLocal lt = new TestThreadLocal();
        lt.useThread();
    }

}

4.5 死锁------------

public class TestA {
    public synchronized void foo(TestB b){
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"-进入A 实例的 foo方法");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"-企图调用B 的方法");
        b.last();
    }

    public synchronized void last(){
        System.out.println("进入了A 类的last 方法");
    }
}
----------------------------------------------------------------------------------------
public class TestB {
    public synchronized void bar(TestA a){
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"-进入B 实例的 bar方法");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"-企图调用A 的方法");
        a.last();
    }

    public synchronized void last(){
        System.out.println("进入了B 类的last 方法");
    }
}
----------------------------------------------------------------------------------------
public class TestDeadLock implements Runnable{
    TestA a = new TestA();
    TestB b = new TestB();

    public void init(){
        Thread.currentThread().setName("主线程");
        a.foo(b);
        System.out.println("进入主线程之后");
    }

    @Override
    public void run(){
        Thread.currentThread().setName("副线程");
        b.bar(a);
        System.out.println("进入副线程之后");
    }
    
    public static void main(String[] args) {
        TestDeadLock tdl = new TestDeadLock();
        Thread dt = new Thread(tdl);
        dt.start();
        tdl.init();
    }
}

5.线程间的通信

通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。

在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。

在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信

java的并发采用的是共享内存模型,java之间的通信总是隐式进行,整个通信对程序员完全透明。

5.1 等待(wait)/通知(notify)机制

/**
 * 等待/通知机制
 * (1) 使用wait()、notify()和notifyAll()时需要先对调用对象加锁。
 * (2) 使用wait() 方法后,现场装填由RUNNING变为WAITING,并将当前线程放置到对象的等待队列
 * (3) notify 或 notifyAll方法调用后,等待线程依旧不会从wait 返回,
 *      需要调用notify 或 notifyAll 的线程释放锁之后,等待线程才有机会从wait返回
 * (4) notify 方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll
 *      方法则是将所有的线程全部移到同步队列,被移动的线程装填由WAITING变为BLOCKED
 * (5) 从wait 方法返回的前提是获取了调用对象的锁
 */
public class WaitNotify {
    static boolean flag = true;
    static Object lock = new Object();

    public static void main(String[] args) throws Exception {
        Thread waitThread = new Thread(new Wait(),"WaitThread");
        waitThread.start();
        TimeUnit.SECONDS.sleep(1);
        Thread notifyThread = new Thread(new Notify(),"NotifyThread");
        notifyThread.start();
    }

    static class Wait implements Runnable{
        @Override
        public void run(){
            //加锁
            synchronized (lock){
                //当条件不满足时,继续wait,同时释放了lock的锁
                while (flag){
                    try {
                        System.out.println(Thread.currentThread()+"flag is true. wait@" +
                                new SimpleDateFormat("HH:mm:ss").format(new Date()));
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread()+"flag is false. running@" +
                        new SimpleDateFormat("HH:mm:ss").format(new Date()));
            }
        }
    }

    static class Notify implements Runnable {
        @Override
        public void run(){
            //加锁
            synchronized (lock){
                //获取lock的锁,然后进行通知,通知不会释放锁,直到当前线程释放了lock之后,WaitThread才能从wait方法中返回
                System.out.println(Thread.currentThread() + "hold lock. notify @ " +
                        new SimpleDateFormat("HH:mm:ss").format(new Date()));
                lock.notifyAll();
                flag = false;
                SleepUtils.second(5);
            }
            //再次加锁
            synchronized (lock){
                System.out.println(Thread.currentThread() + "hold lock again. sleep @ " +
                        new SimpleDateFormat("HH:mm:ss").format(new Date()));
                SleepUtils.second(5);
            }
        }
    }
}

5.2 join

/**
 * 如果一个线程A执行了threadB.join() 语句,其含义是:当前线程A等待threadB 线程终止之后才从
 * threadB.join() 返回。线程Thread 从出了提供join 方法之外,还提供join(long millis) 和
 * join(long millis,int nanos) 两盒具备超时特性的方法。如果线程threadB 在给定的超时时间里
 * 没有终止,那么线程将会从该超时方法中返回。
 */
public class TestJoin {
    public static void main(String[] args) throws Exception{
        Thread previous = Thread.currentThread();
        for(int i=0;i<10;i++){
            Thread thread = new Thread(new Domino(previous),String.valueOf(i));
            thread.start();
            previous = thread;
        }
        SleepUtils.second(5);
        System.out.println(Thread.currentThread().getName() + " terminate . ");
    }
    static class Domino implements Runnable{
        private Thread tHread;
        public Domino (Thread thread){
            this.tHread = thread;
        }

        @Override
        public void run() {
            try{
                tHread.join();
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " terminate .");
        }
    }
}

5.3 ThreadLocal

6.线程池

在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。

java中的线程池是运行场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理的使用线程池可以降低资源消耗、提高相应速度、提高线程的可管理性。

6.1 ThreadPoolExecutor的几个重要参数

https://blog.csdn.net/weixin_28760063/article/details/81266152?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

  1. corePoolSize(核心线程池的大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行,新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。

  2. runnableTaskQueue(任务对列):用于保存等待执行的任务的阻塞对列。可以选择以下几个阻塞队列。

    ArrayBlockingQueue

    LinkedBlockingQueue

    SynchronousQueue

    PriorityBlockingQueue

  3. maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。

  4. ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。

  5. RejectedExecutionHandler(饱和策略)。

img

实际中,如果Executors提供的静态方法能满足要求,就尽量使用它提供的方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。

另外,如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写。

6.2 Executors提供的几个静态方法

Executors类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口

/**
 * 线程池工具类
 */
public class ThreadPoolUtil implements Runnable{
    private Integer index;

    public ThreadPoolUtil(Integer index) {
        this.index = index;
    }

    @Override
    public void run() {
        try {
            System.out.println(index+"开始处理线程!");
            Thread.sleep(50);
            System.out.println("线程标识是:"+this.toString());
            System.out.println(index+"处理结束!");
        }
        catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
}
6.2.1 newFixedThreadPool

定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程

newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

eg:

/**
 * 是四个线程,但是这时设置了线程固定大小是2,所以两个线程先新建,运行完毕后剩下的再执行。
 */
public class TestFixedThreadPool {
    public static void main(String[] args) {
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
        System.out.println("------------newFixedThreadPool-------------");
        for(int i = 0; i < 4; i++) {
            int index = i;
            newFixedThreadPool.execute(new ThreadPoolUtil(index));
        }
        newFixedThreadPool.shutdown();
    }
}
6.2.2 newSingleThreadExecutor

只有一条线程来执行任务,适用于有顺序的任务的应用场景。

newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

eg:

/**
 * 每次只执行一个线程,并且是按顺序执行。
 */
public class TestSingleThreadExecutor {
    public static void main(String[] args) {
        ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
        System.out.println("---------------newSingleThreadExecutor--------------");
        for(int i = 0; i < 4; i++) {
            final int index = i;
            newSingleThreadExecutor.execute(new ThreadPoolUtil(index));
        }
    }
}
6.2.3 newCachedThreadPool

可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。

newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

eg:

/**
 * 设置新建四个线程,四个线程同时进行
 */
public class TestCachedThreadPool {
    public static void main(String[] args) {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        System.out.println("---------------newCachedThreadPool--------------");
        for(int i = 0; i < 4; i++) {
            int index = i;
            newCachedThreadPool.execute(new ThreadPoolUtil(index));
        }
    }
}
6.2.4 newScheduledThreadPool

计划线程池,支持定时及周期性任务执行

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

eg:

/**
 * 除了延迟3秒执行,别的和newFixedThreadPool相同
 */
public class TestScheduledThreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(2);
        System.out.println("---------------newScheduledThreadPool--------------");
        for(int i = 0; i < 4; i++) {
            final int index = i;
            //延迟3秒执行
            newScheduledThreadPool.schedule(new ThreadPoolUtil(index),3, TimeUnit.SECONDS);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值