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

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,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(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)方法即可

        
        
        

    
        
            
        
    
    //in code
    userThreadPool.execute(thread);

好用的方法:

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

//                              RejectedExecutionHandler handler)  - 拒绝策略。
java 线程安全_多线程_线程池_第1张图片
image.png

你可能感兴趣的:(java 线程安全_多线程_线程池)