多线程

线程基础

  1. 线程的五种状态

    1. 创建(New):使用new创建一个线程时的状态,尚未启动。
    2. 就绪(Runnable):调用start()启动线程,加入就绪队列,等待被调度进CPU运行
    3. 运行(Run):处于就绪状态的线程获取了cpu的执行权
    4. 阻塞(Block):由于某些原因该线程放弃了cpu的使用权。停止执行。除非线程进入可运行的状态,才会有机会获取cpu的使用权。
      • 等待阻塞:运行中的线程执行wait方法,这时候该线程会被放入等待队列。
      • 同步阻塞:运行中的线程获取同步锁,如果该同步锁被别的线程占用,这个线程会成被放入锁池,等待其他线程释放同步锁。
      • 其他阻塞:运行的线程执行sleep或者join方法这个线程会成为阻塞状态。当sleep超时,join等待线程终止,该线程会进入可运行状态。
    5. 死亡(Dead):线程run mian 执行完毕后,或者因为某些异常产生退出了 run 方法,该线程的生命周期结束。
  2. 线程状态转换图
    状态转换

  3. 线程有关方法

    1. sleep(time):使线程进入阻塞状态,但不释放锁,睡眠时间发到后自动进入就绪状态。
      作用:给其他线程执行机会。
    2. wait():使线程进入阻塞状态,释放锁,等到被其他线程notify()时进入就绪状态。
    3. interrupt():停止线程,调用时抛出中断异常。
    4. setDaemon(true):后台线程,当所有的前台线程都结束了,后台线程就结束。
      注意:在启动前调用。作用:为前台线程提供服务。
    5. setPriority(Thead.MAX_PRIORITY):设置线程的优先级(1-10),默认优先级为5。
    6. join():当A线程执行到B线程的join()时,A就会等待,直到B结束A才会执行。
      作用:临时加入线程,使该线程运行完,其他线程才继续。
    7. yield():使当前线程放弃分得的CPU时间片,进入就绪状态。
      作用:使同优先级或更高优先级的线程有执行机会。

继承Thread类

  1. 代码演示

    class Test extends Thread 
    {
        Test(String name)
        {
            super(name);
        }
        //run()存储线程要运行的代码
        public void run()
        {
            for(int x = 0; x < 60;x++)
            {
                System.out.println(Thread.currentThread().getName()+"   run....."+x);
            }
        }
    }
    
    class  ThreadDemo1
    {
        public static void main(String[] args) 
        {
            Test t1 = new Test("one");
            Test t2 = new Test("two");
            t1.start();
            t2.start();
        }
    }
    
  2. 存在的不足

    1. 子类无法多继承。
    2. 不适合资源共享。

