Java 线程基础

volatile

Java 线程基础_第1张图片

happens-before原则

1、单一线程原则 在一个线程内,在程序前面的操作线性发生于后面的操作

2、管程锁定原则

一个unlock操作先行发生于后面对同一个锁的lock操作

3、volatile变量原则

对一个volatile变量的写操作先行发生于后面对变量的读操作

4、线程启动规则

Thread对象的start()方法调用先行发生于此线程的每一个动作

5、线程加入规则

Thread对象的结束先行发生于join()方法返回

6、线程中断规则

对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过interrupted()方法检测到是否有中断行为发生

7、对象终结规则

一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始

8、传递性

如果操作A先行于操作B发生,操作B先行发生于操作C,则操作A先行发生于操作C。

线程安全

1、不可变

不可变的对象一定是线程安全的
不可变的类型:

  • final关键字修饰的基本数据类型
  • String
  • 枚举类
  • Number的部分子类,如Long和Double等包装类型,但AtomicInteger和AtomicLong是可变的

对于集合类型,使用Collections.unmodifiableXXX()可以获取一个不可变的集合

Map<String,Integer> map = new HashMap<>();
Map<String,Integer> unmodifyableMap = Collections.unmodifiableMap(map);
unmodifyableMap.put("key",0);

上述代码抛出异常:UnsupportedOperationException

2、绝对线程安全

不管运行时环境如何,调用者都不需要任何额外的同步措施

3、相对线程安全

在一些特定的场合需要额外的同步手段来保证线程安全

4、线程兼容

线程兼容是指对象本身并不是线程安全的,但是可以通过在调用端正确使用同步手段来保证对象在并发环境中可以安全得使用。
平常说线程不安全,大部分指该种情况

5、线程对立

不管怎么样都没法多线程运行的代码

线程安全的实现方法

1、互斥同步

synchronized 和 ReentrantLock

2、非阻塞同步

(1)CAS

比较并交换Compare-and-Swap
CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。

(2)AtomicInteger

AtomicInteger,其中的 compareAndSet() 和 getAndIncrement() 等方法都使用了 Unsafe 类的 CAS 操作

(3)ABA

如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。

J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题,它可以通过控制变量值的版本来保证 CAS 的正确性。大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。

3、无同步方案

(1)栈封闭

多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。

(2)线程本地存储

如果一段代码中所需要的数据必须与其它代码共享,那就看这些共享数据的代码是否能保证在同一个线程中执行。
可以使用 java.lang.ThreadLocal 类来实现线程本地存储功能。
Java 线程基础_第2张图片

public class ThreadLocalExample1 {
    public static void main(String[] args) {
        ThreadLocal threadLocal1 = new ThreadLocal();
        ThreadLocal threadLocal2 = new ThreadLocal();
        Thread thread1 = new Thread(() -> {
            threadLocal1.set(1);
            threadLocal2.set(1);
        });
        Thread thread2 = new Thread(() -> {
            threadLocal1.set(2);
            threadLocal2.set(2);
        });
        thread1.start();
        thread2.start();
    }
}
(3)可重入代码(Reentrant Code)

这种代码也叫做纯代码(Pure Code),可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身)

线程基础

线程状态转换

Java 线程基础_第3张图片

新建(new)

创建后未启动

可运行(runnable)

可能正在运行 也可能正在等待CPU时间片

阻塞(blocking)

等待获取一个排他锁,如果其它线程释放了锁就会结束此状态

无限期等待(waiting)

等待其它线程显式的唤醒,否则不会被分配CPU时间片
Java 线程基础_第4张图片

限期等待(timed waiting)

无需等待其它线程唤醒,在一定时间后自动唤醒
使用Thread.sleep()进入 称为使一个线程睡眠
使用Object.wait()进入 称为挂起一个线程
Java 线程基础_第5张图片

死亡(terminated)

线程结束

线程使用方式

  • 实现Runnable接口
  • 实现Callable接口
  • 继承Thread类

实现Runnable接口

需要实现run()方法 调用start()来启动线程

public class MyRunnable implements Runnable{
    private String message;

