java 多线程2

来源:互联网 发布:天天炫斗刷钻石软件 编辑:程序博客网 时间:2024/06/07 03:08

(> 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

●Lock
Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作
○void lock():获取锁
○void unlock():释放锁
通过查API我们知道了
ReentrantLock是Lock的实现类
所以我们就用Lock来实现一下

   public class SellTicket implements Runnable{          private int tickets=100;          //定义锁对象          private Lock lock=new ReentrantLock();          @Override          public void run(){          //加锁          lock.lock();           while(true){                  if(tickets>0){                  try{                    Thread.sleep(1000);                    } catch(InterruptedException e){                      e.printStackTrace();                    }           System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");                  }                  //释放锁                  lock.unlock();           }   }   }   public class SellTicketDemo{           public static void main(String[] args){               SellTicket st=new SellTicket();               Thread t1=new Thread(st,"窗口1");                Thread t2=new Thread(st,"窗口2");                 Thread t3=new Thread(st,"窗口3");               s1.start();               s2.start();               s3.start();           }   }

其实上面这个代码还有点问题 如果在锁中出现问题 这个锁就放不了 所以用加一个try finally

  @Override          public void run(){          try{          //加锁          lock.lock();           while(true){                  if(tickets>0){                  try{                    Thread.sleep(1000);                    } catch(InterruptedException e){                      e.printStackTrace();                    }           System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");           }             }             finally{                   //释放锁                  lock.unlock();           }   }   }   }

这个代码卖票程序就没有什么问题了。。

●同步弊端
·效率低
·如果出现了同步嵌套,就容易产生死锁问题
●死锁问题及其代码
·是指两个或者两个以上的线程在执行的过程中因争夺资源互相等待现象
·同步代码块的嵌套案例

举例:中国人,美国人吃饭案例
正常情况:
中国人:筷子两支
美国人:刀和叉
现在:
中国人:筷子一支 刀一把
美国人:筷子一支 叉一把
要求中国人必须两支筷子才能吃饭 美国人得拿到刀和叉

public class DieLockDemo{    public static void main(String [] args){       DieLock dl1=new DieLock(true);       DieLock dl2=new DieLock(false);       dl1.start();       dl2.start();    }}public class MyLock{       //创建两把锁对象            public  static final Object objA=new Object();            public  static final Object objB=new Object();       }public class DieLock extends Thread{    private boolean flag;    public DieLock(boolean flag){     this.flag=flag;    } @Override public void run(){     if(flag){      synchronized(MyLock.objA){        System.out.println("if objA");        synchronized(MyLock.objB){        System.out.println("if objB");        }      }     }     else     {       synchronized(MyLock.objB){           System.out.println("else objB");           synchronized(MyLock.objA){            System.out.println("else objA");           }       }     } }}

打印出来的基本就想下面图中一样

这里写图片描述
这里写图片描述

成为了死锁

其实电影票程序:不是特别的符合真实情况

多线程生产消费模式

向如下所示一样

这里写图片描述

线程间通信问题:不同种类的线程间针对同一个资源的操作

通过设置线程(生产者)和获取线程(消费者)针对同一个学生对象进行操作

        //资源类:Student       // 设置学生数据:SetThread(生产者)        //获取学生数据:GetThread(消费者)        //测试类:StudentDemopublic class StudentDemo{        public static void main(String[] args){           SetThread st=new SetThread();           GetThread ge=new GetThread();           Thread t1=new Thread(st);           Thread t2=new Thread(ge);           t1.start();           t2.start();        }}public class Student{       String name;       int age;}public class SetThread implements Runnable{     @Override     public void run(){          Student s=new Student();          s.name="123";          s.age=17;     }}public class GetThread implements Runnable{         @Override     public void run(){          Student s=new Student();          System.out.println(s.name+"---"+s.age);     }}

打印出来是

这里写图片描述

问题出在并没有针对同一个资源上 SetThread类中new个Student GetThread类中也new了个Student 两者并非同一个

解决方法:在外界把这个数据创建出来 通过构造方法传递到其他的类中

public class StudentDemo{        public static void main(String[] args){            Student s=new Student();           SetThread st=new SetThread(s);           GetThread ge=new GetThread(s);           Thread t1=new Thread(st);           Thread t2=new Thread(ge);           t1.start();           t2.start();        }}public class Student{       String name;       int age;}public class SetThread implements Runnable{         private Student s;         public SetThread(Student s){                   this.s=s;         }     @Override     public void run(){          s.name="123";          s.age=17;     }}public class GetThread implements Runnable{        private Student s;        public GetThread(Student s){                   this.s=s;         }         @Override     public void run(){          System.out.println(s.name+"---"+s.age);     }}

其实这里还有个漏洞 因为是两个线程 如果GetThread线程先抢到 那打印出来的肯定是null—0
这样搞不好 所以我们下面想方法解决这个问题

    public class StudentDemo{        public static void main(String[] args){            Student s=new Student();           SetThread st=new SetThread(s);           GetThread ge=new GetThread(s);           Thread t1=new Thread(st);           Thread t2=new Thread(ge);           t1.start();           t2.start();        }}public class Student{       String name;       int age;}public class SetThread implements Runnable{         private Student s;         private int x=0;         public SetThread(Student s){                   this.s=s;         }     @Override     public void run(){       while(true){       if(x%2==0){          s.name="123";//刚走到这里 就被别人抢到了执行权          s.age=17;          }          else          {            s.name="321";//刚走到这里 就被别人抢到了执行权            s.age=27;          }          x++;          }     }}public class GetThread implements Runnable{        private Student s;        public GetThread(Student s){                   this.s=s;         }         @Override     public void run(){          while(true){          System.out.println(s.name+"---"+s.age);          }     }}

问题:为了数据的效果好一些 我加入了循环和判断 给出了不同的值
这个时候产生了新的问题:
A.同一个数据出现多次
B.姓名和年龄不匹配

原因:

A.同一个数据出现多次
CPU的一点点时间片的执行权 就足够你执行很多次
B.姓名和年龄不匹配
线程运行的随机性导致的
线程安全问题:
A.是否是多线程环境
B.是否有共享数据
C.是否有多条语句操作共享数据
解决方案:
加锁
注意:
A.不同种类的线程都要加锁
B.不同种类的线加的锁必须是同一把

正确的代码如下:

public class SetThread implements Runnable{         private Student s;         private int x=0;         public SetThread(Student s){                   this.s=s;         }     @Override     public void run(){       while(true){       synchronized(s){//共同的锁s       if(x%2==0){          s.name="123";          s.age=17;          }          else          {            s.name="321";            s.age=27;          }          x++;          }          }     }     }public class GetThread implements Runnable{        private Student s;        public GetThread(Student s){                   this.s=s;         }         @Override     public void run(){          while(true){          synchronized(s){//共同的锁s          System.out.println(s.name+"---"+s.age);          }          }     }     }

这个线程安全是解决了,但是一样存在着如下的问题:
A.如果消费者先抢到CPU的执行权,就会去消费数据,但是现在的数据是默认值。
B.如果生产者先抢到CPU的执行权,就会去产生数据,但是呢,它产生完数据后,还继续拥有执行权,它有继续产生数据。这是有问题的,你应该等着消费者把数据消费掉,然后再生产。

正常的思路:
A.生产者
先看是否有数据 有就等待 没有就生产 生产完之后通知消费者来消费
B.消费者
先看是否有数据 有就消费 没有就等待
通知生产者生产
为了处理这样的问题,java就提供了一种机制,等待唤醒机制

虽然数据安全了,但是呢,一次一大片不好看,我就想依次的依次一个输出。
实现:
通过Java提供的等待唤醒机制解决
等待唤醒机制:
Object类中提供了三个方法:
notify();//唤醒单个线程
notifyAll();//唤醒所有线程
wait();//等待
为什么这些方法不定义在Thread类中呢?
这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象
所以,这些方法必须定义在Object类

代码如下

public class Student{    String name;    int age;    boolean flag;//默认情况是没有数据,如果是true,说明有数据}public class StudentDemo{    public static void main(String[] args){        Student s=new Student();       SetThread st=new SetThread(s);       GetThread ge=new GetThread(s);       Thread t1=new Thread(st);       Thread t2=new Thread(ge);       t1.start();       t2.start();    }}public class SetThread implements Runnable{    private Student s;    private int x=0;    public SetThread(Student s){              this.s=s;    }@Overridepublic void run(){  while(true){  synchronized(s){//共同的锁s  //判断有没有  if(s.flag){   try{     s.wait();//有数据则等待    } catch(InterruptedException e){        e.printStackTrace();    }  }  if(x%2==0){     s.name="张飞";     s.age=17;     }     else     {       s.name="赵云";       s.age=27;     }     x++;     //修改标记     s.flag=true;     //唤醒     s.notify();     }     }}}public class GetThread implements Runnable{    private Student s;    public GetThread(Student s){               this.s=s;     }     @Override public void run(){      while(true){      synchronized(s){//共同的锁s      if(!s.flag){    try{      s.wait();//有数据则等待     } catch(InterruptedException e){         e.printStackTrace();      }       }      System.out.println(s.name+"---"+s.age);      //修改标记      s.flag=false;      //唤醒线程      s.notify();      }      } } }

这样就实现了你一个我一个的场面(效果如下图)

这里写图片描述

●线程的一般情况

这里写图片描述

常见的情况:
A:新建–就绪–运行–死亡
B:新建–就绪–运行–就绪–运行–死亡
C:新建–就绪–运行–其他阻塞–就绪–运行–死亡
D:新建–就绪–运行–同步阻塞–就绪–运行–死亡
E:新建–就绪–运行–等待阻塞–同步阻塞–就绪–运行–死亡

●线程组

java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,java允许程序直接对线程组进行控制

默认情况下,所有的线程都属于主线程组。

public class ThreadGroupDemo{    public static  void main(String [] args){        method1();        method2();          }     private static void method2(){        //ThreadGroup(String name)         ThreadGroup tg=new ThreadGroup("这是一个新的组");         MyRunnable my=new MyRunnable();        //Thread(ThreadGroup group,Runnable target,String name)         Thread t1=new Thread(tg,my,"张飞");         Thread t2=new Thread(tg,my,"赵云");         System.out.println(t1.getThreadGroup().getName());         System.out.println(t2.getThreadGroup().getName());          //通过组名称设置后台线程 表示该组的线程都是后台线程(守护线程)         tg.setDaemon(true);     }     private static void method1(){        MyRunnable my=new MyRunnable();         Thread t1=new Thread(my,"张飞");         Thread t2=new Thread(my,"赵云");         //我们不知道他们属于哪个线程组          //public final ThreadGroup getThreadGroup()         ThreadGroup tg1=t1.getThreadGroup();         ThreadGroup tg2=t2.getThreadGroup();         //线程组里面的方法:public final String getName()         String name1=tg1.getName();         String name2=tg2.getName();         System.out.println(name1);         System.out.println(name2);         //通过结果我们知道了 :线程默认情况下属于main线程组         //通过下面的测试 你应该能够看到 默认情况下所有的线程都属于同一个组         System.out.println(Thread.currentThread().getThreadGroup().getName());         //我们如何修改线程所在的组呢?         //创建一个线程组         //创建其他线程的时候,把其他线程的组指定为我们自己新建的线程组     }}class MyRunnable implements Runnable{      @Override      public void run(){             for(int x=0;x<100;x++){                System.out.println(Thread.currentThread().getName()+"---"+x);             }      }}

method1()方法打印如下:

这里写图片描述

method2()方法打印如下:

这里写图片描述

●线程池

程序启动了一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,应该考虑到线程池
JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,java内置支持线程池
JDK5新增了一个Executors工厂类产生线程池,有如下几个方法
public static ExecutorService newCachedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)//创建多少个线程池
public static ExecutorService newSingleThreadExecutor() //创建一个线程池

线程池的好处:线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用

如何实现线程池的代码呢?

代码如下

public class ExecutorsDemo{      public static void main(String[] args){            //A.创建一个线程池对象,控制要创建几个线程对象           //public static ExecutorService newFixedThreadPool(int nThreads)            ExecutorService pool=Executors.newFixedThreadPool(2);           //B.可以执行Runnable对象或者Callable对象代表的线程                               // C.调用如下方法即可:                            //Future<?> submit(Runnable task)                       //<T> Future <T> submit(Callable<T> task)            pool.submit(new MyRunnable());            pool.submit(new MyRunnable());//只造了两个线程池             //D.结束线程池            pool.shutdown();    }}class MyRunnable implements Runnable{    @Override    public void run(){         for(int i=0;i<100;i++){          System.out.println(Thread.currentThread().getName()+":"+i);         }    }}

如果最后不让线程池结束 那么就会有这两个不同

这里写图片描述

这里写图片描述

上面的是停止了 下面的是没停止

从中可以看出线程池的好处

学习了线程池 你发现实现多线程的实现又多了一种方法

  • 多线程实现的方式3
          A.创建一个线程池对象,控制要创建几个线程对象                  public static ExcutorService newFixedThreadPool          B.这种线程池的线程可以执行:                   可以执行Runnable对象或者Callable对象代表的线程                  做一个类实现Runnable接口          C.调用如下方法即可                  Future<?> submit(Runnable task)                  <T> Future<T> submit(Callable<T> task)          D.我就要结束,可以嘛?             可以

实现代码如下:

public class CallableDemo{             public static void main(String [] args]{             ExcutorService pool=Excutors.newFixedThreadPool(2);//实现线程池 可以造两个线程             pool.submit(new MyCallable());             pool.submit(new MyCallable());             pool.shutdown();//线程池终止       }}//Callable:是带泛型的接口// 这里指定的泛型其实是call()方法的返回值类型class MyCallable implements Callable{              @Override           public Object call() throws Exception{              for(int x=0;x<100;x++){             System.out.println(Thread.currentThread().getName()+":"+x);             }             return null;          }}
  • 我们可以用线程池实现多线程拿来算前n个数的和

    • 代码如下:
public class Callbale {    public static void main(String[] args) throws InterruptedException, ExecutionException {        ExecutorService pool=Executors.newFixedThreadPool(2);        Future<Integer> f1=pool.submit(new MyCallable(100));        Future<Integer> f2=pool.submit(new MyCallable(200));        Integer t1=f1.get();        Integer t2=f2.get();        System.out.println(t1);        System.out.println(t2);        pool.shutdown();    }}public class MyCallable implements Callable<Integer> {//泛型 Integer          public int number;          public MyCallable(int number) {            this.number=number;        }               @Override            public Integer call() throws Exception {                int sum=0;                for(int i=1;i<=number;i++){                     sum+=i;                }                return sum;            }}
  • 匿名内部类实现多线程

    • 方法
           new 类名或者接口名(){              重写方法;           }; 本质: 是该类或者接口的子类对象
  • 代码如下:
                        new Thread(){                   public void run() {                       for(int i=0;i<100;i++){                           System.out.println(Thread.currentThread().getName()+":"+i);                       }                   };               }.start();              new Thread(new Runnable() {                @Override                public void run() {                     for(int i=0;i<100;i++){                           System.out.println(Thread.currentThread().getName()+":"+i);                       }                }            }){}.start();//这个中间的大括号内也可以写 比如下面                      new Thread(new Runnable() {                @Override                public void run() {                     for(int i=0;i<100;i++){                           System.out.println("hello"+":"+i);                       }                }            }){@Override                public void run() {                 for(int i=0;i<100;i++){                       System.out.println("world"+":"+i);                   }                        }}.start();//但它打印的确实world 没有执行hello

定时器

  • 定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务已经以后台线程的方式执行

依赖Timer和TimerTask这两个类

  • Timer: 定时
    public Timer()
    public void schedule(TimerTask task,long delay){}//delay秒后 只一次 不循环
    public void schedule(TimerTask task,long delay,long period){}//delay秒后开始 period为其多久循环一次
    public void cancel()//终止
  • TimerTask: 任务 抽象类 需要继承 重写方法
public class TimerDemo{         public static void main(String[] args){              Timer t=new Timer();              MyTimer myTimer=new MyTimer(t);              t.schedule(myTimer,2000);//2秒后         }}public class MyTimer extends TimerTask{      private Timer t;      public MyTimer(Timer t){            this.t=t;      }        @Override        public void run(){              System.out.println("报告");              t.cancel();//关闭        }}//像这段代码就打印一次报告后就关闭了

想循环很简单 改下

public class TimerDemo{         public static void main(String[] args){              Timer t=new Timer();              t.schedule(new MyTimer(),2000,3000);//2秒后开始 之后3秒循环一次         }}public class MyTimer extends TimerTask{        @Override        public void run(){              System.out.println("报告");        }}

多线程常见的面试题

1.多线程有几种实现方法,分别是哪几种?
  两种。  继承Thread类  实现Runnable接口  扩展一种: 实现Callable接口。这个得和线程池结合
2.同步有几种方式 分别是什么?
   两种   同步代码块   同步方法
3.启动一个线程时run()还是start()?它们的区别?
 start(); run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用 start(): 启动线程,并由JVM自动调用run()方法
4.sleep()和wait()方法的区别
sleep(): 必须指时间,不释放锁wait(): 可以不指定时间,也可以指定时间;释放锁
5.为什么wait(),notify(),notifyAll()等方法都定义在Object类中
     因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁     而Object代码任意的对象 所以 定义在这里面
6.线程的生命周期图
新建 -- 就绪 -- 运行 -- 死亡新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡建议: 画图解释
1 0