liujijiang

java学习笔记 --21-- 并发(1)

2020.04.07

并发

研究并发问题的最强理由:如果视而不见,你就会遭其反噬

  • 并发编程是程序分解成多个分离的,独立运行的子任务,每个子任务由执行线程驱动
  • 线程就是进程中一个独立的顺序控制流,因此,单个进程也可以拥有多个并发执行的任务
  • CPU轮流给每个任务分配时间,让每个任务都觉得自己一直在占有CPU

定义任务

线程可以驱动任务
定义任务:实现Runnable接口并编写run方法

Thread类

把Runnable对象交给一个Thread构造器

编写一个类实现Runnable接口的run方法,run方法是一个循环
Thread.yield:告诉线程调度器这个线程的主要部分已经执行完了,可以切换到其他的线程中去了
线程调度器:如果你的电脑有多个处理器,线程处理器会在这些处理器之间默默的分发线程

package com.company;
/**
 * @ClassName LiftOff
 * @Description
 * @Auther liuxiansen
 * @Date 2020/4/6 2:59 下午
 **/
public class LiftOff implements Runnable {

    private int num = 10;
    private static int IDCOUNT = 0;
    private final int ID = IDCOUNT++;

    public LiftOff(){

    }

    @Override
    public void run() {
        while (num > 0){
            System.out.print("#(" + ID + ")time: " + num-- + "!!!    ");
            Thread.yield();
        }
        System.out.println();
    }
}

建一个新的Thread对象并新建一个LiftOff对象给他的构造器
start:为该线程的执行初始化,然后调用run方法
10个LiftOff对象在并行的执行,CPU轮流执行每个线程

package com.company;
/**
 * @ClassName Test
 * @Description
 * @Auther liuxiansen
 * @Date 2020/3/29 8:55 上午
 **/
public class Test{
    public static void main(String [] args) {

        for (int i=0;i<10;i++){
            Thread a = new Thread(new LiftOff());
            a.start();
        }
        System.out.println("start!!!");
    }
}

Executor

javaSE5中的java.util.concurrent包中的Executor(执行器)可以帮助你管理Thread对象
ExecutorService:具有生命周期的Executor
CachedThreadPool:为每个任务创建一个线程

使用Executor管理Thread
shutdown:防止新任务提交给当前的Executor,Executor将运行在shutdown之前提交的所有任务

public static void main(String [] args) {

        ExecutorService service = Executors.newCachedThreadPool();
        for(int i=0;i<10;i++){
            service.execute(new LiftOff());
        }
        service.shutdown();
    }

FixedThreadPool:使用有线的线程执行任务
传入初始化的线程参数,使用1个线程完成和使用10个线程完成所有的任务结果是不一样的
FixedThreadPool能创建的Thread对象最多就是参数个

public static void main(String [] args) {

        ExecutorService service = Executors.newFixedThreadPool(5);
        for(int i=0;i<10;i++){
            service.execute(new LiftOff());
        }
        service.shutdown();
    }

在任何线程池中,现有线程在可能情况下会被自动复用

CachedThreadPool通常会创建于所需要的线程数相等的线程
通常情况下使用CachedThreadPool,当出现问题的时候使用FixedThreadPool

SingleThreadExecutor:线程数量为1的FixedThreadPool,处理多任务时任务排队,只有当前一个任务完成时,才开始下一个任务

使用SingleThreadExecutor
每个线程都是等前一个线程完成后才创建执行

public static void main(String [] args) {

        ExecutorService service = Executors.newSingleThreadExecutor();
        for(int i=0;i<10;i++){
            service.execute(new LiftOff());
        }
        service.shutdown();
    }

###从任务中产生返回值
Runnable是执行工作的独立任务,不能返回任何值

如果线程需要返回值,需要实现Callable接口,重写其中的call方法
Callable是泛型的

package com.company;

import java.util.concurrent.Callable;

/**
 * @ClassName LiftOff
 * @Description
 * @Auther liuxiansen
 * @Date 2020/4/6 2:59 下午
 **/
public class LiftOff implements Callable<String> {

    private int num = 10;
    private static int IDCOUNT = 0;
    private final int ID = IDCOUNT++;

    public LiftOff(){

    }

    @Override
    public String call() throws Exception {
        return "#(" + ID + ")run!     ";
    }
}

使用ExecutorServer的submit方法调用线程,返回Future对象,调用Future的get方法返回线程返回的信息

package com.company;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @ClassName Test
 * @Description
 * @Auther liuxiansen
 * @Date 2020/3/29 8:55 上午
 **/
