美文网首页
java 线程安全_多线程_线程池

java 线程安全_多线程_线程池

作者: 会摄影的程序员 | 来源:发表于2019-03-28 19:14 被阅读0次

1. 线程安全demo

  1. code:
public class RunnerableImpl implements Runnable {

    /**
     * 设置电影票数量
     * @Author chenpeng
     * @Description //TODO
     * @Date 23:57
     * @Param
     * @return
     **/
    private static int ticket = 100;

    @Override
    public void run() {
        while (ticket>0){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                new RuntimeException();
            }
            System.out.println(Thread.currentThread().getName()+"  第"+ticket+"张票");
            ticket--;
        }
    }
}
public class RunTest {
    public static void main(String[] args){
        RunnerableImpl run = new RunnerableImpl();

        Thread thread = new Thread(run);
        Thread thread1 = new Thread(run);
        Thread thread2 = new Thread(run);

        thread.start();
        thread1.start();
        thread2.start();
    }
}
  1. 结果:
Thread-0  第100张票
Thread-1  第100张票
Thread-2  第100张票
Thread-1  第97张票
Thread-0  第97张票
Thread-2  第97张票
....
....
Thread-2  第1张票
Thread-0  第0张票
Thread-1  第-1张票
  1. 说明:
    为什么会出现这种情况呢?
    1.重复卖第100张票

    Thread-0 Thread-1 Thread-2同时进入程序,并且在最早到ticket--;的线程执行之前 1,2,3最晚到达syso的线程已经到达;
    2.为什么出现卖不存在的票?
    因为在票数为1的时候
    Thread-0在代码执行完while (ticket>0)进入循环,执行到sleep就失去了cpu,进入等待模式
    这个时候Thread-1得到cpu开始执行while (ticket>0)进入循环,执行到sleep也失去cpu,进入等待模式
    Thread-2得到cpu开始执行while (ticket>0)进入循环,执行到sleep也失去cpu,进入等待
    都还未执行ticket--; 那么他们都认为还有1张票可以卖
    等1,2,3逐渐苏醒,就出现了卖第0张票和第-1张票的情况。

2. 如何解决线程安全问题

2.1 同步代码块synchronized

synchronized (锁对象){
   访问了共享数据的代码块
}

注意事项:

  1. 通过代码块中的锁对象,可以使用任意对象
  2. 但是必须保证多个线程使用的是同一个对象
  3. 锁对象的作用:
    1. 把同步代码块锁住,只让一个线程在其中访问
public class RunnerableImpl implements Runnable {

    /**
     * 设置电影票数量
     * @Author chenpeng
     * @Description //TODO
     * @Date 23:57
     * @Param
     * @return
     **/
    private static int ticket = 100;

    Object object = new Object();

    @Override
    public void run() {
        while (true){
            synchronized (object){
                if (ticket>0){
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        new RuntimeException();
                    }
                    System.out.println(Thread.currentThread().getName()+"  第"+ticket+"张票");
                    ticket--;
                }else{
                    break;
                }
            }
        }
    }
}

保证了只有一个线程在同步中执行了共享数据
程序频繁的获取锁,释放锁,程序的效率会降低

2.2 同步方法 (带有synchronized修饰的方法)

  1. 将访问共享数据的代码抽取出来
  2. 将抽取出来的代码方法加上关键字synchronized

code:

public class RunnerableImpl implements Runnable {

    /**
     * 设置电影票数量
     * @Author chenpeng
     * @Description //TODO
     * @Date 23:57
     * @Param
     * @return
     **/
    private static int ticket = 100;
    Object object = new Object();
    @Override
    public void run() {
        while (runDo()){
        }
    }
    
    public synchronized boolean runDo(){
        if (ticket>0){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                new RuntimeException();
            }
            System.out.println(Thread.currentThread().getName()+"  第"+ticket+"张票");
            ticket--;
            return true;
        }else{
            return false;
        }
    }
}

锁对象是this

2.3 静态同步方法

  1. 在同步方法中加入 static
  2. 锁对象是 本类的class类 也就是方法的.class方法

2.4 Lock锁(jdk1.5之后产生的)

  1. Lock锁比synchronized有更广泛的锁定义操作
  2. Lock接口中常用的方法
    1. lock();
    2. unlock();

code:

public class RunnerableImpl implements Runnable {

    private static int ticket = 100;

    Lock locl = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            locl.lock();
            if (ticket>0){
                try {
                    Thread.sleep(20);
                    System.out.println(Thread.currentThread().getName()+"  第"+ticket+"张票");
                    ticket--;
                } catch (InterruptedException e) {
                    new RuntimeException();
                }finally {
                    locl.unlock();
                }
            }else {
                break;
            }
        }
    }
}

3. 等待与唤醒机制

3.1 线程之间的通行问题

多个线程在处理同一个资源,单处理的动作不一样

生产者与消费之问题
生产一个消费一个

为什么要处理线程间通信问题?
多个线程并发时,cpu是随机切换的,我们需要多个线程同时完成一件事情时,多个线程之间需要一些协调通信,达到多个线程操作一份数据

3.2 等待与唤醒机制

有效的利用资源(餐厅吃东西)
通信: 对餐厅的座位坐判断

  1. wait:客人看到没有座位之后,线程不在活动,不再参与调度,进入wait set中,不会去竞争,也就不会抢在cpu资源,这个时候这个线程的状态是Waiting。需要等待一个信号才能从wait set中释放出来,从新进入ready queue中去
  2. notify:选取一个wait set来释放:等待最久的一个客人得到座位
  3. notifyAll:释放所有的wait set

注:notify之后 也不一定是立马就执行,也是要进行cpu抢占的

code:

  1. 包子对象
public class BaoZi {
    private String baozi;
    private boolean flag = false;

