JAVA多线程高级操作

目录

一,线程的状态

二,线程池

1,以前写多线程的弊端

2,线程池的实现步骤

3,代码实现

a:创建默认个数的线程池

b:创建指定个数的线程池

c:自己创建线程池

三,Volatile

四,原子性

1,AtomicInteger原子型类:

 2,使用原子性解决送冰淇凌问题

 3,AtomicInteger原理

4,Synchroized和CSA的区别

五,并发工具类

1,Hashtable

2,ConcurrentHashMap

3,CountDownLatch类

4,Semaphore类


一,线程的状态

线程的状态分为,新建状态,就绪状态,运行状态,死亡状态

当线程通过start()方法到达就绪状态的时候,强盗CPU的执行权就会到运行状态,没有就等待。

在运行状态会遇到阻塞队列,wait()等待方法,和sleep()都会使这条线程回到就绪状态。

 虚拟机中线程的六种状态:

新建状态(new)------ 创建线程对象

就绪状态(RUNNABLE) ------ start方法

阻塞状态(BLOCKED) ------ 无法获得到锁对象

等待状态(WAITING) ------ wait方法

计时状态(TIMED_WAITING) ------ sleep方法

结束状态(TERMINATED) ------ 全部代码运行完毕

二,线程池

1,以前写多线程的弊端

        (1),用到线程就创建

        (2),用完之后线程就消失

2,线程池的实现步骤

        (1),创建一个池子,池子中是空的

        (2),有任务需要执行时,才会创建线程对象,当任务执行完毕,线程对象归还给池子

3,代码实现

        (1),创建一个池子,池子中是空的 ------ 创建Executor中的静态方法

        (2),有任务需要执行时,创建线程对象  --------   submit方法   

                  任务执行完毕,线程对象归还给池子     

        (3),所有的任务全部执行完毕,关闭连接池  ------ shutdown方法

a:创建默认个数的线程池

public class MyThreadPoolDemo01 {
    public static void main(String[] args) throws InterruptedException {
        //1,创建一个默认的线程池对象,池子中默认是空的,默认最多可以容纳int类型的最大值。
        ExecutorService executorService = Executors.newCachedThreadPool();
        //Executors -- 可以帮助我们创建线程池对象
        //ExecutorService --- 可以帮助我们控制线程池
        //Executors方法是静态的,通过类名调用,返回值是ExecutorService;

        executorService.submit(()->{
            Thread.currentThread().setName("线程一");
            System.out.println(Thread.currentThread().getName()+"在执行了");
        });
        Thread.sleep(2000);//睡眠的话,第一个线程结束。放进了线程池。
                                //下次开启线程的话,是从线程池中获取线程的。
        executorService.submit(()->{
        });

        executorService.shutdown();
    }
}

默认的线程池,最多可以 int类型最大值数量个线程

 ExectursService中的submit()方法会自动创建线程,线程执行完之后,也会自动返回到池子中

线程池执行完毕之后一定要使用shoutdown方法关闭。

b:创建指定个数的线程池

//        static ExecutorService new FixedThreadPool(int nThreads);
//        创建一个指定最多线程数量的线程池
public class MyThreadPoolDemo02 {
    public static void main(String[] args) throws InterruptedException {
        //这个里面的参数不是初始值,而是最大值。创建出来的线程池也是空的。
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //强转
        ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;
        System.out.println(pool.getPoolSize());//这里时使用ThreadpoolExecutor中的
        //getPoolSize()方法获取线程池中的线程个数
        //线程一
        executorService.submit(() -> {
            Thread.currentThread().setName("线程一");
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });
        //线程二
        executorService.submit(() -> {
            Thread.currentThread().setName("线程二");
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });
        //线程三
        executorService.submit(() -> {
            Thread.currentThread().setName("线程三");
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });
        //关闭线程
        executorService.shutdown();
        //获取线程池中线程的个数
        System.out.println(pool.getPoolSize());

    }
}

 指定线程池中的最大数量就是表示线程池中最大可以有的线程数。

c:自己创建线程池

构造方法:

ThreadPoolExecutor  tpe   =   new  ThreadPoolExecutor

(核心线程数量,最大线程数量,空闲线程最大存活时间,存活时间单位,任务队列,创建线程工厂,任务的拒绝策略);

1,参数一:核心线程数量
2,参数二:最大线程数量
3,参数三:空闲线程最大存活时间
4,参数四:空闲线程存活时间的单位-- TimeUnit类
5,参数五:任务队列--阻塞队列ArrayBlockingQueue
         让任务在队列中等着,等有空闲线程了,再从这个队列中获取任务并执行。