实现Runnable接口

  1. 代码演示

    class Ticket implements Runnable
    {
        private int tick = 100;
        public void run()
        {
            while(true)
            {
                if(tick>0)
                {
                    System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
                }
            }
        }
    }
    class  ThreadDemo3
    {
        public static void main(String[] args) 
        {
            Ticket t  = new Ticket();
    
            //创建线程的同时明确线程要运行的代码
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            Thread t3 = new Thread(t);
            Thread t4 = new Thread(t);
    
            //启动线程,使线程处于就绪状态,等待被调度
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
  2. 注意:实现Runnable接口的类不是线程类。创建线程对象的是Thread或Thread的子类。

多线程的安全问题

  1. 代码演示安全问题

    class Ticket implements Runnable
    {
        //线程的共享数据
        private int tick = 100;
        public void run()
        {
            while(true)
            {
                if(tick>0)
                {
                    //为了演示可能出现的问题,让当前线程释放执行权
                    try{Thread.sleep(10);} catch(Exception e){}
                    System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
                }
            }
        }
    }

    当多线程用到共享资源时,由于CPU的时间片特性,不管run()有没有执行完毕,CPU时间片一旦执行完一个线程的时间片,会自动跳转到另一个线程的run()中去执行其时间片,这样就可能导致共享数据的不一致,也即出现了多线程安全问题。

  2. 解决方案

    class Ticket implements Runnable
    {
        private int tick = 100;
        public void run()
        {
            while(true)
            {
                //在涉及到共享数据的代码上加互斥锁
                synchronized(this)
                {
                    if(tick>0)
                    {
                        try{Thread.sleep(10);} catch(Exception e){}
                        System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
                    }
                }
            }
        }
    }

互斥锁

  1. 互斥锁的理解

    互斥锁相当于公共厕所的一把锁,公共厕所是一个共享资源,为了使当前正在使用资源的人不被其他人打扰,在门上加一把锁,里面的人不开门(释放锁),外面的人就进不去。

  2. 如何判断哪些代码需要加锁

    1. 明确哪些代码是多线程运行代码
    2. 明确共享数据
    3. 明确多线程运行代码中哪些语句是操作共享数据的。
  3. 加锁后同步的前提

    1. 有两个或两个以上的线程
    2. 多个线程使用同一个锁
  4. 加锁的弊端

    多个线程需要判断锁,较为消耗资源。

  5. 锁对象

    1. 同步代码块的锁可以是任意对象。
    2. 同步函数的锁是this。
    3. 静态同步函数的锁是Class对象。

多线程间的通信

  1. 问题描述

    当多个线程在操作同一个资源,但是操作的动作不同(run()不同),就涉及到了线程间的通信问题。

  2. 解决方法:用标记+等待唤醒机制wait()和notify()实现同步

    1. wait():使正在执行的线程等待,进入阻塞状态。
    2. notify():唤醒线程池中等待的线程,进入就绪状态。
    3. notifyAll() :唤醒线程池中所有线程。

    注意:这些方法都在互斥锁中使用,因为它们都被互斥锁对象调用。 以下是代码示例。

    class Res
    {
        private String name;
        private String sex;
        private boolean flag = false;   //是否释放锁的标记
    
        //送入
        public synchronized void  set(String name,String sex)   //加锁
        {
            if(flag)        //有煤了,等,释放锁
                try{this.wait();} catch(Exception e){}  
    
            this.name = name;
            this.sex = sex;
    
            flag = true;        //标记有资源了
            this.notify();      //唤醒对方线程,叫对方送走资源
        }
    
        //送走
        public synchronized void out()
        {
            if(!flag)       //没有资源,等
                try{this.wait();} catch(Exception e){}  
    
            System.out.println(name+"........"+sex);
    
            flag = false;   //标记没有资源了
            this.notify();  //唤醒对方线程,叫对方送资源
        }
    }
    
    class Input implements Runnable
    {
        Res r;
        Input(Res r)
        {
            this.r = r;
        }
        public void run()
        {
            int x = 0;
            while(true)
            {
                if(x == 0)  
                    r.set("mike","male");
                else
                    r.set("lili","女女女女女女");
                x = (x+1) % 2;
            }
        }
    }
    
    class Output implements Runnable
    {
        Res r;
        Output(Res r)
        {
            this.r = r;
        }
        public void run()
        {
            while(true)
            {
                r.out();
            }
        }
    }
    
    class  ThreadDemo7
    {
        public static void main(String[] args) 
        {
            //要操作的共享资源
            Res r = new Res();
            //开启两个线程同时运行
            new Thread(new Input(r)).start();
            new Thread(new Output(r)).start();
        }
    }
  3. 需要同步的几种情况

    1. 两个操作一样的线程:直接加锁(锁对象是本身对象)
    2. 两个操作不一样的线程:加锁+判断标记+等待唤醒机制(notify())
    3. 两个执行A操作的线程、两个执行B操作的线程:加锁+循环判断标记+等待唤醒机制(notifyAll())
  4. 生产者消费者问题代码示例

    //共享资源
    class Resource
    {
        private String name;            //资源名称
        private int count = 1;          //操作资源的次数
        private boolean flag = false;       //是否释放锁的标记
    
        public synchronized void set(String name)       //隐式的锁机制
        {
            while(flag)                 //有资源
                try{this.wait();} catch(Exception e){}  //等待的同时释放锁
    
            this.name = name + "--" + count++;
            System.out.println(Thread.currentThread().getName()+"....生产者...."+this.name);
    
            flag = true;                    //有资源了
            this.notifyAll();               //唤醒所有等待队列中的线程
        }
        public synchronized void out()
        {
            while(!flag)                    //没有资源
                try{this.wait();} catch(Exception e){}
    
            System.out.println(Thread.currentThread().getName()+"....消费者.........."+this.name);
    
            flag = false;                   //资源被取走了
            this.notifyAll();               //唤醒所有等待队列中的线程
        }
    }
    
    //生产者
    class Producer implements Runnable 
    {
        private Resource res;
        Producer(Resource res)
        {
            this.res = res;
        }
        public void run()
        {
            while(true)
            {
                res.set("+商品+");
            }
        }
    }
    
    //消费者
    class Consumer implements Runnable
    {
        private Resource res;
        Consumer(Resource res)
        {
            this.res = res;
        }
        public void run()
        {
            while(true)
            {
                res.out();
            }
        }
    }
    
    class ThreadDemo8
    {
        public static void main(String[] args) 
        {
            //共享资源对象
            Resource r = new Resource();
    
            //生产者、消费者对象操作同一个资源
            Producer pro = new Producer(r);
            Consumer con = new Consumer(r);
    
            //两个生产者线程、两个消费者线程
            Thread t1 = new Thread(pro);
            Thread t2 = new Thread(pro);
            Thread t3 = new Thread(con);
            Thread t4 = new Thread(con);
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

JDK5.0升级后的同步解决方案

  1. 升级内容

    1. 将同步synchronized替换成Lock接口
    2. 由于wait()、notify()都要标识自己所属的锁,因此将Object中的wait()、notify()、 notifyAll()封装成Condition监视器对象,该对象可以通过Lock锁对象获取,且一个锁可以对应多个监视器对象。
  2. 升级后的好处

    1. 显式的锁机制,使加锁和释放锁操作更透明。
    2. 显式的等待唤醒机制,可以有多个Condition监视器对象,使不会唤醒本方。
    3. 由于一个锁可以对应多个Condition监视器对象,因此不容易发生死锁。
  3. 生产者消费者问题代码示例

    import java.util.concurrent.locks.*;
    
    //要操作的共享资源
    class Resource
    {
        private String name;                    //资源名称
        private int count = 1;                  //操作资源的次数
        private boolean flag = false;               //是否释放锁的标记
        private Lock lock = new ReentrantLock();        //接口引用指向子类对象
        private Condition condition_pro = lock.newCondition();  //生产者监视器
        private Condition condition_con = lock.newCondition();  //消费者监视器
    
        //设置资源
        public void set(String name) throws InterruptedException
        {
            lock.lock();        //加锁
            try
            {
                while(flag)
                    condition_pro.await();  //生产者等待,没有释放锁
    
                this.name = name + "--" + count++;
                System.out.println(Thread.currentThread().getName()+"....生产者...."+this.name);
    
                flag = true;
                condition_con.signal();     //消费者醒来
            }
    
            finally         //在await()之前必须先释放锁
            {
                lock.unlock();  //释放锁
            }
        }
        //获取资源
        public void out() throws InterruptedException
        {
            lock.lock();        //加锁
            try
            {
                while(!flag)
                    condition_con.await();  //消费者等待,没有释放锁
    
                System.out.println(Thread.currentThread().getName()+"....消费者.........."+this.name);
    
                flag = false;
                condition_pro.signal();     //生产者醒来
            }
            finally         //在await()之前必须先释放锁
            {
                lock.unlock();
            }
        }
    }
    
    //生产者
    class Producer implements Runnable 
    {
        private Resource res;
        Producer(Resource res)
        {
            this.res = res;
        }
        public void run()
        {
            while(true)
            {
                try
                {
                    res.set("+商品+");
                }
                catch (InterruptedException e)
                {
                }
            }
        }
    }
    
    //消费者
    class Consumer implements Runnable
    {
        private Resource res;
        Consumer(Resource res)
        {
            this.res = res;
        }
        public void run()
        {
            while(true)
            {
                try
                {
                    res.out();
                }
                catch (InterruptedException e)
                {
                }
            }
        }
    }
    class ThreadDemo9
    {
        public static void main(String[] args) 
        {
            //创建共享资源对象
            Resource r = new Resource();
    
            //创建生产者消费者对象,操作同一个资源对象
            Producer pro = new Producer(r);
            Consumer con = new Consumer(r);
    
            //两个生产者线程,两个消费者线程
            Thread t1 = new Thread(pro);
            Thread t2 = new Thread(pro);
            Thread t3 = new Thread(con);
            Thread t4 = new Thread(con);
    
            //开启线程对象
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值