JAVA多线程与并发(基础)

了解多线程之前,对一些基本概念进行理解。


共享资源

进程:系统进行资源分配和调度的一个独立单位,是代码在一个数据集合上的一次运行活动。我们启动的Java程序,就会产生一个java.exe的进程。
线程:系统能够进行运算调度的最小单位,包含在进程中,一个进程可以并发多个线程,每个线程可以执行不同的任务。线程本身只拥有的资源很少(计数器、寄存器、栈),同一线程中多个进程共享进程的资源。

并行:同一时间内多个任务在多个CPU上同时执行,真正意义上的同时执行。
并发:通过CPU调度算法,让用户感觉同一时间段内多个任务都在执行,且并没有执行结束。以单CPU为例,为多个线程分配CPU时间片运行,实际上CPU操作层面上并不是真正的同时执行,而是快速轮换执行。

线程安全性:并发情况下,线程调度顺序不影响程序执行结果,即为线程安全的;
并发情况下,线程调度顺序影响了执行结果,即为线程不安全的;

同步:排队,并发情况下,对于共享资源的读写访问时需要排队访问,才能保证其线程安全性。(synchronized、volatile、Reentrantlock)

一、多线程实现方式

多线程有两种实现方式,继承Thread类和实现Runnable / Callable 接口,通过源码我们可以看到Thread也是实现了Rannable接口,两者的区别就在于,使用Thread类方式创建新的线程,就不支持多继承了。本质上没有其他区别。

具体区别
Thread: 不支持多继承;代码不能被共享;
Runnable: 避免了单继承的局限性;适合多个相同个程序代码的县城区处理同一资源情况;不能返回值
Callable: 避免了单继承的局限性;可返回值(阻塞)

线程的几种状态

线程状态图,摘自博客园

NEW:初始状态,刚被创建,但还未启动(未start);
RUNNABLE:线程正常运行中,当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等;
BLOCKED:阻塞状态,等待另一个线程的synchronized块的执行释放,也就是线程在等待进入的临界区;
WAITING: 等待状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify;
TIMED-WAITING:有时限的等待,例如sleep(10000),这10秒都为这个状态;
TERMINATED:线程run()方法执行完毕;

1.Thread类 (java.lang 包)

以下代码使用Thread类实现多线程,职位展示部分状态信息。