    public String getBaozi() {
        return baozi;
    }

    public void setBaozi(String baozi) {
        this.baozi = baozi;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
  1. 生产者对象
public class BaoZiBoss implements Runnable{
    private BaoZi baozi;

    public BaoZiBoss(BaoZi baozi) {
        this.baozi = baozi;
    }

    @Override
    public void run() {
        while (true){
            synchronized (baozi){
                if (baozi.isFlag()) {
                    try {
                        baozi.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println("制作包子中....");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                baozi.setBaozi("牛肉包子");
                baozi.setFlag(true);

                System.out.println("包子制作好了!");

                baozi.notify();

            }
        }
    }
}
  1. 消费者对象
public class XiaoFeiZhe implements Runnable {
    private BaoZi baoZi;

    public XiaoFeiZhe(BaoZi baoZi) {
        this.baoZi = baoZi;
    }

    @Override
    public void run() {
        while (true){
            synchronized (baoZi){
                if (baoZi.isFlag()){
                     System.out.println("我吃包子  "+baoZi.getBaozi());
                     baoZi.setFlag(false);
                     baoZi.notify();
                }

                try {
                    baoZi.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  1. 测试类
public class BaoZiDo {
    public static void main(String[] args){
        BaoZi baoZi = new BaoZi();

        new Thread(new BaoZiBoss(baoZi)).start();

        new Thread(new XiaoFeiZhe(baoZi)).start();


    }
}

4 线程池

线程池的容器是集合(ArrayList,HashSet,LinkedList<Thread>,HashMap)

当程序第一次启动时,创建多个集合,保存到一个集合中,使用线程的时候就可以从集合汇总取出一个线程来使用
Thread t = linked.removerFist();
当使用完线程之后,需要把线程归还给线程池

在JDK1.5之后,JDK已经自带线程池
java.util.concurrent.Executors线程池的工厂类,用来生成线程池

4.1 线程池的好处

  1. 降低了资源消耗(减少了创建和销毁线程的次数,每个工作线程可以重复利用)
  2. 提高响应速度,当任务到达时,任务可以不需要等到线程创建就可以立即执行
  3. 提高线程的可管理性,可根据系统的承受能力,调整线程池中工作的数目

demo:

public class ThreadPoolDemo1 {
      public static void main(String[] args){
          //建立线程池
          ExecutorService executorService = Executors.newFixedThreadPool(2);
          //线程池会一直
          executorService.submit(new TestThread());
          executorService.submit(new TestThread());
          executorService.submit(new TestThread());

          executorService.shutdown();
      }
}

阿里巴巴推荐使用:

推荐方式1:
  首先引入:commons-lang3包
  ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
        new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
     
推荐方式 2:
首先引入:com.google.guava包
    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();

    //Common Thread Pool
    ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

    pool.execute(()-> System.out.println(Thread.currentThread().getName()));
    pool.shutdown();//gracefully shutdown
            
推荐方式 3:
   spring配置线程池方式:自定义线程工厂bean需要实现ThreadFactory,可参考该接口的其它默认实现类,使用方式直接注入bean
调用execute(Runnable task)方法即可
<bean id="userThreadPool"
        class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="queueCapacity" value="2000" />

    <property name="threadFactory" value= threadFactory />
        <property name="rejectedExecutionHandler">
            <ref local="rejectedExecutionHandler" />
        </property>
    </bean>
    //in code
    userThreadPool.execute(thread);

好用的方法:

//          public ThreadPoolExecutor(
//                  int corePoolSize, - 线程池核心池的大小。
//                              int maximumPoolSize, - 线程池的最大线程数。
//                              long keepAliveTime, - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
//                              TimeUnit unit, - keepAliveTime 的时间单位。
//                              BlockingQueue<Runnable> workQueue, - 用来储存等待执行任务的队列。
//                              ThreadFactory threadFactory, - 线程工厂。

//                              RejectedExecutionHandler handler)  - 拒绝策略。
image.png

相关文章

  • JAVA 多线程与锁

    JAVA 多线程与锁 线程与线程池 线程安全可能出现的场景 共享变量资源多线程间的操作。 依赖时序的操作。 不同数...

  • JAVA开发-常见面试题

    一、java多线程 线程池的原理,为什么要创建线程池? 线程的生命周期,什么时候会出现僵死进程? 什么是线程安全,...

  • Java:线程池Executors.newFixedThread

    摘要:Java,多线程,线程池 多线程编程和线程池概述 (1)多线程程序: 计算机可以实现多任务 ( multit...

  • 10.3多线程详解

    Java高级-多线程 多线程创建 多线程通讯 线程池 1.多线程创建 thread/runnable图:继承Thr...

  • 后端架构师技术图谱(三)-并发、锁、设计模式(二)

    并发 多线程 《40个Java多线程问题总结》 线程安全 《Java并发编程——线程安全及解决机制简介》 一致性、...

  • 线程

    Java 并发编程:线程池的使用 Java 并发编程:线程池的使用java 多线程核心技术梳理 (附源码) 本文对...

  • 多线程juc锁

    java_basic 1 线程安全 在Java多线程编程当中,实现线程安全: 内部锁(Synchronized...

  • Java源码-线程池

    一、线程池实现原理 Java支持多线程,多线程可以提高任务的执行效率。但是Java里面的线程跟操作系统的线程是一一...

  • ConcurrentHashMap

    总结 HashMap在多线程中不安全,java提供了线程安全的ConcurrentHashMap 类,保证在多线程...

  • Thread

    队列 线程锁 多线程,线程池 队列 多线程爬虫示例 多线程 自定义线程 线程池

网友评论

      本文标题:java 线程安全_多线程_线程池

      本文链接:https://www.haomeiwen.com/subject/mdicbqtx.html