6,参数六:创建线程工厂--怎么创建线程的对象--Executors中的静态方法,defaultThreadFactory()
           按照默认的方式创建线程对象
7,参数七:任务的拒绝策略,ThreadPoolExecutor中的内部类对象---new  ThreadPoolExecutor.AborPolicy();
         (1)什么时候拒绝
              当提交的任务大于池子中最大线程数量+任务队列容量
         (2)怎么拒绝

//自己创建线程池
public class ThreadPoolExecutorDemo01 {
    public static void main(String[] args) {
        MyThread01 mt = new MyThread01();
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(2,
                5, 2, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());


        tpe.submit(()->{//创建第一个线程
           Thread.currentThread().setName("线程一");
            System.out.println(Thread.currentThread().getName()+"执行了");
        });

        tpe.submit(()->{//创建第二个线程
            Thread.currentThread().setName("线程二");
            System.out.println(Thread.currentThread().getName()+"执行了");
        });

        System.out.println(tpe.getPoolSize());//获取线程的数量
        tpe.shutdown();//关闭线程池
    }
    //也可以使用创建资源类的方法来写,只需要把资源类对象传入tpe.submit()方法中。
}

 参数四是使用TimeUnit类来调用时间单位的

                Days:天

                HOURS:小时

                MICROSECONDS:微妙

                MILLISECONDS:毫秒

                MINUTES:分钟

                NANOSECONDS:纳秒

                SECONDS:秒

参数七,任务拒绝策略

        ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。默认的策略

        ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常,这是不推荐的做法

        ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务然后把当前任务加入队列中

        ThreadPoolExecutor.CallerRunsPlolicy:调用run()方法绕过线程池直接执行

三,Volatile

        1,堆内存是唯一的,每一个线程都有直接的线程栈

        2,每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中

        3,在线程中,每一次使用时从变量的副本中获取饿

问题:

        如果A线程修改了堆中共享变量的值

        那么其他线程不一定能及时使用最新的值

Volatile关键字

        强制线程每次在使用的时候,都会看一下共享区域最新的值

public class Money {
    public static volatile int money = 100000;
}

使用只需要在共享数据前加上Volatile关键字即可 

也可以使用Synchronized同步代码块

        1,线程获得锁

        2,清空变量副本

        3,拷贝共享变量最新的值到变量副本中

        4,执行代码

        5,将修改后变量副本中的值赋值给贡献数据

        6,释放锁

四,原子性

所谓的原子性时指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不受到任何因素的干扰而打断,要么所有的操作都不执行,多个操作是一个不可分割的整体

coubt++:不是一个原子性操作,也就是在执行的过程中有可能被其他线程打断操作。

public class MyAtom01 implements Runnable{
    private int count = 0;
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //1,从共享数据中读取数据到本线程栈中
            //2,修改本线程栈变量副本的值
            //3,会把本线程栈变量副本的值赋值给共享数据
            synchronized (this) {
                count++;
                System.out.println("送出了"+count+"个");
            }


        }
    }
}

Volatile关键字:只能保证线程每次使用共享数据的时候是最新指,

                            但是不能保证原子性。

使用Synchronized加锁可以解决代码的原子性,但是效率太低。

1,AtomicInteger原子型类:

JDK1.5之后,使用原子类(Atomiclnteger)来更新共享数据

1,构造方法:

public class AtomicInteger01 {
    public static void main(String[] args) {
        AtomicInteger ac = new AtomicInteger();//初始化一个默认值为0的原子型Integer
        System.out.println(ac);

        AtomicInteger ac2 = new AtomicInteger(10);
        //初始化一个指定值的原子型Integer
        System.out.println(ac2);

    }

2,成员方法:

public class AtomicInteger02 {
    public static void main(String[] args) {
        /*
        * int get()  获取值
        * int  getAndIncrement(): 以原子方式将当前值加1,这里返回的是自增前的值
        * int  incrementAndGet(): 以原子方式将当前值加1,这里返回的是自增后的值
        * int  addAndGet(int data): 以原子方式将参数与对象中的值相加,并返回结果
        * int  getAndSet(int value):以原子方式设置为newValue的值,并返回旧值*/
        AtomicInteger ac = new AtomicInteger(10);
        int i = ac.get();
        System.out.println(i);

        int andIncrement = ac.getAndIncrement();
        System.out.println(andIncrement);

        int i1 = ac.incrementAndGet();
        System.out.println(i1);

        int i2 = ac.addAndGet(10);
        System.out.println(i2);

        int andSet = ac.getAndSet(19);
        System.out.println(andSet);

        System.out.println(ac.get());
    }
}

