Java并发编程学习笔记

前言

LZ看的是高洪岩的《Java多线程编程核心技术》和《Java并发编程核心方法与框架》,都两本书都是偏入门的书籍,《Java并发编程的艺术》和《Java并发编程实战》是业内公认的好书

Java多线程技能

实现多线程编程的方式主要有两种,继承Thread类和实现Runnable接口

线程启动顺序与start()方法执行顺序无关

共享自定义线程类中的实例变量

public class MyThread extends Thread{

    private int count = 3;

    @Override
    public synchronized void run() {
        count--;
        System.out.println(this.currentThread().getName() + count);
    }
}
public class Run {

    public static void main(String[] args) {

        MyThread myThread = new MyThread();
        Thread a = new Thread(myThread, "a");
        Thread b = new Thread(myThread, "b");
        Thread c = new Thread(myThread, "c");
        //a2
        //b1
        //c0
        a.start();
        b.start();
        c.start();
    }
}

停止线程

有3种方法可以停止当前运行的线程

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止(在run方法中设置标记变量,使用while循环判断这个标志是否符合条件)
  2. 使用stop方法强行终止线程,但这个是作废的方法
  3. 使用interrupt方法中断线程
    判断线程是否是停止状态,Thread类中提供了两种方法

static boolean interrupted()
boolean isInterrupted()

interrupted() Tests whether the current thread has been interrupted,执行后具有将状态标志清除为false的功能
isInterrupted() Tests whether this thread has been interrupted,不清除状态标志

public class Run {

    public static void main(String[] args) {

        Thread.currentThread().interrupt();
        //是否停止? true
        System.out.println("是否停止? " + Thread.interrupted());
        //是否停止? false
        System.out.println("是否停止? " + Thread.interrupted());
    }
}
public class MyThread extends Thread{

    @Override
    public synchronized void run() {
        for (int i=0; i<5000; i++) {
            System.out.println(i);
        }
    }
}
public class Run2 {

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

        MyThread myThread = new MyThread();
        myThread.start();
        Thread.sleep(20);
        myThread.interrupt();
        System.out.println("是否停止? " + myThread.isInterrupted());
        System.out.println("是否停止? " + myThread.isInterrupted());
    }
}

这里写图片描述这里写图片描述
对线程调用interrupt,并不能直接停止线程的运行,会等run方法运行完毕
interrupt+异常停止线程

public class MyThread extends Thread{

    @Override
    public void run() {

        for (int i=0; i<50000; i++) {
            if (this.isInterrupted()) {
                System.out.println("中断线程");
                break;
            }
            System.out.println(i);
        }
        System.out.println("for循环语句后");
    }
}

Java并发编程学习笔记_第1张图片
能中断for循环中的,for循环之后的还会执行

public class MyThread extends Thread{

    @Override
    public void run() {

        try {
            for (int i=0; i<50000; i++) {
                if (this.isInterrupted()) {
                    System.out.println("中断线程");
                    throw new InterruptedException();
                }
                System.out.println(i);
            }
            System.out.println("for循环语句后");
        } catch (InterruptedException e) {
            System.out.println("进入catch方法");
        }
    }
}

Java并发编程学习笔记_第2张图片
这样就能中断线程了

先调用sleep,后调用interrupt或者先调用interrupt再调用sleep会产生异常

interrupt+return停止异常

public class MyThread extends Thread{

    @Override
    public void run() {

        while (true) {
            if (this.isInterrupted()) {
                System.out.println("停止了");
                return;
            }
            System.out.println(System.currentTimeMillis());
        }
    }
}

这里写图片描述

yield方法

yield方法的作用是放弃当前的CPU资源,让其他任务去占用CPU执行时间。但放弃时间不确定,有可能刚放弃,马上又获得CPU时间片

public class MyThread extends Thread{

    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        for (int i=0; i<500000; i++) {
            //Thread.yield();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("运行时间" + (endTime - beginTime));
    }
}

未打开注释:运行时间3
打开注释:运行时间59

