Java并发编程之线程修炼


### 前言
Java 提供了多线程编程内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

- 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。

- 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。


一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。


### 线程的生命周期
线程的生命周期是此线程创建到此线程死亡的过程。如下图:

![threadsatus](https://blog-ituac-1252741530.cos.ap-beijing.myqcloud.com/threadstatus_1570872397334.png)

生命周期完整状态:
1. 初始化状态:线程创建,但是还没有调用start()方法。
2. 运行状态:就绪状态和运行中状态在这里合并为运行状态。
3. 阻塞状态:线程阻塞,线程等待进入synchronized修饰的代码块或方法(方法包含:sleep()、suspend()、wait()、join()等方法)。
4. 等待状态:处于要调用notify()或notifyAll()进行唤醒通知才可以运行。
5. 超时等待状态:线程进入等待状态,在指定时间后自行返回。
6. 终止状态:线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。


### 创建线程
创建线程有三种方式:
1. 通过实现 Runnable 接口
2. 通过继承 Thread 类本身
3. 通过 Callable 和 Future 创建线程


### 线程优先级
Java 线程优先级使用 1 ~ 10 的整数表示:
1. 最低优先级 1:Thread.MIN_PRIORITY
2. 最高优先级 10:Thread.MAX_PRIORITY
3. 普通优先级 5:Thread.NORM_PRIORITY 【父线程默认】

ps:父线程默认优先级是普通优先级5,子线程默认优先级是父优先级,也就是子线程默认优先级并非是普通优先级5,当父线程优先级修改后创建子线程,子线程则会是当前父线程的优先级。(好不通顺^0^,应该可以理解)


如下代码操作线程优先级:
```java
// 获取线程优先级
Thread.currentThread().getPriority()

//设置线程优先级
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
Thread.currentThread().setPriority(6);

```
Priority 设置范围在 1-10,不然会发生java.lang.IllegalArgumentException 异常。


### 线程间通信
子线程执行完毕通知主线程处理某些逻辑的场景还是比较容易遇见的业务场景,这就需要用到线程间的通信。

- 非线程安全(暂停、恢复、停止操作)

Thread提供的方法可以实现对线程进行暂停suspend(),恢复resume(),停止stop()的操作
```java
/**
 * 
 * @author ituac
 */

public class ThreadNotify implements Runnable{

    public void run() {
        while (true){
                try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName()+"输出helloword");
                }catch (InterruptedException e){
                System.err.println(e.getMessage());
              }
           }
    }
    
    public static void main(String[] args) throws Exception{
        Runnable runnable = new ThreadNotify();
        Thread thread = new Thread(runnable,"operationThread");
        /**
         * 启动,输出helloword
         */
        thread.start();
        TimeUnit.SECONDS.sleep(3L);

        /**
         * 线程暂停,不在输出helloword
         */
        System.out.println("线程暂停:"+LocalTime.now());
        thread.suspend();
        TimeUnit.SECONDS.sleep(3L);

        /**
         * 线程恢复,继续输出helloword
         */
        System.out.println("线程恢复:"+LocalTime.now());
        thread.resume();
        TimeUnit.SECONDS.sleep(3L);

        /**
         * 线程停止,不在输出helloword
         */
        thread.stop();
        System.out.println("线程停止:"+LocalTime.now());
        TimeUnit.SECONDS.sleep(3L);
    }
    
}

```

运行结果如下:

![cc1](https://blog-ituac-1252741530.cos.ap-beijing.myqcloud.com/cs11_1570874724137.png)


- 线程安全(等待/通知机制)

线程安全的暂停,恢复操作可以使用等待/通知机制代替,等待通知模式是 Java 中比较经典的线程通信方式。

相关API操作

|方法名|描述|
|-------|-------|
|notify()|通知一个在对象上等待的线程,使其重wait()方法中返回,前提是该线程获得了对象的锁|
|notifyAll()|通知所有等待在该对象上的线程|
|wait()|只有等待另外线程的通知或被中断才会返回,调用该方法会释放对象的锁|
|wait(long)|设置超时等待一段时间(毫秒),如果超过时间就返回|
|wait(long,int)|超时时间细粒度的控制,可以达到纳秒|


通过上述APi操作线程通信,如下代码:

```java
/**
 * @author ituac
 */
public class ThreadWaitNotify {
    
    private int startNum = 1;
    
    private boolean flag = false;
    
    //奇数计算
    public static class ThreadA implements Runnable{

        private ThreadWaitNotify threada;
        
        public ThreadA(ThreadWaitNotify threada) {
            this.threada = threada;
        }
        
        public void run() {
            while (threada.startNum <= 50) {
                synchronized (ThreadWaitNotify.class) {
                    //System.out.println("奇数线程抢到锁了");
                    if (!threada.flag) {
                        System.out.println(Thread.currentThread().getName() + "+-+奇数" + threada.startNum);
                        threada.startNum++;
                        threada.flag = true;
                        ThreadWaitNotify.class.notify();
                    }else {
                        try {
                            ThreadWaitNotify.class.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }    
                }
            }
        }    
    }
    
    
    //偶数计算
    public static class ThreadB implements Runnable{

        private ThreadWaitNotify threadb;
        
        public ThreadB(ThreadWaitNotify threadb) {
            this.threadb = threadb;
        }
        
        public void run() {
            while (threadb.startNum <= 50) {
                synchronized (ThreadWaitNotify.class) {
                    // System.out.println("偶数线程抢到锁了");
                    if (threadb.flag) {
                        System.out.println(Thread.currentThread().getName() + "+-+偶数" + threadb.startNum);
                        threadb.startNum++;
                        threadb.flag = false;
                        ThreadWaitNotify.class.notify();
                    }else {
                        try {
                            ThreadWaitNotify.class.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }    
                }
            }
        }    
    }
    
    
    public static void main(String[] args) {
        ThreadWaitNotify thread = new ThreadWaitNotify();
        Thread t1 = new Thread(new ThreadA(thread));
        t1.setName("A");
        Thread t2 = new Thread(new ThreadB(thread));
        t2.setName("B");
        t1.start();
        t2.start();
        
        
    }

}

```

运行结果如下:

![xcaq](https://blog-ituac-1252741530.cos.ap-beijing.myqcloud.com/xcaq_1570876159797.png)

安全线程操作中需要注意的是:
1. wait() 、nofify() 、nofityAll() 调用的前提都是获得了对象的锁(也可称为对象监视器)。
2. 调用 wait() 方法后线程会释放锁,进入 WAITING 状态,该线程也会被移动到等待队列中。
3. 调用 notify() 方法会将等待队列中的线程移动到同步队列中,线程状态也会更新为 BLOCKED
4. 从 wait() 方法返回的前提是调用 notify() 方法的线程释放锁,wait() 方法的线程获得锁。


等待通知经典范式: 
1. 线程 A 作为消费者
2. 获取对象的锁
3. 进入 while(判断条件),并调用 wait() 方法。
4. 当条件满足跳出循环执行具体处理逻辑
5. 线程 B 作为生产者
6. 获取对象锁。
7. 更改与线程 A 共用的判断条件
8. 调用 notify() 方法

上述范式伪代码:
```java

//Thread A
synchronized(Object){
    while(条件){
        Object.wait();
    }
    //do something
}
//Thread B
synchronized(Object){
    条件=false;//改变条件
    Object.notify();
}
```


### Thread.join()

Thread.join()作用是等待该线程终止。

相关API操作

|方法名|作用|
|-------|-------|
|join()|A线程调用B线程的join()方法后,那么A线程需要等待B线程终止,才可以继续执行|
|join(long)|在join()方法的基础上增加了时间限制(毫秒),超出时间后,无论A线程是否执行完,当前线程都进入就绪状态,重新等待cpu调用|
|join(long,int)|超时时间细粒度的控制,可以达到纳秒|

```java
       private static void join() throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                System.out.println("t1");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }) ;
        
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                System.out.println("t2");
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }) ;
        t1.start();
        t2.start();
        //等待线程1终止
        t1.join();
        //等待线程2终止
        t2.join();
        System.out.println("thread end");
    }

```

ps: join()方法实现等待其实是调用了wait()方法,isAlive()方法的作用是监测子线程是否终止,如果终止或者超过了指定时间,代码就继续往下执行,否则就继续等待,直到条件成立。

### volatile 共享内存
Java 是采用共享内存的方式进行线程通信的,所以可以采用以下方式用主线程关闭 A 线程:

```java
public class Volatile implements Runnable{
    private static volatile boolean flag = true ;
    @Override
    public void run() {
        while (flag){
            System.out.println(Thread.currentThread().getName() + "运行中");
        }
        System.out.println(Thread.currentThread().getName() +"执行完成");
    }
    public static void main(String[] args) throws InterruptedException {
        Volatile aVolatile = new Volatile();
        new Thread(aVolatile,"thread A").start();
        System.out.println("main 线程正在运行") ;
        TimeUnit.MILLISECONDS.sleep(100) ;
        aVolatile.stopThread();
    }
    private void stopThread(){
        flag = false ;
    }
}
```
volatile定义的flag 存放于主内存中,所以主线程和线程 A 都可以看到。
volatile怎么实现存放在内存,可了解volatile原理(自行百度谢谢^-^)。

### ThreadLocal

ThreadLocal,叫线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构,ThreadLocal类型的变量在每个线程中是独立的,在多线程环境下不会相互影响

```java
    private static String STATE;
    private static ThreadLocal<String> STRING_THREAD_LOCAL = new InheritableThreadLocal<>();


    public static void main(String[] args) throws InterruptedException{

        STATE = "未重置";
        STRING_THREAD_LOCAL.set("未重置");

        Thread thread = new Thread(() ->
        {
            STATE = "已重置";
            STRING_THREAD_LOCAL.set("已重置");
            System.out.println(Thread.currentThread().getName() + " : 变量已重置");
        });
        thread.start();
        thread.join();

        System.out.println(Thread.currentThread().getName() + "STATE : " + STATE);
        System.out.println(Thread.currentThread().getName() + "STRING_THREAD_LOCAL : " + STRING_THREAD_LOCAL.get());
    }

```

### CountDownLatch 并发工具

CountDownLatch 可以实现 join 相同的功能,相对而言更加方便,实例如下:
```java
private static void countDownLatch() throws Exception{
    int thread = 3 ;
    long start = System.currentTimeMillis();
    final CountDownLatch countDown = new CountDownLatch(thread);
    for (int i= 0 ;i<thread ; i++){
        new Thread(new Runnable() {
            @Override
            public void run() {
                LOGGER.info("线程开始");
                try {
                    Thread.sleep(100);
                    countDown.countDown();
                    LOGGER.info("线程结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    countDown.await();
}
```

CountDownLatch 基于 AQS(AbstractQueuedSynchronizer) 实现。

CountDownLatch思路如下:
1. 初始化一个 CountDownLatch 时告诉并发的线程,然后在每个线程处理完毕之后调用 countDown() 方法。
2. 此方法会将 AQS 内置的一个 state 状态 -1 。
3. 最终在主线程调用 await() 方法,它会阻塞直到 state == 0 的时候返回。

Ps:CyclicBarrier工具,叫做屏障,也可以用于线程间通信,它可以等待 N 个线程都达到某个状态后继续运行的效果。之后再做展示。

### 管道通信

Java 虽说是基于内存通信的,也可以使用管道进行通信,管道通信概念对于netty开发者并不陌生,代码如下:
```java
/**
 * @author ituac
 */
public class ThreadPip {

    
    public static void piped() throws IOException {
        //面向于字符 PipedInputStream 面向于字节
        final PipedWriter writer = new PipedWriter();
        final PipedReader reader = new PipedReader();
        //输入输出流建立连接
        writer.connect(reader);
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                System.out.println("线程1已运行");
                try {
                    for (int i = 0; i < 10; i++) {
                        System.out.println("线程1发送线程2第"+i+"条消息");
                        writer.write(i+"");
                        Thread.sleep(10);
                    }
                } catch (Exception e) {
                } finally {
                    try {
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                System.out.println("线程2已运行");
                int msg = 0;
                try {
                    while ((msg = reader.read()) != -1) {
                        System.out.println("msg="+(char) msg);
                    }
                } catch (Exception e) {
                }
            }
        });
        t1.start();
        t2.start();
    }
    
    public static void main(String[] args) throws IOException {
        piped();
    }
    
}
```
运行结果如下:
![theadpip](https://blog-ituac-1252741530.cos.ap-beijing.myqcloud.com/thread1111_1570878328370.png)

### 小结

在实际应用开发中可以根据需求选择最业务场景所适合的线程通信方式。
即将发布下一篇章Java并发编程之线程池修炼。

本文由 波罗星 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://blog.ituac.com/archives/thread


 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值