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<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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容

  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,087评论 0 23
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,336评论 3 87
  • 写在前面的话: 这篇博客是我从这里“转载”的,为什么转载两个字加“”呢?因为这绝不是简单的复制粘贴,我花了五六个小...
    SmartSean阅读 4,715评论 12 45
  • 林炳文Evankaka原创作品。转载自http://blog.csdn.net/evankaka 本文主要讲了ja...
    ccq_inori阅读 647评论 0 4
  • 三十出头姑娘,有个正需要好好照顾和教育的五岁女儿,有个对工作尤为上心对生活没有太多经验的单纯先生。她只能发挥母性所...
    疯妍风语阅读 276评论 0 1