public class Test{
    public static void main(String [] args) {

        ExecutorService executorService = Executors.newCachedThreadPool();

        List<Future<String>> list = new ArrayList<>();

        for(int i=0;i<10;i++){
            list.add(executorService.submit(new LiftOff()));
        }

        try {
            for(Future<String> future : list){
                System.out.println(future.get());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }

    }
}

休眠

使用sleep让线程阻塞一段时间

package com.company;

/**
 * @ClassName LiftOff
 * @Description
 * @Auther liuxiansen
 * @Date 2020/4/6 2:59 下午
 **/
public class LiftOff implements Runnable {

    private int num = 10;
    private static int IDCOUNT = 0;
    private final int ID = IDCOUNT++;

    public LiftOff(){

    }

    @Override
    public void run() {
        while (num > 0){
            System.out.print("#(" + ID + ")time: " + num-- + "!!!    ");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println();
    }
}

想要控制任务执行顺序:

  • 使用同步控制
  • 不使用线程,编写自己的协作例程

优先级

调度器会让优先级高的先执行,但并不意味着让优先级低的不执行(造成死锁),优先级低的仅仅是执行频率较低


绝大多数时间里,线程应该按照默认的优先级执行,改变优先级通常是错误的

Thread.currentThread:获取驱动当前的Thread对象的引用

package com.company;

/**
 * @ClassName LiftOff
 * @Description
 * @Auther liuxiansen
 * @Date 2020/4/6 2:59 下午
 **/
public class LiftOff implements Runnable {

    private int num = 10;
    private static int IDCOUNT = 0;
    private final int ID = IDCOUNT++;

    private int priority;

    public LiftOff(int priority){
        this.priority = priority;
    }