public class AppThreads extends Thread {
    @Override
    public void run() {
        super.run();
        //打印线程基本信息 Thread[Name 线程名,Priority 优先级,group 线程组]
        System.out.println(this+";线程ID:"+getId());
        System.out.println("【状态】"+getState()+";接下来10秒进入休眠;执行时间:"+new Date());
            try {
                sleep(10000);   //休眠
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        System.out.println("appThreads1线程执行结束"+";执行时间:"+new Date());
    }
    public static void main(String[] args) throws Exception {
        AppThreads appThreads1 = new AppThreads();  //创建线程类
        System.out.println("【状态】"+appThreads1.getState()+";执行时间:"+new Date());
        appThreads1.start();    //启动线程
        sleep(5000);   //main方法等待5秒
        System.out.println("第6秒线程【状态】"+appThreads1.getState()+";执行时间:"+new Date());
        System.out.println("main主线程等待appThreads1线程结束"+";执行时间:"+new Date());
        appThreads1.join(); //main主线程调用 appThreads1 线程的 join()方法,当前线程进入阻塞状态,直到 appThreads1 线程结束才回复运行
        System.out.println("main主线程结束"+";执行时间:"+new Date());
        System.out.println("【状态】"+appThreads1.getState()+";执行时间:"+new Date());
    }
}

控制台打印结果
Thread[Thread-0,5,main];线程ID:12
main执行时间:Mon Apr 13 23:46:22 CST 2020;【状态】NEW
run 执行时间:Mon Apr 13 23:46:22 CST 2020;【状态】RUNNABLE;接下来10秒进入休眠
main执行时间:Mon Apr 13 23:46:27 CST 2020;第5秒线程【状态】TIMED_WAITING
main执行时间:Mon Apr 13 23:46:27 CST 2020;main主线程等待appThreads1线程结束
run 执行时间:Mon Apr 13 23:46:32 CST 2020;appThreads1线程执行结束
main执行时间:Mon Apr 13 23:46:32 CST 2020;main主线程结束
main执行时间:Mon Apr 13 23:46:32 CST 2020;【状态】TERMINATED

2.Runnable类 (java.lang 包)

模拟小明、小红、小蓝三人一起购买商品,并发情况下商品库存为10个。

package Test;
import java.util.Date;
class BuyGoods implements Runnable{
    static int goodsNum = 10;     //初始化10件商品
    private String personName;  //购买人
    private int buyNum = 0; //购买数量
    public BuyGoods(Person person){
        this.personName = person.name;
        this.buyNum = person.buyNum;
    }
    //获取当前库存
    static int getGoodsNum(){
        return goodsNum;
    }
    //购买商品
    synchronized static void buyGoods(String threadName,int num) {
        System.out.println("执行时间:"+new Date()+";"+threadName+" 准备购买 "+num+" 件商品,当前库存:"+getGoodsNum());
        if(goodsNum==0){    //商品买完时不再出售
            throw new NullPointerException();
        }
        goodsNum--;
        System.out.println("执行时间:"+new Date()+";"+threadName+" 开始购买第 "+num+" 件商品");
        System.out.println("执行时间:"+new Date()+";"+threadName+"购买第 "+num+" 件商品完成,当前库存:"+getGoodsNum());
        System.out.println();
    }
    @Override
    public void run() {
        Thread thread = Thread.currentThread();   //Thread.currentThread() 获取当前对象线程的引用

        for(int i=1;i<=this.buyNum;i++){
            try{
                buyGoods(this.personName,i);
            }catch (NullPointerException e){
                System.out.println("商品已经售空,"+this.personName+",没有购买到第 "+i+" 件商品");
                break;
            }
        }
    }
}

/**
 * 购买人
 */
class Person{
    String name;    //姓名
    int buyNum;     //购买数量
    public Person(String name,int buyNum){
        this.name = name;
        this.buyNum = buyNum;
    }
}

public class AppRunnable{
    public static void main(String[] args) throws Exception{
        //创建三个购买人
        Person personA = new Person("小明",5);
        Person personB = new Person("小红",2);
        Person personC = new Person("小蓝",6);
        Thread thread1 = new Thread(new BuyGoods(personA));
        Thread thread2 = new Thread(new BuyGoods(personB));
        Thread thread3 = new Thread(new BuyGoods(personC));
        thread1.start();
        thread2.start();
        thread3.start();
        thread1.join();     //等待购买完成
        thread2.join();     //等待购买完成
        thread3.join();     //等待购买完成
        System.out.println("所有人购买完成,当前库存:"+ BuyGoods.getGoodsNum());
    }
}

控制台打印结果
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【准备购买】 1 件商品,当前库存:10
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【开始购买】第 1 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【购买完成】第 1 件商品,当前库存:9
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 1 件商品,当前库存:9
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 1 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 1 件商品,当前库存:8
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 2 件商品,当前库存:8
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 2 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 2 件商品,当前库存:7
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 3 件商品,当前库存:7
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 3 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 3 件商品,当前库存:6
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 4 件商品,当前库存:6
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 4 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 4 件商品,当前库存:5
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 5 件商品,当前库存:5
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 5 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 5 件商品,当前库存:4
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【准备购买】 6 件商品,当前库存:4
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【开始购买】第 6 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小蓝【购买完成】第 6 件商品,当前库存:3
执行时间:Tue Apr 14 11:29:30 CST 2020;小红【准备购买】 1 件商品,当前库存:3
执行时间:Tue Apr 14 11:29:30 CST 2020;小红【开始购买】第 1 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小红【购买完成】第 1 件商品,当前库存:2
执行时间:Tue Apr 14 11:29:30 CST 2020;小红【准备购买】 2 件商品,当前库存:2
执行时间:Tue Apr 14 11:29:30 CST 2020;小红【开始购买】第 2 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小红【购买完成】第 2 件商品,当前库存:1
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【准备购买】 2 件商品,当前库存:1
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【开始购买】第 2 件商品
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【购买完成】第 2 件商品,当前库存:0
执行时间:Tue Apr 14 11:29:30 CST 2020;小明【准备购买】 3 件商品,当前库存:0
商品已经售空,小明,没有购买到第 3 件商品
所有人购买完成,当前库存:0

3.Callable类 (java.util.concurrent 包)

同样以购买商品为例,Callable可以通过Future.get()方法获取call方法返回值,并且call()方法中的异常,我们可以向外抛出,而不是像Runnable中的run()方法,自行处理。

package MyCode;

import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class BuyGoods2 implements Callable {
    static int goodsNum = 10;     //初始化10件商品
    private String personName;  //购买人
    private int buyNum = 0; //购买数量
    public BuyGoods2(Person2 person){
        this.personName = person.name;
        this.buyNum = person.buyNum;
    }
    //获取当前库存
    static int getGoodsNum(){
        return goodsNum;
    }
    //购买商品
    synchronized static void buyGoods(String threadName,int num) {
        System.out.println("执行时间:"+new Date()+";"+threadName+"【准备购买】 "+num+" 件商品,当前库存:"+getGoodsNum());

        if(goodsNum==0){    //商品买完时不再出售
            throw new NullPointerException();
        }
        System.out.println("执行时间:"+new Date()+";"+threadName+"【开始购买】第 "+num+" 件商品");
        goodsNum--;
        System.out.println("执行时间:"+new Date()+";"+threadName+"【购买完成】第 "+num+" 件商品,当前库存:"+getGoodsNum());
    }
    @Override
    public Object call() {
        int i = 0;
        for(i=1;i<=this.buyNum;i++){
            try{
                buyGoods(this.personName,i);
            }catch (NullPointerException e){
                System.out.println("商品已经售空,"+this.personName+",没有购买到第 "+i+" 件商品");
                return ""+this.personName+" 购买了 " + (i-1) + "件商品";
            }
            try {
                Thread.sleep(100);  //休息一下
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return this.personName+" 购买了 " + (i-1) + "件商品";
    }
}

/**
 * 购买人
 */
class Person2{
    String name;    //姓名
    int buyNum;     //购买数量
    public Person2(String name,int buyNum){
        this.name = name;
        this.buyNum = buyNum;
    }
}

public class AppCallable{
    public static void main(String[] args) throws Exception{
        //创建三个购买人
        Person2 personA = new Person2("小明",4);
        Person2 personB = new Person2("小红",2);
        Person2 personC = new Person2("小蓝",5);
        BuyGoods2 buyGoods1 = new BuyGoods2(personA);
        BuyGoods2 buyGoods2 = new BuyGoods2(personB);
        BuyGoods2 buyGoods3 = new BuyGoods2(personC);
        // 创建一个执行任务的服务
        ExecutorService es = Executors.newFixedThreadPool(3);
        Future future1 = es.submit(buyGoods1);
        Future future2 = es.submit(buyGoods2);
        Future future3 = es.submit(buyGoods3);
        // 如果调用get方法,当前线程会等待任务执行完毕后才往下执行
        System.out.println("buyGoods1: " + future1.get());
        System.out.println("buyGoods2: " + future2.get());
        System.out.println("buyGoods3: " + future3.get());
        System.out.println("所有人购买完成,当前库存:"+BuyGoods2.getGoodsNum());
        // 停止任务执行服务
        es.shutdownNow();
    }
}

控制台执行结果
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【准备购买】 1 件商品,当前库存:10
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【开始购买】第 1 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【购买完成】第 1 件商品,当前库存:9
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 1 件商品,当前库存:9
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【开始购买】第 1 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【购买完成】第 1 件商品,当前库存:8
执行时间:Tue Apr 14 11:50:25 CST 2020;小红【准备购买】 1 件商品,当前库存:8
执行时间:Tue Apr 14 11:50:25 CST 2020;小红【开始购买】第 1 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小红【购买完成】第 1 件商品,当前库存:7
执行时间:Tue Apr 14 11:50:25 CST 2020;小红【准备购买】 2 件商品,当前库存:7
执行时间:Tue Apr 14 11:50:25 CST 2020;小红【开始购买】第 2 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小红【购买完成】第 2 件商品,当前库存:6
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【准备购买】 2 件商品,当前库存:6
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【开始购买】第 2 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【购买完成】第 2 件商品,当前库存:5
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 2 件商品,当前库存:5
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【开始购买】第 2 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【购买完成】第 2 件商品,当前库存:4
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【准备购买】 3 件商品,当前库存:4
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【开始购买】第 3 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【购买完成】第 3 件商品,当前库存:3
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 3 件商品,当前库存:3
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【开始购买】第 3 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【购买完成】第 3 件商品,当前库存:2
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【准备购买】 4 件商品,当前库存:2
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【开始购买】第 4 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小明【购买完成】第 4 件商品,当前库存:1
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 4 件商品,当前库存:1
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【开始购买】第 4 件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【购买完成】第 4 件商品,当前库存:0
buyGoods1: 小明 购买了 4件商品
buyGoods2: 小红 购买了 2件商品
执行时间:Tue Apr 14 11:50:25 CST 2020;小蓝【准备购买】 5 件商品,当前库存:0
商品已经售空,小蓝,没有购买到第 5 件商品
buyGoods3: 小蓝 购买了 4件商品
所有人购买完成,当前库存:0

3.线程安全性

上面程序中,使用了synchronized进行方法同步,来保证线程是安全的,同时间只能有一个人进行购买操作。那就线程安全性问题,记录一些知识点。

三个特性

原子性:不可分(同生共死);互斥访问,同一时刻只能有一个线程进行操作。(atomic,synchronized)
可见性:一个线程修改了共享变量后,其他线程可以即时观察到。(synchroinzed,volatile)
有序性:一个线程观察另一个线程的执行顺序时,由于指令重排序,观察结果是无序的。

volatile 和 synchronized、ReentrantLock

特性的比较
volatile:具有有序性、可见性,不具有原子性,锁变量;
synchronized:具有原子性、有序性、可见性,锁对象和类;
ReentrantLock:具有原子性、有序性、可见性;

可重入锁:可重复可递归调用,线程获取了对象锁之后,可以再次获取对象的锁,儿其他线程不可以(ReentrantLock、syntronized)。通过计数器实现,调用一次计数器+1,释放一次计数器-1。
不可重入锁:不可递归调用,调用就死锁。

synchronized锁
对象锁:一个线程得到对象锁,其他线程可以访问没有进行同步的方法或者代码,同步方法和非同步方法互不影响。
类锁:一个线程得到了类锁,其他线程调用该类就会被阻塞。

ReentrantLock: ReentrantLock构造器提供了一个boolean值,来选择是一个公平锁(fair)还是不公平锁(unfair)。公平锁按照线程请求锁的顺序一次获得锁。
需要手动声明来加锁和释放。(finally中声明释放锁)
synchronized:不公平锁,不保证下一个次是哪个线程获得锁,并且由编译器去保证加锁和释放动作。

你可能感兴趣的:(JAVA多线程与并发(基础))