    public void run() {
        System.out.println(message);
    }

    public MyRunnable(String message) {
        this.message = message;
    }
}

实现Callable接口

与Runnable相比,Callable可以有返回值,返回值通过FutureTask进行封装

public class MyCallable implements Callable<Integer> {
    public Integer call() {
        return 123;
    }
}

继承Thread类

同样需要实现run()方法

public class MyThread extends Thread {
    public void run() {
        // ...
    }
}

使用匿名内部类和lambda表达式

public class CreatingThread03 {
    public static void main(String[] args) {
        // Thread匿名类,重写Thread的run()方法
        new Thread() {
            @Override
            public void run() {
                System.out.println(getName() + " is running");
            }
        }.start();

        // Runnable匿名类,实现其run()方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " is running");
            }
        }).start();
    }
}

lambda表达式更简单,因为只需要实现一个run方法,lambda表达式可以进一步简写
注:lambda写法实质上是匿名实现了runable接口

new Thread(()->{
            System.out.println(Thread.currentThread().getName() + " is running");
        }).start();

基础线程机制

Executor

Executor管理多个异步任务的执行

  1. CacheedThreadPool:一个任务创建一个线程
  2. FixedThreadPool:所有任务只能使用固定大小的线程
  3. SingleThreadExecutor:相当于大小为1的FixedThreadPool
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0;i < 5;i++) {
    executorService.execute(new MyRunnable("你好" + i));
}
executorService.shutdown();
Daemon

守护线程是程序运行时在后台提供服务的线程
使用setDaemon()将一个线程设置为守护线程

sleep()

Thread.sleep(millisec)方法会休眠当前正在执行的线程,单位ms

线程中断

一个线程执行完成之后会自动结束,如果在运行中结束会抛出异常

InterruptedException

通过调用interrupt()来中断线程,如果改线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出InterruptedException

interrupted()

如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。

但是调用 interrupt() 方法会设置线程的中断标记,此时调用 isinterrupted() 方法会返回 true。因此可以在循环体中使用 isinterrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。

Executor的中断操作

调用Executor的shutdown()方法会等待线程都执行完毕后再关闭,但是如果调用的是shutdownNow()方法,则相当于调用每个线程的interrup()方法

线程互斥同步

java有两种锁机制来控制多个线程对共享资源的互斥访问,第一个是JVM实现的synchronized 另一个是JDK实现的ReentrantLock

synchronized

1、同步一个代码块

public void func() {
    synchronized (this) {
        // ...} 
}

它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步

SynchronizedExample e1 = new SynchronizedExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> e1.func1());
executorService.execute(() -> e1.func1());

上面这种情况,如果func1()拥有同步,则两个线程不会同时进行

SynchronizedExample e1 = new SynchronizedExample();
SynchronizedExample e2 = new SynchronizedExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> e1.func1());
executorService.execute(() -> e2.func1());

这样则会同时进行
2、同步一个方法

public synchronized void func () {
    // ...
}

和同步代码块一样,作用于同一个对象
3、同步一个类

public void func () {
    synchronized (SynchronizedExample.class) {
        // ... }
}

作用于整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步
4、同步一个静态方法

public synchronized static void fun() {
    // ...
}

作用于整个类

ReentrantLock

ReentrantLock是java.util.concurrent中包含的锁

public class LockExample {
    private Lock lock = new ReentrantLock();

    public void func () {
        lock.lock();
        try {
            for (int i =0 ; i < 10 ; i ++) {
                System.out.println(i + " ");
            }
        } finally {
            lock.unlock();
        }
    }
}
LockExample lockExample = new LockExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> lockExample.func());
executorService.execute(() -> lockExample.func());

该锁也是对同一个对象而言的,对于不同对象,仍然是同时进行的

线程之间的协作

join()

如果在一个线程中 调用另外一个线程的join()方法 会将当前线程挂起,直到目标线程结束

wait() notify() notifyAll()

调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。

它们都属于 Object 的一部分,而不属于 Thread。

只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateExeception

await() signal() signalAll()

java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活

你可能感兴趣的:(java,java,开发语言)