线程基础知识(一)
   
    
    
    线程状态
   
    
     从操作系统层面5种状态
    
   
     
   
- 【初始状态】仅仅在语言层面创建了线程对象,还未与操作系统线程关联
- 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由cpu调度执行
- 
     【运行状态】
- 指获取了cpu时间片运行中的状态
- 当cpu时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程上下文切换
 
- 
     【阻塞状态】
- 如果调用了阻塞的API,如BIO读写文件,这是该线程实际不会调用CPU,会导致线程上下文切换,进入【阻塞状态】
- 等BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是:对于【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
 
- 【终止状态】表示线程已经执行完毕,生命周期就已经结束,不会再转换为其他状态
    
     从java语言层面六种状态
    
   
根据Thread.state的内部枚举决定
     
   
- 
【new】线程刚被创建,但是还没有调用start(); 
- 
【Runnable】当调用了start()方法之后。注意,java层面的【runnable】涵盖了操作系统层面【运行状态】【可运行状态】【IO阻塞状态】。 
- 
【Blocked】【Waiting】【Timed_Waiting】是java对阻塞状态的细分 - 【Blocked】:被动阻塞,线程争取时间片(锁)失败进入
- 【Waiting】:主动阻塞,程序员主动调用Wait(),join() 是线程进入阻塞状态
- 【Timed_Waiting】:主动阻塞,程序员主动调用Wait(long) ,sleep(long)是线程进入阻塞状态
 
- 
【Terminated】终止状态 
    
    
    华罗庚《统筹方法》
   
想泡壶茶喝。当时的情况是:开水没有;水壶要洗,茶壶、茶杯要洗;火已生了,茶叶也有了。怎么
办?
- 
洗好水壶,灌上凉水,放在火上;在等待水开的时间里,洗茶壶、洗茶杯、拿茶叶;等水开 了,泡茶喝。   
package com.test;
import lombok.extern.slf4j.Slf4j;
/**
 * @author Seven
 * @create 2021-10-19 22:29
 */
@Slf4j(topic = "c.Test5")
public class Test5 {
    public static void main(String[] args) {
        int time=0;
        Thread t1 = new Thread(() -> {
            log.debug("洗水壶");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("烧开水");
            try {
                Thread.sleep(15000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"老周");
        Thread t2 = new Thread(() -> {
            log.debug("洗茶壶");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("洗茶杯");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("拿茶叶");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("泡茶");
        }, "小周");
        t1.start();
        t2.start();
    }
}
11:21:38.970 [小周] DEBUG c.Test5 - 洗茶壶
11:21:38.970 [老周] DEBUG c.Test5 - 洗水壶
11:21:39.973 [老周] DEBUG c.Test5 - 烧开水
11:21:39.973 [小周] DEBUG c.Test5 - 洗茶杯
11:21:41.973 [小周] DEBUG c.Test5 - 拿茶叶
11:21:54.973 [小周] DEBUG c.Test5 - 泡茶
    
    
    线程安全问题
   
因为写的指令不能原子化,交错导致程序结果出错。
    
     成员变量和静态变量是否线程安全
    
   
- 如果它们没有被共享,则线程安全。
- 
     如果它们被共享了,更具他们的状态是否能被改变,又分为两种情况
- 如果只有读操作,则线程安全
- 如果有读写操作,则这段代码是临界区,需要考虑线程安全。
 
    
     局部变量是否线程安全
    
   
- 局部变量是线程安全的。
- 
     但局部变量引用的对象未必
- 如果该对象没有逃离方法的作用访问,他是线程安全的
- 如果该对象逃离党法的作用访问,需要考虑线程安全。
 
    
     局部变量线程安全分析
    
   
- 无共享,无伤害
public static void test(){
	int i=10;
	i++;
}
每个线程调用 test1() 方法时局部变量 i,会在每个线程的栈帧内存中被创建多份,因此不存在共享
     
   
- 当局部变量引用的是一个对象
先举一个成员变量的例子
class ThreadUnsafe {
    ArrayList<String> list = new ArrayList<>();
    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            // { 临界区, 会产生竞态条件
            method2();
            method3();
           
            // } 临界区
        }
    }
    private void method2() {
        list.add("1");
    }
    private void method3() {
        list.remove(0);
    }
}
执行
	static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
        ThreadUnsafe test = new ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                test.method1(LOOP_NUMBER);
            }, "Thread" + i).start();
        }
    }
     
   
将 list 修改为局部变量,引用不暴露给外部
class ThreadSafe {
    public final void method1(int loopNumber) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }
    private void method2(ArrayList<String> list) {
        list.add("1");
    }
    private void method3(ArrayList<String> list) {
        list.remove(0);
    }
}
分析:
list 是局部变量,每个线程调用时会创建其不同实例,没有共享
- 而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象
- method3 的参数分析与 method2 相同
     
   
暴露给外部
- 情况1:有其它线程调用 method2 和 method3
- 情况2:在 情况1 的基础上,为 ThreadSafe 类添加子类,子类覆盖 method2 或 method3 方法,即
class ThreadSafe {
    public final void method1(int loopNumber) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }
    private void method2(ArrayList<String> list) {
        list.add("1");
       
    }
    private void method3(ArrayList<String> list) {
        list.remove(0);
    }
}
class ThreadSafeSubClass extends ThreadSafe{
    @Override
    public void method3(ArrayList<String> list) {
        new Thread(() -> {
            list.remove(0);
        }).start();
    }
}
如果父类方法为public,final没有,即父类会被子类覆盖。则会出现线程安全
    
    
    常见的线程安全类
   
- String
- Integer
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent
这里的线程安全是指:多个线程调用它们同一个实例的方法,是线程安全的。他里面的方法是原子的,但组合的时候不一定线程安全。
String和Integer都是不可变类,都是线程安全的。
 
