阅读 19

多线程笔记(二)

线程状态

6个状态定义;

1.New:尚未启动的线程的线程状态

Thread thread = new Thread();
复制代码

2.Runnable:可运行线程的线程状态,等待CPU调度。

thread.start();
复制代码

3.Blocked:线程阻塞等待监视器锁定的线程状态。处于synchronized同步代码块或非法中被阻塞。

4.Waiting:线程等待的线程状态。不带timeout参数的方式调用

Object.wait、LockSupport.join、LockSupport.park.
复制代码

5.Timed Waiting:具有指定等待时间的等待线程的线程状态、下列带超时的方式:

Thread.sleep、Object.wait、Thread.join、LockSupport.parkNanos、LockSupport.parkUntil
复制代码

6.Terminated:终止线程的线程状态。线程正常完成执行或者出现异常。

终止线程

1.stop方法

public class Demo1 {
    public static void main(String[] args) throws Exception {
        MyThread my = new MyThread();
        my.start();
        my.sleep(1000);
        //stop方法
        //my.stop();
        //interrupt方法
        my.interrupt();
        while(my.isAlive()) {}
        my.point();
    }
}
class MyThread extends Thread{
    private int i = 0, j = 0;
    @Override
    public void run() {
        synchronized (this){
            ++i;
            try {
                Thread.sleep(10000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            ++j;
        }
        System.out.println("被释放");
    }
    public void point() {
        System.out.println("i:" + i);
        System.out.println("j:" + j);
    }
}
复制代码

结果

stop方法破坏了原子性,被废弃了

还有interrupt方法结果

不会破坏原子性

标志位方法 在while方法中

public class Demo2 {
    public volatile static boolean flag = true;

    public static void main(String[] args) throws InterruptedException{
        new Thread(()->{
            try {
                while(flag) {
                    System.out.println("运行中");
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        Thread.sleep(3000);
        flag = false;
        System.out.println("释放了");
    }
}
复制代码

线程封闭

多线程访问共享可变数时,涉及到线程间的数据同步的问题。

ThreadLocal来实现

线程级别变量,每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被消除,在并发模式下是绝对安全的变量。 用法ThreadLocal threadLocal = new ThreadLocal<>();

public class Demo3 {
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(()->{
            threadLocal.set("21312312");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadLocal.get());
        }).start();


        new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadLocal.get());
        }).start();
    }
}
复制代码

栈封闭

局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。

CPU缓存和内存屏障

CPU高速缓存中的数据是内存中的一小部分,但这一小部分是短时间内CPU即将访问的,当CPU存在大量访问时,可以从Cache中拿。

多级缓存

L1 Cache是CPU第一次高速缓存,她的容量非常小,一般为32-256KB,提高容量所带来的技术难度增加和成本增加非常大

L2 由于L1高速缓存的容量限制,为了再次提高CPU的运算速度,在CPU外部放置一高速存储器,即二级缓存,现在家庭用CPU容量最大的是4MB,而服务器和工作站上用CPU的L2高速缓存普遍大于4MB,有的高达8MB或者16MB。

L3 现在一般都是内置的。在拥有三级缓存的CPU中,只有约5%的数据需要从内存中调用,进 一步提升了CPU效率,同时提升大数据量计算时处理器的性能。具有较大L3缓存的处理器提供 更有效的文件系统缓存行为及较短消息和处理器队列长度。一般是多核共享一个L3缓存!

CPU在读取数据时,先在L1中寻找,再从L2寻找,再从L3寻找,然后是内存,再后是外存储器。

缓存同步协议

多CPU读取同样的数据进行缓存,进行不同运算之后,最终写入主内存以哪个CPU为准?

在这种高速缓存回写的场景下,有一个缓存一致性协议(MESI协议)多数CPU厂商对它 进行了实现。

多处理器时,单个CPU对缓存中数据进行了改动,需要通知给其他CPU。也就是意味着,CPU处理要控制自己的读写操作,还要监听其他CPU发出的通知,从而保证最终一致。

CPU性能优化手段-运行时指令重拍

指令重排的场景:当CPU写缓存时发现缓存区块正被其他CPU占用,为了提高CPU处理性能,可 能将后面的读缓存命令优先执行。

// 代码
x = 100;
y = z;
// 正常执行的三步骤
1、 将100写入x
2、 读取z的值
3、 将z值写入y
// 重排序后执行
1、读取z的值
2、将z值写入y
3、将100写入x
复制代码

as-if-serial语义:指令重排时,不管怎么重排序,单个线程的执行结果不能被改变。 换句话说,编译器和处理器不会对存在数据依赖关系的操作做重排序。

1、 CPU高速缓存下有一个问题: 缓存中的数据与主内存的数据并不是实时同步的,各CPU(或CPU核心)间缓存的数据也不是 实时同步。在同一个时间点,各CPU所看到同一内存地址的数据的值可能是不一致的。

2、 CPU执行指令重排序优化下有一个问题: 虽然遵守了as-if-serial语义,单仅在单CPU自己执行的情况下能保证结果正确。 多核多线程中,指令逻辑无法分辨因果关联,可能出现乱序执行,导致程序运行结果错误

内存屏障

处理器提供了两个内存屏障指令(Memory Barrier)用于解决上述两个问题:

写内存屏障(Store Memory Barrier):在写指令后插入Store Barrier,能让写入缓存中的 最新数据更新写入主内存,让其他线程可见。 强制写入主内存,这种显示调用,CPU就不会因为性能考虑而去对指令重排。

读内存屏障(Load Memory Barrier):在读指令前插入Load Barrier,可以让高速缓存中的 数据失效,强制从新从主内存加载数据。 强制读取主内存内容,让CPU缓存与主内存保持一致,避免了缓存导致的一致性问题