    @Override
    public void run() {
        Thread.currentThread().setPriority(priority);
        while (num > 0){
            System.out.println("#(" + ID + ")time: " + num-- + "!");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println();
    }
}

让步

Thread.yield:暗示调度器这个线程的主要工作已经做完,可以去做别的线程了,仅仅是暗示(调度器不一定会采纳)
在任何重要的调度工作中不能依赖于 yield

后台程序

后台程序:在程序运行时在后台为程序提供服务的线程,并且这种线程不属于程序不可或缺的一部分
程序的所有非后台程序终止时所有的后台程序也会被杀死,但是只要有一个非后台程序在运行,后台程序就不会被杀死

在Thread调用start之前setDaemon
如果主程序的执行时间短,后台线程没有执行完就会被杀死

public static void main(String [] args) {

        for (int i=0;i<10;i++){
            Thread thread = new Thread(new LiftOff(Thread.MAX_PRIORITY));

            thread.setDaemon(true);

            thread.start();
        }

        try {
            Thread.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

可以使用isDaemon方法判断一个线程是否是后台线程
后台线程在不执行finally子句的情况下是不会终止run方法的


package com.company;

import java.util.concurrent.TimeUnit;

/**
 * @ClassName LiftOff
 * @Description
 * @Auther liuxiansen
 * @Date 2020/4/6 2:59 下午
 **/
public class LiftOff implements Runnable {

    @Override
    public void run() {
        try {
            System.out.println("new thread starting!");
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("thread stop");
        }
    }
}

package com.company;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @ClassName Test
 * @Description
 * @Auther liuxiansen
 * @Date 2020/3/29 8:55 上午
 **/
public class Test{
    public static void main(String [] args) {

        Thread thread = new Thread(new LiftOff());
        //thread.setDaemon(true);
        thread.start();

    }
}

当线程为后台线程时,finally不会执行
注释掉setDaemon后,finally就会执行
程序执行完成后不会给后台线程一个体面的结束


编码的变体

直接继承Thread类

package com.company;

import java.util.concurrent.TimeUnit;

/**
 * @ClassName LiftOff
 * @Description
 * @Auther liuxiansen
 * @Date 2020/4/6 2:59 下午
 **/
public class LiftOff  extends Thread {
    @Override
    public void run() {
        try {
            System.out.println("new thread starting!");
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("thread stop");
        }
    }
}

package com.company;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @ClassName Test
 * @Description
 * @Auther liuxiansen
 * @Date 2020/3/29 8:55 上午
 **/
public class Test{
    public static void main(String [] args) {

        new LiftOff().start();

    }
}


自管理

package com.company;

import java.util.concurrent.TimeUnit;

/**
 * @ClassName LiftOff
 * @Description
 * @Auther liuxiansen
 * @Date 2020/4/6 2:59 下午
 **/
public class LiftOff  implements Runnable {
    private  Thread thread = new Thread(this);

    public LiftOff(){
        thread.start();
    }

    @Override
    public void run() {
        try {
            System.out.println("new thread starting!");
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("thread stop");
        }
    }
}

package com.company;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @ClassName Test
 * @Description
 * @Auther liuxiansen
 * @Date 2020/3/29 8:55 上午
 **/
public class Test{
    public static void main(String [] args) {
        new LiftOff();
    }
}


术语

对线程Thread没有任何的实际的控制权
线程只是执行赋予他的任务
错误的理解:线程是一个任务
java的线程机方式来源于c的p线程方式(应深入研究)
任务:要执行的工作
线程:驱动任务的机制

加入一个线程


package com.company;

import java.util.concurrent.TimeUnit;

/**
 * @ClassName LiftOff
 * @Description
 * @Auther liuxiansen
 * @Date 2020/4/6 2:59 下午
 **/
public class LiftOff extends Thread {

    public LiftOff(String name){
        super(name);
        this.start();
    }

    @Override
    public void run() {
        try {
            System.out.println("Thread:" + Thread.currentThread().getName() + " starting");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + " stoped");
        }
    }
}

package com.company;

/**
 * @Author redarm
 * @Date 2020/4/9 下午7:16
 **/
public class RightNow extends Thread {

    private LiftOff liftOff;

     public RightNow(String name, LiftOff liftOff){
         super(name);
         this.liftOff = liftOff;
         this.start();
     }

     @Override
    public void run(){
         try {
             liftOff.join();
             System.out.println(Thread.currentThread().getName() + " started");
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         } finally {
             System.out.println(Thread.currentThread().getName() + " stoped");
         }
     }
}
package com.company;

import java.io.*;
import java.nio.channels.FileChannel;

/**
 * @ClassName Test
 * @Description
 * @Auther liuxiansen
 * @Date 2020/3/29 8:55 上午
 **/
public class Test{
    public static void main(String [] args) throws IOException {
        LiftOff liftOff1 = new LiftOff("liftoff1");
        RightNow rightNow1 = new RightNow("rightnow1",liftOff1);

    }
}

rightnow 中liftoff.join,所以只有liftoff线程执行完后rightnow才开始执行


共享受限资源

防止两个任务访问相同的资源:
在资源被一个任务使用的时候加上锁
当任务访问synchronized的资源的时候:

  • 检查锁是否可用
  • 获取锁
  • 执行代码
  • 释放锁

在对象的方法上加上关键字synchronized:

  • 这个方法只能同时被一个任务调用
  • 一个对象的多个synchronized方法只能同时被调用一个
  • 一个synchronized方法被调用的时候,调用其他的synchronized方法的任务就会等待
  • 应该把成员声明为private才有意义,才能控制域的访问

每个访问临界共享资源的方法都必须被同步

package com.company;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

/**
 * @Author redarm
 * @Date 2020/4/9 下午8:23
 **/
public class Stone extends Thread {
    private String storyOfStone;

    public Stone(String name){
        super(name);
        this.read();
        this.start();
    }

    @Override
    public void run(){
        this.read();
    }

    private synchronized void read(){
        try {
            System.out.println(Thread.currentThread().getName() + "  started");
            BufferedReader bufferedReader = new BufferedReader(new FileReader("test_file"));
            String ss;
            StringBuilder sb = new StringBuilder();

            Thread.sleep(1000);

            while ((ss = bufferedReader.readLine()) != null){
                sb.append(ss + "\n");
            }

            this.storyOfStone = sb.toString();

            bufferedReader.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "  stoped");
        }
    }
}

使用显示的Lock对象

Lock对象必须显示的创建,锁定和释放

使用Lock的ReentrantLock实现,

  • lock:锁住
  • unlock:解锁
package com.company;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author redarm
 * @Date 2020/4/9 下午8:23
 **/
public class Stone extends Thread {
    private String storyOfStone;
    private Lock lock = new ReentrantLock();

    public Stone(String name){
        super(name);
        this.read();
        this.start();
    }

    @Override
    public void run(){
        this.read();
    }

    private  void read(){
        try {
            lock.lock();
            
            System.out.println(Thread.currentThread().getName() + "  started");
            BufferedReader bufferedReader = new BufferedReader(new FileReader("test_file"));
            String ss;
            StringBuilder sb = new StringBuilder();

            Thread.sleep(1000);

            while ((ss = bufferedReader.readLine()) != null){
                sb.append(ss + "\n");
            }

            this.storyOfStone = sb.toString();

            bufferedReader.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "  stoped");
            lock.unlock();
        }
    }
}

使用tryLock尝试获取锁,如果其他人已经获取了这个锁,那么就去执行其他的事情

lock.tryLock(2, TimeUnit.SECONDS);