java-多线程与锁

概述:

  多线程的使用,锁,线程池,更多的锁。

1:多线程的使用。

  1.1:使用多线程需要保证:原子性,可见性,有序性。

  1.2:启动多线程的方法:(不做 多余赘述,写两种常见的方式)

    继承Thread类,重写run方法。

    实现Runnable接口,重写run方法。

  1.3:这里引出一个方法  join()。(一个执行先后的方法)

    假设开启了两个线程T1,T2,如果同时 start 之后T1,T2的执行顺序比较随机,如果在T2的 run 方法中写上T1.join(),那么就是说T2在T1执行完之后执行。

2:高并发保证线程安全:锁。

  2.1:分类:synchronized(内置锁),lock。

  2.2:区别:lock需要unlock方法手动解锁,synchronized在执行完锁的部分或者异常就是释放锁。

  2.3:对于synchronized的使用。(解决的是高并发情况多线程公用一个变量引起的线程安全问题)

    2.3.1:同步方法。

public class Test {

    /**
     * 同步方法
     * 说明:锁的对象:这个类,即Test类,也叫this锁。
     */
    public synchronized void syncTest() {
        // TODO Auto-generated method stub

    }
}

    2.3.2:同步代码块。

public class Test {
    
    private Object object = new Object();

    /**
     * 同步代码块
     * 说明:锁的对象:
     *         这个类,即Test类,
     *         也可以的一对象。
     * 以下两种皆可
     */
    public void syncTest() {
        //锁的类
        synchronized (this) {
            
        }
        //锁的对象
        synchronized (object) {
            
        }
    }
}

    2.3.3:静态同步代码。

public class Test {
    
    /**
     * 同步代码块
     * 说明:锁的对象:
     *         这个类(即Test类)的字节码文件,
     */
    public static void syncTest() {
        synchronized (Test.class) {
            
        }
    }
}

  2.4:延伸的概念。

    2.4.1:修饰词 threadLocal(解决的是多线程之间公用的变量隔离出来使用,不影响其他线程)

      概述:将共享变量私有化。具体使用网上有很多的帖子。

    2.4.2:修饰词 volatile (修改变量,快速写入主内存)

      解释说明:首先了解共享内存模型(jmm),即一个变量在内存中存于主内存,还有一块虚拟的私有内存(虚拟的,不存在的)。

        内存结构是我们常说的 jvm。

        普通变量:修改的是自己私有内存的变量,然后再同步到主内存中。

        被volatile 修饰的变量:1:会快速的被写入主内存,然后其他线程使用时候直接从主内存中拿取。我的理解是不是volatile 修饰的变量

                  一直操作的是主内存中的呢?(没有看过源码进行深入的研究,只是一个疑问,不过不太影响对这个的理解)

                  2:volatile 可以禁止指令重排序(指令重排是cpu为了提高程序的执行性能的一种方法)。

      对比synchronized:

        1:优点,volatile 不会造成线程阻塞,性能方面要由于锁。volatile 可以禁止指令重排序。

        2:缺点,volatile 是可以保证可见性,有序性,但是不能保证原子性,所以不能取代synchronized。

        总结:volatile 是轻量级的synchronized,如果有对变量进行操作等场景使用synchronized。

      使用场景:

 

        单例模式是个典型的应用。

        说明:变量需要volition修饰,变量的构造赋值部分是用synchronized修饰的,目的是T1进入了synchronized部分之后,

           如果对象还未完全构建成功,那么T2进入,发现判断的对象不为null,这样会存在问题。

    2.4.3:wait(),notify(),notifyAll()应用。(wait让线程进入等待状态,notify唤醒当前线程,notifyAll唤醒所以线程)

      假设场景 :假如对一个共享变量,T1进行set值,T2进行get值。我们要保证:T2 get值时候,变量里面要有值的,T1 set值时候变量是无指的。

           T1与T2中使用wait(),notify()组合。

      对比 sleep() :sleep等待多长时间继续执行,不会释放锁。wait会释放锁。

3:线程池。

  3.1:为何使用线程池:

    频繁的以1的描述方式开启线程,关闭线程,很消耗性能,所以引入线程池。

  3.2:线程池种类:

    newCachedThreadPool:缓存线程池

    newFixedThreadPool:定长线程池

  3.3:spring项目中的应用。(自定义一个线程池)

@Configuration
@EnableAsync    //开启异步调用
public class Test {
    
    /**
     * 详细的参数解释可参见,里面有相应的线程池参数配置规则
     * https://www.cnblogs.com/waytobestcoder/p/5323130.html
     * @return
     */
    @Bean("pool1")
    public Executor threadPool1() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        //线程核心数目
        threadPoolTaskExecutor.setCorePoolSize(10);
        //核心线程,超时是否关闭,一般缓存线程池默认60S,其他线程池设置的是0S,我们默认这个也是60S
        threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);
        //最大线程数
        threadPoolTaskExecutor.setMaxPoolSize(10);
        //配置队列大小
        threadPoolTaskExecutor.setQueueCapacity(50);
        //配置线程池前缀,相当于@Bean("pool1")已经指定了使用的线程池名称
//        threadPoolTaskExecutor.setThreadNamePrefix("pool1");
        //配置拒绝策略
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        //数据初始化
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
    
}
@Async("pool1")
public void test(){
        
}

  如果调用了test(),就是使用了线程池pool1。

4:更多的锁

  4.1:乐观锁,悲观锁,重入锁,自旋锁,分布式锁,CAS无锁

  CAS概述:三个值,初始值,期望值,新值。如果初始值=期望值,那么初始值=新值,否则代表有其他线程已经改了,再重新取值对比。

  

posted @ 2020-01-07 19:46  bug修复中  阅读(393)  评论(0编辑  收藏  举报