对象及变量的并发访问

synchronized同步方法

如果多个线程共同访问1个对象中的实例变量,则有可能出现“非线程安全”问题

public class SelfPrivateNum {

    private int num = 0;
    public void add(String userName) {
        try {
            if (userName.equals("a")) {
                num = 100;
                Thread.sleep(1000);
            } else if (userName.equals("b")) {
                num = 200;
            }
            System.out.println(userName + " " + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class MyThreadA extends Thread{

    private SelfPrivateNum selfPrivateNum;

    public MyThreadA(SelfPrivateNum selfPrivateNum) {
        this.selfPrivateNum = selfPrivateNum;
    }

    @Override
    public void run() {
        selfPrivateNum.add("a");
    }
}

MyThreadB是在run方法中调用selfPrivateNum.add(“b”),其他和MyThreadA都一样

public class Run {

    public static void main(String[] args) {

        SelfPrivateNum selfPrivateNum = new SelfPrivateNum();
        MyThreadA myThreadA = new MyThreadA(selfPrivateNum);
        //b 200
        myThreadA.start();
        //a 200
        MyThreadB myThreadB = new MyThreadB(selfPrivateNum);
        myThreadB.start();
    }
}

调用用关键字synchronized声明的方法一定是排队运行的,但却可以异步调用非synchronized类型的方法
关键字synchronized具有锁重入的功能,当一个线程得到一个对象锁后,可以再次请求得到对象锁。因此在一个synchronized方法内部调用本类的synchronized方法时,是可以得到锁的

public class Service {

    public synchronized void service1() {
        System.out.println("service1");
        service2();
    }

    public synchronized void service2() {
        System.out.println("service2");
    }
}
public class MyThread extends Thread{

    @Override
    public void run() {
        Service service = new Service();
        service.service1();
    }
}
public class Run {

    public static void main(String[] args) {

        MyThread myThread = new MyThread();
        //service1
        //service2
        myThread.start();
    }
}

当存在继承关系时,子类可以通过“可重入锁”调用父类的同步方法


当出现异常时,锁被自动释放,子类从父类中继承的同步方法不具有同步性,必须自己加上synchronized

synchronized同步代码块

volatile关键字

解决异步死循环

public class RunThread extends Thread{

    private boolean isRunning = true;

    public boolean isRunning() {
        return isRunning;
    }

    public void setRunning(boolean running) {
        isRunning = running;
    }

    @Override
    public void run() {
        System.out.println("进入run方法");
        while (isRunning) {

        }
        System.out.println("线程被停止");
    }
}
public class Run {

    public static void main(String[] args) {

        try {
            RunThread thread = new RunThread();
            thread.start();
            Thread.sleep(1000);
            thread.setRunning(false);
            System.out.println("已经赋值为false");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这里写图片描述

线程被停止这句话始终没有被输出,是因为private boolean isRunning = true;存在于公共堆栈及线程的私有堆栈中,线程一直在私有堆栈中取得isRunning的值为true。而代码thread.setRunning(false);虽然被执行,更新的确实公共堆栈中的isRunning变量值false,所以一直就是死循环状态

Java并发编程学习笔记_第3张图片

private volatile boolean isRunning = true;

将isRunning用volatile修饰,结果如图
这里写图片描述
是因为使用volatile关键字,强制从公共内存中读取变量的值
Java并发编程学习笔记_第4张图片
synchronized和volatile的对比

  1. volatile是线程同步的轻量级实现,性能比synchronized要好,volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块
  2. 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞
  3. volatile能保证数据的可见性,但不能保证原子性,而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。
  4. volatile解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性

volatile非原子特性

public class MyThread extends Thread{

    public static volatile int count;

    private static void addCount() {
        for (int i=0; i<100; i++) {
            count++;
        }
        System.out.println(count);
    }

    @Override
    public void run() {
        addCount();
    }
}
public class Run {

    public static void main(String[] args) {

        MyThread[] myThreads = new MyThread[100];
        for (int i=0; i<100; i++) {
            myThreads[i] = new MyThread();
        }
        for (int i=0; i<100; i++) {
            myThreads[i].start();
        }
    }
}

如果能保证原子性,则最后输出应该是10000
Java并发编程学习笔记_第5张图片
使用关键字volatile时出现非线程安全的原因
变量在内存中工作的过程如下
Java并发编程学习笔记_第6张图片

除了在i++操作时使用synchronized关键字实现同步外,还可以使用AtomicInteger原子类进行实现

synchronized代码块有volatile同步的功能

public class Service {

    private boolean isContinueRun = true;

    public void runMethod() {
        while (isContinueRun) {

        }
        System.out.println("已经停下来了");
    }
    public void stopMethod() {
        isContinueRun = false;
    }

}
public class ThreadA extends Thread{

    private Service service;

    public ThreadA(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.runMethod();
    }
}
public class ThreadB extends Thread{

    private Service service;

    public ThreadB(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.stopMethod();
    }
}
public class Run {

    public static void main(String[] args) {

        try {
            Service service = new Service();
            ThreadA a = new ThreadA(service);
            ThreadB b = new ThreadB(service);
            a.start();
            Thread.sleep(1000);
            b.start();
            System.out.println("已经发出停止命令");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

这里写图片描述
得到这个结果时各线程间的数据值没有可视性造成的,而关键字synchronized可以具有可视性

public class Service {

    private boolean isContinueRun = true;

    public void runMethod() {
        while (isContinueRun) {
            String anyString = new String();
            synchronized (anyString) {

            }
        }
        System.out.println("已经停下来了");
    }
    public void stopMethod() {
        isContinueRun = false;
    }

}

将Service类更改为如上后,再次运行Run类,结果为
Java并发编程学习笔记_第7张图片

线程间通信

wait,notify,notifyAll都是Object类的方法,方法wait和notify要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获得该对象锁,要等到notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。

public class Test1 {

    public static void main(String[] args) {

        try {
            String newString = new String("");
            newString.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Java并发编程学习笔记_第8张图片
出现异常的原因是没有“对象监视器”,也就是没有同步加锁

public class MyThread1 extends Thread{

    private Object lock;

    public MyThread1(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                System.out.println("开始 wait time = " + System.currentTimeMillis());
                lock.wait();
                System.out.println("结束 wait time = " + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class MyThread2 extends Thread{

    private Object lock;

    public MyThread2(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("开始 notify time = " + System.currentTimeMillis());
            lock.notify();
            System.out.println("结束 nofity time = " + System.currentTimeMillis());
        }
    }
}
public class Run {

    public static void main(String[] args) {

        try {
            Object obj = new Object();
            MyThread1 myThread1 = new MyThread1(obj);
            myThread1.start();
            Thread.sleep(3000);
            MyThread2 myThread2 = new MyThread2(obj);
            myThread2.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Java并发编程学习笔记_第9张图片

wait被执行后,锁被自动释放,但执行完notify方法,锁却不自动释放,必须执行完notify方法所在的同步synchronized代码块后才释放锁

将MyThread2的代码更改如下,再次运行

public class MyThread2 extends Thread{

    private Object lock;

    public MyThread2(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                System.out.println("开始 notify time = " + System.currentTimeMillis());
                lock.notify();
                Thread.sleep(3000);
                System.out.println("结束 nofity time = " + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Java并发编程学习笔记_第10张图片
可以看到开始通知和结束通知之间隔了3秒

调用notify一次只随机通知一个线程进行唤醒,为了唤醒所有线程,可以使用notifyAll方法

生产者消费者模式实现

一生产一消费:操作值

public class P implements Runnable{

    private String lock;

    public P(String lock) {
        this.lock = lock;
    }

    public void setValue() {
        try {
            synchronized (lock) {
                if (!ValueObject.vlaue.equals("")) {
                    lock.wait();
                }
                String value = System.currentTimeMillis() + "";
                System.out.println("set的值为 " + value);
                ValueObject.vlaue = value;
                lock.notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            setValue();
        }
    }
}
public class C implements Runnable{

    private String lock;

    public C(String lock) {
        this.lock = lock;
    }

    public void getValue() {
        try {
            synchronized (lock) {
                if (ValueObject.vlaue.equals("")) {
                    lock.wait();
                }
                System.out.println("get的值为 " + ValueObject.vlaue);
                ValueObject.vlaue = "";
                lock.notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            getValue();
        }
    }
}
public class ValueObject {
    public static String vlaue = "";
}
public class Run {

    public static void main(String[] args) {
        String lock = new String("");
        P p = new P(lock);
        C c = new C(lock);
        Thread threadp = new Thread(p);
        Thread threadc = new Thread(c);
        threadp.start();
        threadc.start();
    }
}

多生产多消费:操作值-假死
将上面代码中的P类和C类的if语句变为while语句

while (!ValueObject.vlaue.equals("")) {
    lock.wait();
    System.out.println(Thread.currentThread().getName() + " waiting");
}

Run更改如下

public class Run {

    public static void main(String[] args) throws InterruptedException {
        String lock = new String("");
        P p = new P(lock);
        C c = new C(lock);
        Thread[] threadp = new Thread[2];
        Thread[] threadc = new Thread[2];
        for (int i=0; i<2; i++) {
            threadp[i] = new Thread(p, "生产者" + (i + 1));
            threadp[i].start();
            threadc[i] = new Thread(c, "消费者" + (i + 1));
            threadc[i].start();
        }
        Thread.sleep(5000);
        Thread[] threads = new Thread[Thread.currentThread().getThreadGroup().activeCount()];
        Thread.currentThread().getThreadGroup().enumerate(threads);
        for (int i = 0; i < threads.length; i++) {
            System.out.println("状态" + threads[i].getName() + " " + threads[i].getState());
        }
    }
}

Java并发编程学习笔记_第11张图片
有可能出现假死的情况,原因是有可能出现“生产者”唤醒“生产者”,或者“消费者”唤醒“消费者”的情况,解决的办法就是将P类和C类的notify改成notifyAll方法

方法join的使用

public class MyThread extends Thread{

    @Override
    public void run() {
        try {
            Thread.sleep(3000);
            System.out.println("子线程运行结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Run {

    public static void main(String[] args) {

        MyThread myThread = new MyThread();
        myThread.start();
        //Thread.sleep();有时候我不知道子线程运行多长时间,所以不确定值
        System.out.println("主线程运行完毕");
    }
}

Java并发编程学习笔记_第12张图片
将Run类更改为如下

public class Run {

    public static void main(String[] args) {

        try {
            MyThread myThread = new MyThread();
            myThread.start();
            myThread.join();
            //Thread.sleep();有时候我不知道子线程运行多长时间,所以不确定值
            System.out.println("主线程运行完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Java并发编程学习笔记_第13张图片

在join过程中,如果当前线程对象被中断,则当前线程出现异常
join(long)中的参数时设定等待时间,超时就不再等待,方法join(long)的功能是在内部使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点,而Thread.sleep(long)方法却不释放锁

ThreadLocal的使用

public class Tools {
    public static ThreadLocal t1 = new ThreadLocal();
}
public class ThreadA extends Thread{

    @Override
    public void run() {
        try {
            for (int i=0; i<10; i++) {
                Tools.t1.set("ThreadA " + i);
                Thread.sleep(200);
                System.out.println("ThreadA get value = " + Tools.t1.get());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Run {

    public static void main(String[] args) {

        try {
            ThreadA threadA = new ThreadA();
            threadA.start();
            for (int i=10; i<20; i++) {
                Tools.t1.set("Main " + i);
                Thread.sleep(200);
                System.out.println("Main get value = " + Tools.t1.get());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Java并发编程学习笔记_第14张图片

Lock的使用

public class MyService {

    private Lock lock = new ReentrantLock();
    public void testMethod() {
        lock.lock();
        for (int i=0; i<5; i++) {
            System.out.println("Thread Name " + Thread.currentThread().getName() + " " + i);
        }
        lock.unlock();
    }
}
public class MyThread extends Thread{

    MyService myService;

    public MyThread(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run() {
        myService.testMethod();
    }
}
public class Run {

    public static void main(String[] args) {
        MyService myService = new MyService();
        MyThread myThread = new MyThread(myService);
        MyThread myThread1 = new MyThread(myService);
        myThread.start();
        myThread1.start();
    }
}

Java并发编程学习笔记_第15张图片
顺序打印

调用lock.lock()代码的线程就持有了“对象监视器”,其他线程只有等待锁被释放时再次争抢

使用Condition实现等待通知

public class MyService {

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void await() {
        try {
            //condition.await()方法调用之前调用lock.lock()代码获得同步监视器
            lock.lock();
            System.out.println("await时间为 " + System.currentTimeMillis());
            condition.await();
        } catch (InterruptedException e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void signal() {
        try {
            lock.lock();
            System.out.println("signal时间为 " + System.currentTimeMillis());
            condition.signal();
        } finally {
            lock.unlock();
        }

    }
}
public class ThreadA extends Thread{

    private MyService myService;

    public ThreadA(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run() {
        myService.await();
    }
}
public class Run {

    public static void main(String[] args) throws InterruptedException {
        MyService myService = new MyService();
        ThreadA threadA = new ThreadA(myService);
        threadA.start();
        Thread.sleep(3000);
        myService.signal();
    }
}

Java并发编程学习笔记_第16张图片

Object Condition
wait await
wait(long timeout) await(long time,TimeUnit unit)
notify signal
notify signalAll

使用多个Condition实现通知部分线程

public class MyService {
    private Lock lock = new ReentrantLock();
    public Condition conditionA = lock.newCondition();
    public Condition conditionB = lock.newCondition();

    public void awaitA() {
        try {
            lock.lock();
            System.out.println("begin awaitA 时间为" + System.currentTimeMillis() + " ThreadName = " + Thread.currentThread().getName());
            conditionA.await();
            System.out.println("end awaitA 时间为" + System.currentTimeMillis() + " ThreadName = " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        try {
            lock.lock();
            System.out.println("begin awaitB 时间为" + System.currentTimeMillis() + " ThreadName = " + Thread.currentThread().getName());
            conditionB.await();
            System.out.println("end awaitB 时间为" + System.currentTimeMillis() + " ThreadName = " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalAll_A() {
        try {
            lock.lock();
            System.out.println("signalAll_A时间为 " + System.currentTimeMillis() + " ThreadName = " + Thread.currentThread().getName());
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }
    }

}
public class ThreadA extends Thread{

    private MyService myService;

    public ThreadA(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run() {
        myService.awaitA();
    }
}
public class Run {

    public static void main(String[] args) {

        try {
            MyService myService = new MyService();
            ThreadA threadA = new ThreadA(myService);
            threadA.setName("A");
            threadA.start();
            ThreadB threadB = new ThreadB(myService);
            threadB.setName("B");
            threadB.start();
            Thread.sleep(3000);
            myService.signalAll_A();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ThreadB线程调用MyService的awaitB方法
Java并发编程学习笔记_第17张图片
因为B线程没有结束,所以始终没有停止

Future和Callable的使用

Callable接口和Runnable接口的区别

  1. Callable接口的call()方法可以有返回值,而Runnable接口的run()方法没有返回值
  2. Callable接口的call()方法可以声明抛出异常,而Runnable接口的run()方法不可以声明抛出异常

线程池execute()和submit()的区别

  1. execute()方法没有返回值,submit()方法可以有返回值
  2. 2.

参考博客

生产者消费者模式-Java实现
[1]https://www.cnblogs.com/chentingk/p/6497107.html

你可能感兴趣的:(Java,EE)