zoukankan      html  css  js  c++  java
  • java多线程的学习之路(二)

    笔记来源
    java多线程的学习之路(一)

    一、临界资源问题

    模拟四个售票员同时售票的场景

    public class SourceConflict {
        //演示临界资源问题
        //某个景点有4个售票员在同时售票。
        public static void main(String[] args) {
            //1 实例化四个售票员,用4个线程模拟4个售票员
            Runnable r = () -> {
                while (TicketCenter.restCount > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + --TicketCenter.restCount);
                }
            };
            Thread t1 = new Thread(r, "售票员1");
            t1.start();
            Thread t2 = new Thread(r, "售票员2");
            t2.start();
            Thread t3 = new Thread(r, "售票员3");
            t3.start();
            Thread t4 = new Thread(r, "售票员4");
            t4.start();
        }
    }
    
    class TicketCenter {
        //描述剩余的票的数量
        public static int restCount = 100;
    }
    

    部分输出结果下如图
    在这里插入图片描述
    可以发现余票不是顺序减少的:这是因为一个线程强夺到CPU以后,做完减法还没来得及输出,CPU又被另一个线程强夺了

    并且出现了两次剩余97

    出现这些问题是由于多个线程同时访问同一个临界资源


    二、同步代码段

    为了解决上面的临界资源问题,可以对访问临近资源时加锁

    添加同步代码段

    public class SynchronizedDemo {
        //演示临界资源问题
        //某个景点有4个售票员在同时售票。
        public static void main(String[] args) {
            //1 实例化四个售票员,用4个线程模拟4个售票员
            Runnable r = () -> {
                while (TicketCenter.restCount > 0) {
                    //对象锁 括号里可以是任意对象
                    //类锁 例如SynchronizedDemo.class
                    //需要保证一点:多个线程看到的锁,需要是同一把锁
                    synchronized (""){
                        System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + --TicketCenter.restCount);
                    }
                }
            };
            Thread t1 = new Thread(r, "售票员1");
            t1.start();
            Thread t2 = new Thread(r, "售票员2");
            t2.start();
            Thread t3 = new Thread(r, "售票员3");
            t3.start();
            Thread t4 = new Thread(r, "售票员4");
            t4.start();
        }
    }
    

    此时得到输出结果,发现顺序的问题已经解决了,但是出现了负数的情况。
    这是因为还剩一张票时,线程1、2、3、4都已经进入了while循环,线程1强夺到了然后上锁,等到线程1执行结束,线程2、3、4还是可以获得锁继续执行。
    在这里插入图片描述
    为了解决负数的问题,添加一个if语句就可以了

     //对象锁
    synchronized (""){
        if(TicketCenter.restCount <= 0)
            return;
        System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + --TicketCenter.restCount);
    }
    

    三、同步方法

    使用同步方法可以达到同样的效果

    //同步方法: 用关键字synchronized 修饰的方法就是同步方法
    /**
    *同步的方法
    *静态方法:同步锁就是类锁当前类.class
    *非静态方法:同步锁是this
    */
    private synchronized static void sellTicket(){
        if(TicketCenter.restCount <= 0)
            return;
        System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + --TicketCenter.restCount);
    }
    

    四、锁

    使用ReentrantLock实例化一个锁

    //实例化一个锁对象
    ReentrantLock lock = new ReentrantLock();
    

    对需要上锁的部分加上.lock()方法和.unlock()方法

    //对临近资源上锁
     lock.lock();
     if (TicketCenter.restCount <= 0)
         return;
     System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + --TicketCenter.restCount);
     //对临界资源解锁
     lock.unlock();
    

    可以达到和上面的同步代码段、同步方法同样的效果


    死锁

    在这个例子中,线程A强夺到了A锁,同时线程B强夺到了B锁,但是程序还没有结束,因为线程A要等待B锁释放后,去强夺B锁继续执行后面的代码,但没有释放自己的锁;线程B也在等待A锁释放,而不释放自己的锁。所以产生了死锁

    public class DeadLock {
        public static void main(String[] args) {
            //死锁:多个线程彼此持有对方所需要的锁对象,而不释放自己的锁。
            Runnable r1 = ()->{
                synchronized ("A"){
                    System.out.println("A线程持有了A锁,等待B锁");
                    synchronized ("B"){
                        System.out.println("A线程持有了A锁,也持有了B锁");
                    }
                }
            };
    
            Runnable r2 = ()->{
                synchronized ("B"){
                    System.out.println("B线程持有了B锁,等待A锁");
                    synchronized ("A"){
                        System.out.println("B线程持有了B锁,也持有了A锁");
                    }
                }
            };
    
            Thread ta = new Thread(r1,"ThreadA");
            Thread tb = new Thread(r2,"ThreadB");
            ta.start();
            tb.start();
        }
    }
    

    输出结果:

    A线程持有了A锁,等待B锁
    B线程持有了B锁,等待A锁


    wait和notify

    1. wait:
      等待,object类中的方法,让当前线程释放锁标记,并且让出CPU原则,进入等待队列
    2. notify:
      通知,唤醒等待队列中的一个线程(由cpu确定),使这个线程进入锁池
    3. notifyAll:
      通知,唤醒等待队列中等待特定锁的全部线程,使线程进入锁池
    public class DeadLock2 {
    
        public static void main(String[] args) {
            //死锁:多个线程彼此持有对方所需要的锁对象,而不释放自己的锁。
            Runnable r1 = ()->{
                synchronized ("A"){
                    System.out.println("A线程持有了A锁,等待B锁");
    				//线程A释放A锁,进入等待队列
                    try {
                        "A".wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized ("B"){
                        System.out.println("A线程持有了A锁,也持有了B锁");
                    }
                }
            };
    
            Runnable r2 = ()->{
                synchronized ("B"){
                    System.out.println("B线程持有了B锁,等待A锁");
                    synchronized ("A"){
                        System.out.println("B线程持有了B锁,也持有了A锁");
                        "A".notifyAll();//完成任务后释放
                    }
                }
            };
    
            Thread ta = new Thread(r1,"ThreadA");
            Thread tb = new Thread(r2,"ThreadB");
            ta.start();
            tb.start();
        }
    }
    

    输出结果:

    A线程持有了A锁,等待B锁
    B线程持有了B锁,等待A锁
    B线程持有了B锁,也持有了A锁
    A线程持有了A锁,也持有了B锁


    线程池

    笔记来源

    每一个线程的启动和结束都是比较消耗时间和占用资源的。如果在系统中用到了很多的线程,大量的启动和结束动作会导致系统的性能变卡,响应变慢。为了解决这个问题,引入线程池这种设计思想。

    线程池设计思路

    线程池的模式很像生产者消费者模式,消费的对象是一个一个的能够运行的任务。

    1. 准备一个任务容器
    2. 一次性启动10个 消费者线程
    3. 刚开始任务容器是空的,所以线程都wait在上面。
    4. 直到一个外部线程往这个任务容器中扔了一个“任务”,就会有一个消费者线程被唤醒notify
    5. 这个消费者线程取出“任务”,并且执行这个任务,执行完毕后,继续等待下一次任务的到来。
    6. 如果短时间内,有较多的任务加入,那么就会有多个线程被唤醒,去执行这些任务。

    在整个过程中,都不需要创建新的线程,而是循环使用这些已经存在的线程
    在这里插入图片描述
    java自带的线程池

    第一个参数10 表示这个线程池初始化了10个线程在里面工作
    第二个参数15 表示如果10个线程不够用了,就会自动增加到最多15个线程
    第三个参数60 结合第四个参数TimeUnit.SECONDS,表示经过60秒,多出来的线程还没有接到活儿,就会回收,最后保持池子里就10个
    第四个参数TimeUnit.SECONDS 如上
    第五个参数 new LinkedBlockingQueue() 用来放任务的集合
    execute方法用于添加新的任务

    public class TestThread {
       
        public static void main(String[] args) throws InterruptedException {
               
            ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
               
            threadPool.execute(new Runnable(){
       
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    System.out.println("任务1");
                }
                   
            });
       
        }
       
    }
    
  • 相关阅读:
    栈和堆的详细介绍
    在DataTable中执行DataTable.Select("条件")返回DataTable;
    委托和事件
    面试宝典
    sql的寫法,推薦的寫法,全文索引提高類似like查詢的效率
    Google地图
    一般处理程序中,获取session
    提交表单
    手脱tElock 0.98b1 -> tE!
    手脱FSG 2.0 -> bart/xt
  • 原文地址:https://www.cnblogs.com/csyxsy/p/13340209.html
Copyright © 2011-2022 走看看