 2,使用原子性解决送冰淇凌问题

线程体类

public class MyAtom01 implements Runnable {
    //    private int count = 0;
    AtomicInteger ac = new AtomicInteger(0);
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            int i1 = ac.incrementAndGet();
            System.out.println("送出了" + i1 + "个");
        }
    }
}

测试类

public class Demo01 {
    public static void main(String[] args) {
        MyAtom01 mt = new MyAtom01();
        for (int i = 0; i < 100; i++) {
            new Thread(mt).start();
        }
    }
}

 3,AtomicInteger原理

        自旋锁+ CAS算法

        CAS算法:有3个操作数(内存值V,旧的预期值A,要修改的值B)

                        当旧的预期值A == 内存值  此时修改成功 ,将V改为B

                        当旧的预期值A !=内存值  此时修改失败,不做任何操作

                        并重新获取现在的最新值(这个重新获取的动作就是自旋)

在修改共享数据的时候,把原来的旧值记录下来。如果现在内存中的值跟原来的旧值一样,证明没有其他线程操作过内存值,则修改成功。如果现在内存中跟原来的旧址不一样了,证明已经有其他线程操作过内存值了,则修改失败,需要获取现在的最新值,再次进行操作,这个重新操作的过曾就是自旋。

4,Synchroized和CSA的区别

        相同点:在多线程情况下,都可以保证共享数据的安全性

        不同点:Synchroized总是从最坏的角度出发,认为每次获取数据的时候,别人有可能修改,所以在每次操作共享数据之前,都会上锁(悲观锁)

        csa是从乐观的角度除法,假设每次过去数据别人都不会修改,所以不会上锁,只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据

        如果别人修改过,那么我们再次获取现在的最新值

        如果没有修改过,那么就直接修改这个共享数据的值

        (乐观锁)

五,并发工具类

HashMap是线程不安全的(多线程环境下可能出现问题)

为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下

1,Hashtable

        (1),Hashtable采取悲观锁synchronized的形式保证数据的安全性

        (2),只要有线程访问,会将整张表全部锁起来,所以Hashtable的效率底下

2,ConcurrentHashMap

       Map接口:HashMap,Hashtable ,TreeMap, ConcurrentMap 

 ConcurrentHashMap1.8版本底层原理是:哈希表(数组,链表,红黑树结合体)

结合CAS机制+synchronized同步代码块形式保证线程安全

3,CountDownLatch类

方法:

方法解释
public  CountDownLatch(int  count);参数传递线程数,表示等待线程数量
public  void  await();让线程等待
public  void  countDoen();

当前线程执行完毕

运用

等待的资源类

public class MotherThread extends Thread{
    private CountDownLatch cdl;
    public MotherThread(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("洗碗");
    }
}

 在执行的资源类

public class ChileThread1 extends Thread{
    private CountDownLatch cdl;
    public ChileThread1(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        for (int i = 1; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "在吃第" + i + "个饺子。");

        }
        cdl.countDown();
    }
}

测试类: 

public class Demo {
    public static void main(String[] args) {
        CountDownLatch cdl = new CountDownLatch(1);

        MotherThread mt = new MotherThread(cdl);
        mt.start();

        ChileThread1 c1 = new ChileThread1(cdl);
        c1.setName("孩子一");

        c1.start();
    }
}

 小结:

        使用场景:让某一条线程等待其他线程执行王碧之后再执行

   CountDownLatch(int  count) :参数写等待线程的数量,并定义一个计数器

    await():让线程等待,当计数器为0时,会唤醒等待的线程

     countDown():线程执行完毕时调用,会将技术器减1;

4,Semaphore类

Semaphore可以设置线程的通行数量

资源类

public class MyRunnable01 implements Runnable{
    private Semaphore Semaphore;
    public MyRunnable01(Semaphore ap) {
        this.Semaphore = ap;
    }
    @Override
    public void run() {
        try {
            Semaphore.acquire();
            System.out.println("获得了通行证。");
            Semaphore.release();
            System.out.println("归还。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试类 

public class Demo01 {
    public static void main(String[] args) {
        Semaphore ap = new Semaphore(2);
        MyRunnable01 mr = new MyRunnable01(ap);
        for (int i = 0; i < 10; i++) {
            new Thread(mr).start();

        }
    }
}

Semaphore   s  = new  Semaphore(参数);

获取管理员对象,参数是最大可执行的线程数

s.acquire();获取通行证的方法

s.release();归还通行证的方法

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值