多线程 java 同步 、锁 、 synchronized 、 Thread 、 Runnable

线程

1 线程概述

1.1 什么是线程

v  线程是程序执行的一条路径, 一个进程中可以包含多条线程

v  一个应用程序可以理解成就是一个进程

v  多线程并发执行可以提高程序的效率, 可以同时完成多项工作

1.2 多线程应用场景

  • VNC同时共享屏幕给多个电脑
  • 迅雷开启多条线程一起下载
  • QQ同时和多个人一起视频
  • 服务器同时处理多个客户端请求

1.3并行和并发的区别

  • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
  • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于间时间隔较短,使人感觉两个任务都在运行(画图-任务调度)。
 
 

1.4 Java程序运行原理

  • Java命令会启动java虚拟机(JVM),等于启动了一个应用程序,也就是启动了一个进程。
  • 该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法
  • 一个应用程序有且只有一个主线程,程序员不能New主线程,可以New子线程。

1.5 JVM启动的是多线程吗?

  • JVM启动至少启动了垃圾回收线程主线程,所以是多线程的。
  • main方法的代码执行的位置就是在主线程(路径)
  • 一个进程有多个线程
  • finalize()这个方法在子线程(垃圾回收线程)执行

public class Demo01 {

    public static void main(String[] args) {

/*JVM的启动是多线程的吗?【面试题】*/

       

        System.out.println("AAAAA");

        System.out.println("BBBBB");

        System.out.println("CCCCC");

        System.out.println("DDDDD");

       

        //打印线程名称

        System.out.println(Thread.currentThread());//主线程

       

        for(int i = 0;i<2;i++){

            new Student();

            System.gc();//启动垃圾回收

        }

    }

}

class Student{

    //被垃圾回收器回收时,会调用

    //对象从内存释放时,会调用

    @Override

    protected void finalize() throws Throwable {

        // TODO Auto-generated method stub

        System.out.println("student 被回收了...");

        //打印线程名称

        System.out.println(Thread.currentThread());//子线程

    }

}

2 Java中线程的实现方式

2.1方式一、继承Thread

使用步骤:

1.定义类继承Thread

2.重写run方法

3.把新线程要做的事写在run方法中

4.创建线程对象

5.开启新线程, 内部会自动执行run方法

代码:

public class Demo01 {

    public static void main(String[] args) {

        /*主线程,程序员不能创建,程序员只能创建子线程*/

       

        //1.创建子线程对象

        MyThread t1 = new MyThread();

       

        /**不能通过下面的方式来执行任务

         * 因为这种试的任务是在主线程执行的*/

        //t1.run();

       

        //2.正确的执行任务的方式,调用start,内部会开启新线程,调用run方法

        t1.start();

       

        //3.再创建子线程

        MyThread t2 = new MyThread();

        t2.start();

               

        //4.循环创建子线程

        for(int i=0;i<10;i++){

            MyThread th = new MyThread();

            th.start();

        }

       

    }

 

}

 

class MyThread extends Thread{

   

    @Override

    public void run() {

        System.out.println("银行信用卡还款短信任务..." + Thread.currentThread());

   

        System.out.println("线程名称" + this.getName());

    }

}

 

 

2.2方式二、实现Runnable接口

实现步骤:

1.定义类实现Runnable接口

2.实现run方法

3.把新线程要做的事写在run方法中

4.创建自定义的Runnable的子类对象,创建Thread对象传入Runnable

5.调用start()开启新线程, 内部会自动调用Runnable的run()方法

代码:

public class Demo01 {

    public static void main(String[] args) {

/*      线程实现的方式 (2) - 定义类实现Runnable接口

        //1.创建runable对象

        BankTask task = new BankTask();

       

        //2.创建Thread对象

        Thread t1 = new Thread(task);

       

        //3.启动线程

        t1.start();

       

        //4.再开启2个线程

        Thread t2 = new Thread(task);

        t2.start();

       

        Thread t3 = new Thread(task);

        t3.start();

    }

}

 

class BankTask  implements Runnable{

    @Override

    public void run() {

        // TODO Auto-generated method stub

        System.out.println("银行储蓄卡自动结算利息任务..." + Thread.currentThread());

       

        //System.out.println("线程名称:" + this.getName());

        System.out.println("线程名称:" +Thread.currentThread().getName());

    }

   

}

2.3两种方式的区别

区别:

  • 继承Thread : 由于子类重写了Thread类的run(), 当调用start()时直接找子类的run()方法
  • 实现Runnable : 构造函数中传入了Runnable的引用, 有个成员变量记住了它, 调用run()方法时内部判断成员变量Runnable的引用是否为空。
 

继承Thread

  • 好处是:可以直接使用Thread类中的方法,代码简单
  • 弊端是:如果已经有了父类,就不能用这种方法

实现Runnable接口

  • 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,代码更灵活
  • 弊端是:不能直接使用Thread中的方法,需要先获取到线程对象后,才能得到Thread的方法,代码复杂
 
 

2.4 匿名内部类实现线程的两种方式

public static void main(String[] args) {

        //匿名内部类实现线程的两种方式      

        /*Thread t1 = new Thread(){

            @Override

            public void run() {

                System.out.println("任务1...." + Thread.currentThread());

            }

        };

        t1.start();*/

       

        new Thread(){

            public void run() {

                System.out.println("任务1...." + Thread.currentThread());

            };

        }.start();

       

       

        /*Thread t2 = new Thread(new Runnable() {

            @Override

            public void run() {

                System.out.println("任务2...." + Thread.currentThread());

            }

        });

        t2.start();*/

        new Thread(new Runnable() {

            @Override

            public void run() {

                System.out.println("任务2...." + Thread.currentThread());

            }

        }).start();

    }

2.5 获取线程名字和设置名字

  • 通过Thread的getName()方法获取线程对象的名字
  • 通过setName(String)方法可以设置线程对象的名字
  • 通过构造函数可以传入String类型的名字
  • 每个线程系统都会默认分配个名字,主线程:main,子线程thread-0 ....

public class Demo01 {

    public static void main(String[] args) {

/*     获取线程名字和设置名字(掌握)

       //1.获取主线程对象

       Thread mainThread = Thread.currentThread();

       System.out.println(Thread.currentThread());

       System.out.println(mainThread);

       System.out.println("名称:" + mainThread.getName());

      

       //2.设置线程的名称

       mainThread.setName("主线程");

       System.out.println(mainThread);

      

       //3.设置子线程的名称

       MyThread myThread = new MyThread("子线程1");

       myThread.start();

    }

}

 

class MyThread extends Thread{

   

    public MyThread(String name) {

       super(name);

    }

 

    @Override

    public void run() {

       System.out.println("银行代发工资任务..." + Thread.currentThread());

    }

}

 

2.6 获取当前线程的对象

  • Thread.currentThread()方法用于获取当前线程对象
  • 在不同的方法中,获取的线程对象名称是有可能不一样的
  • 在main中获取的是主线程对象
  • 在子线程的run方法中获取的是子线程对象

public class Demo01 {

 

    public static void main(String[] args) {

       //获取当前线程的对象(掌握)

       Thread mainThread = Thread.currentThread();

       mainThread.setName("主线程");

       //打印主线程对象

       System.out.println(mainThread);

      

       //打印主线程对象类名

       System.out.println(mainThread.getClass());

      

       System.out.println("================");

       //开启子线程

       MyThread mt = new MyThread();

       mt.start();

    }

}

 

class MyThread extends Thread{

    @Override

    public void run() {

       System.out.println("任务...");

       Thread subThread = Thread.currentThread();

       //打印子线程对象

       System.out.println(subThread);

       //打印子线程对象类名

       System.out.println(subThread.getClass().getName());

      

    }

}

 

3 线程的其它方法

3.1 线程休眠(掌握)

  • Thread.sleep(毫秒), 控制当前线程休眠若干毫秒
  • 1秒= 1000毫秒
  • 1秒 = 1000毫秒* 1000微妙 * 1000纳秒(1000000000 )

主线程休眠

/*** 主线程休眠 */

     public static void test1() {

          for(int i=0;i<10;i++){

               System.out.println(i);

               //休眠【暂停】

               try {

                    Thread.sleep(1000);//主线程休眠

               } catch (InterruptedException e) {

                    // TODO Auto-generated catch block

                    e.printStackTrace();

               }

          }

          System.out.println("AAAAAAAAAAAAAAAAAA");

     }

子线程休眠

/**

     * 子线程休眠

     */

    public static void test2() {

         //子线程休眠

         new Thread(){

             public void run() {

                  for(int i=0;i<10;i++){

                      System.out.println(Thread.currentThread() + " " + i);

                      //休眠

                      try {

                          Thread.sleep(1000);

                      } catch (InterruptedException e) {

                          // TODO Auto-generated catch block

                          e.printStackTrace();

                      }

                  }

             };

         }.start();

        

 

         System.out.println("AAAAAAAAAAAAAAAA");

    }

3.2 守护线程(了解)

  • setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
  • 特点:男守护女,女的死,男的也不想活了
  • join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
  • join(int), 可以等待指定的毫秒之后继续

3.3 加入线程(了解)

3.4 线程让出(了解)

  • yield() 让出cpu

3.5 线程优先级

  • setPriority()设置线程的优先级
  • 默认优先级是5,最小优先级1,最高优先级10
  • 可以设置2,3,4
  • Thread里面有静态常量
  • 开发几乎不用,了解
 

4 线程与同步

什么是同步

  • 同步就是加锁,不让其它人访问
  • synchronized指的就是同步的意思
  • 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
  • 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.

什么情况下需要同步

同步代码块

  • 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
  • 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
 

同步方法

  • 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
  • 非静态同步函数的锁是:this
  • 静态同步函数的锁是:字节码对象(xx.class)

案例:卖火车票

  • 需求,有A\B\C\D4个窗口同时买票,只有100张票可买

死锁

回顾线程安全的类

  • Vector,StringBuffer,Hashtable,Collections.synchroinzed(xxx)
  • Vector是线程安全的,ArrayList是线程不安全的
  • StringBuffer是线程安全的,StringBuilder是线程不安全的
  • Hashtable是线程安全的,HashMap是线程不安全的

线程

1 线程概述

1.1 什么是线程

v  线程是程序执行的一条路径, 一个进程中可以包含多条线程

v  一个应用程序可以理解成就是一个进程

v  多线程并发执行可以提高程序的效率, 可以同时完成多项工作

1.2 多线程应用场景

  • VNC同时共享屏幕给多个电脑
  • 迅雷开启多条线程一起下载
  • QQ同时和多个人一起视频
  • 服务器同时处理多个客户端请求

1.3并行和并发的区别

  • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
  • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于间时间隔较短,使人感觉两个任务都在运行(画图-任务调度)。
 
 

1.4 Java程序运行原理

  • Java命令会启动java虚拟机(JVM),等于启动了一个应用程序,也就是启动了一个进程。
  • 该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法
  • 一个应用程序有且只有一个主线程,程序员不能New主线程,可以New子线程。

1.5 JVM启动的是多线程吗?

  • JVM启动至少启动了垃圾回收线程主线程,所以是多线程的。
  • main方法的代码执行的位置就是在主线程(路径)
  • 一个进程有多个线程
  • finalize()这个方法在子线程(垃圾回收线程)执行

public class Demo01 {

    public static void main(String[] args) {

/*JVM的启动是多线程的吗?【面试题】*/

       

        System.out.println("AAAAA");

        System.out.println("BBBBB");

        System.out.println("CCCCC");

        System.out.println("DDDDD");

       

        //打印线程名称

        System.out.println(Thread.currentThread());//主线程

       

        for(int i = 0;i<2;i++){

            new Student();

            System.gc();//启动垃圾回收

        }

    }

}

class Student{

    //被垃圾回收器回收时,会调用

    //对象从内存释放时,会调用

    @Override

    protected void finalize() throws Throwable {

        // TODO Auto-generated method stub

        System.out.println("student 被回收了...");

        //打印线程名称

        System.out.println(Thread.currentThread());//子线程

    }

}

2 Java中线程的实现方式

2.1方式一、继承Thread

使用步骤:

1.定义类继承Thread

2.重写run方法

3.把新线程要做的事写在run方法中

4.创建线程对象

5.开启新线程, 内部会自动执行run方法

代码:

public class Demo01 {

    public static void main(String[] args) {

        /*主线程,程序员不能创建,程序员只能创建子线程*/

       

        //1.创建子线程对象

        MyThread t1 = new MyThread();

       

        /**不能通过下面的方式来执行任务

         * 因为这种试的任务是在主线程执行的*/

        //t1.run();

       

        //2.正确的执行任务的方式,调用start,内部会开启新线程,调用run方法

        t1.start();

       

        //3.再创建子线程

        MyThread t2 = new MyThread();

        t2.start();

               

        //4.循环创建子线程

        for(int i=0;i<10;i++){

            MyThread th = new MyThread();

            th.start();

        }

       

    }

 

}

 

class MyThread extends Thread{

   

    @Override

    public void run() {

        System.out.println("银行信用卡还款短信任务..." + Thread.currentThread());

   

        System.out.println("线程名称" + this.getName());

    }

}

 

 

2.2方式二、实现Runnable接口

实现步骤:

1.定义类实现Runnable接口

2.实现run方法

3.把新线程要做的事写在run方法中

4.创建自定义的Runnable的子类对象,创建Thread对象传入Runnable

5.调用start()开启新线程, 内部会自动调用Runnable的run()方法

代码:

public class Demo01 {

    public static void main(String[] args) {

/*      线程实现的方式 (2) - 定义类实现Runnable接口

        //1.创建runable对象

        BankTask task = new BankTask();

       

        //2.创建Thread对象

        Thread t1 = new Thread(task);

       

        //3.启动线程

        t1.start();

       

        //4.再开启2个线程

        Thread t2 = new Thread(task);

        t2.start();

       

        Thread t3 = new Thread(task);

        t3.start();

    }

}

 

class BankTask  implements Runnable{

    @Override

    public void run() {

        // TODO Auto-generated method stub

        System.out.println("银行储蓄卡自动结算利息任务..." + Thread.currentThread());

       

        //System.out.println("线程名称:" + this.getName());

        System.out.println("线程名称:" +Thread.currentThread().getName());

    }

   

}

2.3两种方式的区别

区别:

  • 继承Thread : 由于子类重写了Thread类的run(), 当调用start()时直接找子类的run()方法
  • 实现Runnable : 构造函数中传入了Runnable的引用, 有个成员变量记住了它, 调用run()方法时内部判断成员变量Runnable的引用是否为空。
 

继承Thread

  • 好处是:可以直接使用Thread类中的方法,代码简单
  • 弊端是:如果已经有了父类,就不能用这种方法

实现Runnable接口

  • 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,代码更灵活
  • 弊端是:不能直接使用Thread中的方法,需要先获取到线程对象后,才能得到Thread的方法,代码复杂
 
 

2.4 匿名内部类实现线程的两种方式

public static void main(String[] args) {

        //匿名内部类实现线程的两种方式      

        /*Thread t1 = new Thread(){

            @Override

            public void run() {

                System.out.println("任务1...." + Thread.currentThread());

            }

        };

        t1.start();*/

       

        new Thread(){

            public void run() {

                System.out.println("任务1...." + Thread.currentThread());

            };

        }.start();

       

       

        /*Thread t2 = new Thread(new Runnable() {

            @Override

            public void run() {

                System.out.println("任务2...." + Thread.currentThread());

            }

        });

        t2.start();*/

        new Thread(new Runnable() {

            @Override

            public void run() {

                System.out.println("任务2...." + Thread.currentThread());

            }

        }).start();

    }

2.5 获取线程名字和设置名字

  • 通过Thread的getName()方法获取线程对象的名字
  • 通过setName(String)方法可以设置线程对象的名字
  • 通过构造函数可以传入String类型的名字
  • 每个线程系统都会默认分配个名字,主线程:main,子线程thread-0 ....

public class Demo01 {

    public static void main(String[] args) {

/*     获取线程名字和设置名字(掌握)

       //1.获取主线程对象

       Thread mainThread = Thread.currentThread();

       System.out.println(Thread.currentThread());

       System.out.println(mainThread);

       System.out.println("名称:" + mainThread.getName());

      

       //2.设置线程的名称

       mainThread.setName("主线程");

       System.out.println(mainThread);

      

       //3.设置子线程的名称

       MyThread myThread = new MyThread("子线程1");

       myThread.start();

    }

}

 

class MyThread extends Thread{

   

    public MyThread(String name) {

       super(name);

    }

 

    @Override

    public void run() {

       System.out.println("银行代发工资任务..." + Thread.currentThread());

    }

}

 

2.6 获取当前线程的对象

  • Thread.currentThread()方法用于获取当前线程对象
  • 在不同的方法中,获取的线程对象名称是有可能不一样的
  • 在main中获取的是主线程对象
  • 在子线程的run方法中获取的是子线程对象

public class Demo01 {

 

    public static void main(String[] args) {

       //获取当前线程的对象(掌握)

       Thread mainThread = Thread.currentThread();

       mainThread.setName("主线程");

       //打印主线程对象

       System.out.println(mainThread);

      

       //打印主线程对象类名

       System.out.println(mainThread.getClass());

      

       System.out.println("================");

       //开启子线程

       MyThread mt = new MyThread();

       mt.start();

    }

}

 

class MyThread extends Thread{

    @Override

    public void run() {

       System.out.println("任务...");

       Thread subThread = Thread.currentThread();

       //打印子线程对象

       System.out.println(subThread);

       //打印子线程对象类名

       System.out.println(subThread.getClass().getName());

      

    }

}

 

3 线程的其它方法

3.1 线程休眠(掌握)

  • Thread.sleep(毫秒), 控制当前线程休眠若干毫秒
  • 1秒= 1000毫秒
  • 1秒 = 1000毫秒* 1000微妙 * 1000纳秒(1000000000 )

主线程休眠

/*** 主线程休眠 */

     public static void test1() {

          for(int i=0;i<10;i++){

               System.out.println(i);

               //休眠【暂停】

               try {

                    Thread.sleep(1000);//主线程休眠

               } catch (InterruptedException e) {

                    // TODO Auto-generated catch block

                    e.printStackTrace();

               }

          }

          System.out.println("AAAAAAAAAAAAAAAAAA");

     }

子线程休眠

/**

     * 子线程休眠

     */

    public static void test2() {

         //子线程休眠

         new Thread(){

             public void run() {

                  for(int i=0;i<10;i++){

                      System.out.println(Thread.currentThread() + " " + i);

                      //休眠

                      try {

                          Thread.sleep(1000);

                      } catch (InterruptedException e) {

                          // TODO Auto-generated catch block

                          e.printStackTrace();

                      }

                  }

             };

         }.start();

        

 

         System.out.println("AAAAAAAAAAAAAAAA");

    }

3.2 守护线程(了解)

  • setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
  • 特点:男守护女,女的死,男的也不想活了
  • join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
  • join(int), 可以等待指定的毫秒之后继续

3.3 加入线程(了解)

3.4 线程让出(了解)

  • yield() 让出cpu

3.5 线程优先级

  • setPriority()设置线程的优先级
  • 默认优先级是5,最小优先级1,最高优先级10
  • 可以设置2,3,4
  • Thread里面有静态常量
  • 开发几乎不用,了解
 

4 线程与同步

什么是同步

  • 同步就是加锁,不让其它人访问
  • synchronized指的就是同步的意思
  • 当多线程并发, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步,否则会有线程安全问题.

什么情况下需要同步

同步代码块

  • 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
  • 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
  • 使用同步锁时,应该尽是让锁的范围小点,才能提高性能
 

同步方法

  • 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
  • 非静态同步方法的锁是:this
  • 静态同步方法的锁是:字节码对象(xx.class)

案例:卖火车票

  • 需求,有A\B\C\D4个窗口同时买票,只有100张票可买
  • 多线程会有安全问题熟记

    //火车站卖票【问题】

        /**

         * 湖南到广州火车票:今天13:00 ,100张

         * 火车站有4个窗口在同时卖票,要保证一张票只能被卖一次

         *

         * 搞4个线程表示4个窗口

         *

         * 通过加锁可以解决被多次卖同一张票的问题

         *

         * 使用同步代码块

         */

       

        //创建卖票的任务

        TicketTask task = new TicketTask();

       

        //A窗口

        Thread t1 = new Thread(task);

        t1.setName("窗口A");

        //B窗口

        Thread t2 = new Thread(task);

        t2.setName("窗口B");

        //C窗口

        Thread t3 = new Thread(task);

        t3.setName("窗口C");

        //D窗口

        Thread t4 = new Thread(task);

        t4.setName("窗口D");

       

        //开启线程

        t1.start();

        t2.start();

        t3.start();

        t4.start();

class TicketTask implements Runnable{

     //只有100张票

     int ticket = 100;

     @Override

     public synchronized void run() {

          //卖票

          while (true) {

               if (ticket <= 0) {

                    System.out.println("不好意思,票已经卖完了...");

                    break;

               } else {

                    System.out.println(Thread.currentThread() + "恭喜你卖到票,票号" + ticket);

                    ticket--;

               }

          }

     }

    

     /*@Override

     public void run() {

          // TODO Auto-generated method stub

          *//**

           * 同步代码换括号里参数可以传任意对象

           * this是一个锁对象

           * 不同的一把锁,卖相同的票总是还是存在

           *//*

         

          //卖票

          while (true) {

               synchronized (String.class) {// 同步:加锁

                    if (ticket <= 0) {

                         System.out.println("不好意思,票已经卖完了...");

                         break;

                    } else {

                         System.out.println(Thread.currentThread() + "恭喜你卖到票,票号" + ticket);

                         ticket--;

                    }

               }

          }

     }*/

    

     /*@Override

     public void run() {

          // TODO Auto-generated method stub

          *//**

           * 同步代码换括号里参数可以传任意对象

           *//*

          synchronized (this) {

               //卖票

               while(true){

                    if(ticket <= 0){

                         System.out.println("不好意思,票已经卖完了...");

                         break;

                    }else{

                         System.out.println(Thread.currentThread() + "恭喜你卖到票,票号" + ticket);

                         ticket --;

                    }

               }

          }

     }*/

}

锁的总结

/**

 * 1.锁问题:

 *  同步中,锁最好同一个对象,如果不是同一对象,还是会有线程安全问题

 *   锁:this,代表当前对象

 *   锁:如果 new 对象,就不是同一把锁

 *   锁:字节码对象 String.class,内存中,只有一个字节码对象

 *   开发中:一般都是this

 *  

 * 2.在方法内部声明synchronized的就是 “同步代码块”

 *

 * 3.在声明方法的时候,添加 synchronized,就是同步方法

 *    》如果是非静态方法,锁就是this

 *    》如果是静态方法,锁就当前类的字节码对象

 *      //TicketTask.class

         public static synchronized void test1(){}

 * 

 * 4.同步使用的建议:

 *   同步加锁的时候,尽量让锁住的代码范围小一点,这样可以让其它线程等待时间少一点,性能高

 *

 */

死锁

  • 死锁就是大家都抱着锁,不释放

public class Demo {

     static String s1 = "筷子左";

     static String s2 = "筷子右";

     public static void main(String[] args) {

          new Thread(){

               public void run() {

                    while(true){

                         synchronized (s1) {

                              System.out.println("线程A 拿到" + s1 + " 等待" + s2);

                              synchronized (s2) {

                                   System.out.println("线程A 拿到" + s2 + " 开吃");

                              }

                         }

                    }        

               };

          }.start();

         

          new Thread(){

               public void run() {

                    while(true){

                         synchronized (s2) {

                              System.out.println("线程B 拿到" + s2 + " 等待" + s1);

                              synchronized (s1) {

                                   System.out.println("线程B 拿到" + s1 + " 开吃");

                              }

                         }

                    }

               };

          }.start();

     }

}

回顾线程安全的类

  • Vector,StringBuffer,Hashtable
  • Vector是线程安全的,ArrayList是线程不安全的
  • StringBuffer是线程安全的,StringBuilder是线程不安全的
  • Hashtable是线程安全的,HashMap是线程不安全的

5单例设计模式

5.1什么是单例

  • 保证类在内存中只有一个对象。
  • 对象是new出来的,因此也就是说在程序中只能new一次对象

5.2 单例实现的基本步骤

1》声明一个类,类中有一个静态属性,类型与类名相同     

2》把空参构造方法声明为私有

3》在类中提供一个公共静态访问方法来返回该对象实例

5.3 单例的多种写法

写法一 饿汉式

class Singleton{

    private static Singleton instance = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){

       return instance;

    }

}

写法二 懒汉式

class Singleton{

    private static Singleton instance;

   

    private Singleton(){}

   

    public static Singleton getInstance(){

       if(instance == null){

           instance = new Singleton();

       }

       return instance;

    }

}

写法三 另一种简单

class Singleton{

    public static final Singleton instance = new Singleton();

    private Singleton(){}

}

5.4饿汉式和懒汉式的区别

  • 饿汉式是空间换时间,懒汉式是时间换空间
  • 在多线程访问时,饿汉式不会创建多个对象,而懒汉式有可能会创建多个对象
  • 如果考虑线程安全问题,用饿汉式
  • 如果不考虑线程安全问题,用懒汉式

5.5 Runtime类的使用

  • Runtime类是一个单例类
  • 每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。通过 getRuntime 方法获取当前运行时
  • 案例:自动关机

         Runtime r = Runtime.getRuntime();              

         r.exec("shutdown -s -t 300");//300秒后关机

         r.exec("shutdown -a"); //取消关机

6 Timer定时器

  • Timer一种工具,用于在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。
  • 方法

public void schedule(TimerTask task, long delay)

public void schedule(TimerTask task, long delay, long period)

public void schedule(TimerTask task, Date firstTime, long period)

 
 

public static void test3() {

        /**定时器的细节

         * 1.定时器在子线程中执行

         * 2.timer.cancel(); 取消定时器

         */

       

        Timer timer = new Timer();

        timer.schedule(new TimerTask() {

            int count = 5;

            @Override

            public void run() {

                // TODO Auto-generated method stub

                System.out.println("任务A:" + count +"..." + Thread.currentThread());

                count --;

                if(count == 0){

                   //取消定时器

                   timer.cancel();

                }

            }

        }, 1000,2000);

       

        //timer.cancel();//主线程

    }

7 线程间的通讯

7.1 什么时候需要通信

多个线程并发执行时, 在默认情况下CPU是随机切换线程的,如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印

7.2 线程怎么通信

》如果希望线程等待, 就调用wait()

》如果希望唤醒等待的线程, 就调用notify();

notify是随机唤醒一个线程

notifyAll是唤醒所有线程

这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用

》如果方法中没有同步锁,会有异常IllegalMonitorStateException

7.3 案例:两个线程间的通讯

public class Demo01 {

    public static void main(String[] args) {

       //1.创建任务对象

       MyTask task = new MyTask();

      

       //2.开启两个线程执行2个任务

       new Thread(){

           public void run() {

              while(true){

                  try {

                     task.task1();

                  } catch (InterruptedException e1) {

                     // TODO Auto-generated catch block

                     e1.printStackTrace();

                  }

                 

                  try {

                     Thread.sleep(10);

                  } catch (InterruptedException e) {

                     // TODO Auto-generated catch block

                     e.printStackTrace();

                  }

              }

           };

       }.start();

      

       new Thread(){

           public void run() {

              while(true){

                  try {

                     task.task2();

                  } catch (InterruptedException e1) {

                     // TODO Auto-generated catch block

                     e1.printStackTrace();

                  }

                  try {

                     Thread.sleep(10);

                  } catch (InterruptedException e) {

                     // TODO Auto-generated catch block

                     e.printStackTrace();

                  }

              }

           };

       }.start();

    }

}

 

class MyTask{

   

    //标识 1:可以执行任务1,2:可以执行任务2

    int flag = 1;

   

    public synchronized void task1() throws InterruptedException{

       if(flag != 1){

           this.wait();//当前线程等待

       }

      

       System.out.println("1.银行信用卡自动还款任务...");

       flag = 2;

       this.notify();//唤醒其它线程

      

    }

   

    public synchronized void task2() throws InterruptedException{

      

       if(flag != 2){

           this.wait();//线程等待

       }

      

       System.out.println("2.银行储蓄卡自动结算利息任务...");

       flag = 1;

       this.notify();//唤醒其它线程

    }

}

7.4 案例:三个线程间的通讯

public class Demo01 {

    public static void main(String[] args) {

       //三个线程间的通讯

       MyTask task = new MyTask();

       new Thread(){

           public void run() {

              while(true){

                  try {

                     task.task1();

                  } catch (InterruptedException e1) {

                     // TODO Auto-generated catch block

                     e1.printStackTrace();

                  }

                  try {

                     Thread.sleep(10);

                  } catch (InterruptedException e) {

                     // TODO Auto-generated catch block

                     e.printStackTrace();

                  }

              }

           };

       }.start();

       new Thread(){

           public void run() {

              while(true){

                  try {

                     task.task2();

                  } catch (InterruptedException e1) {

                     // TODO Auto-generated catch block

                     e1.printStackTrace();

                  }

                  try {

                     Thread.sleep(10);

                  } catch (InterruptedException e) {

                     // TODO Auto-generated catch block

                     e.printStackTrace();

                  }

              }

           };

       }.start();

       new Thread(){

           public void run() {

              while(true){

                  try {

                     task.task3();

                  } catch (InterruptedException e1) {

                     // TODO Auto-generated catch block

                     e1.printStackTrace();

                  }

                  try {

                     Thread.sleep(10);

                  } catch (InterruptedException e) {

                     // TODO Auto-generated catch block

                     e.printStackTrace();

                  }

              }

           };

       }.start();

    }

}

 

class MyTask{

   

    //标识 1:可以执行任务1,2:可以执行任务2, 3:可以执行任务3

    int flag = 1;

   

    public synchronized void task1() throws InterruptedException{

       if(flag != 1){

           this.wait();//当前线程等待

           //this.wait(timeout);

       }

      

       System.out.println("1.银行信用卡自动还款任务...");

       flag = 2;

       //this.notify();//唤醒随机线程

       this.notifyAll();//唤醒所有等待线程

      

    }

   

    public synchronized void task2() throws InterruptedException{

      

       if(flag != 2){

           this.wait();//线程等待

       }

      

       System.out.println("2.银行储蓄卡自动结算利息任务...");

       flag = 3;

       //this.notify();//唤醒其它线程

       this.notifyAll();

       //Thread.sleep(millis);

    }

   

    public synchronized void task3() throws InterruptedException{

           if(flag != 3){

              this.wait();//线程等待

           }

          

           System.out.println("3.银行短信提醒任务...");

           flag = 1;

           //this.notify();//唤醒其它线程

           this.notifyAll();

    }

}

7.5 线程通讯的一些疑问

1.在同步代码块中,用哪个对象锁,就用哪个对象调用wait方法

2.为什么wait方法和notify方法定义在Object这类中?

                  因为锁对象可以是任意对象,Object是所有的类的基类,所以wait方法和notify方法需要定义在Object这个类中

3.sleep方法和wait方法的区别?

         》sleep方法必须传入参数,参数就是时间,时间到了自动醒来

         》wait方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待

         》sleep方法在同步函数或同步代码块中,不释放锁,睡着了也抱着锁睡

         》wait方法在同步函数或者同步代码块中,释放锁

                         

7.6 JDK1.5新特性互斥锁

ReentrantLock介绍

  • 使用ReentrantLock类也可以实现同步加锁
  • ReentrantLock叫[互斥锁],使用lock()和unlock()方法进行同步
  • 使用ReentrantLock类的newCondition()方法可以获取Condition对象
  • 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
  • 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了

使用ReentrantLock类使用要点

                 

案例:

/**

 * 互斥锁的使用步骤

 * 1.创建互斥锁对象

 * 2.创建3个Condition

 * 3.加锁、解锁

 * 4.线程等待-Condition的await方法

 * 5.线程唤醒-Condition的signal方法

 * @author gyf

 *

 */

class MyTask{

    //创建互斥锁对象

    ReentrantLock rl = new ReentrantLock();

    //创建3个Condition

    Condition c1 = rl.newCondition();

    Condition c2 = rl.newCondition();

    Condition c3 = rl.newCondition();

   

    //标识 1:可以执行任务1,2:可以执行任务2, 3:可以执行任务3

    int flag = 1;

   

    public void task1() throws InterruptedException{

     rl.lock();//加锁

           if(flag != 1){

              c1.await();//当前线程等待

           }

          

           System.out.println("1.银行信用卡自动还款任务...");

           flag = 2;

          

           //指定唤醒线程2

           c2.signal();

     rl.unlock();//解锁

    }

   

    public void task2() throws InterruptedException{

      rl.lock();  

           if(flag != 2){

              c2.await();//线程等待

           }

          

           System.out.println("2.银行储蓄卡自动结算利息任务...");

           flag = 3;

          

           //唤醒线程3

           c3.signal();

      rl.unlock();

    }

   

    public void task3() throws InterruptedException{

     rl.lock();

           if(flag != 3){

              c3.await();//线程等待

           }

          

           System.out.println("3.银行短信提醒任务...");

           flag = 1;

          

           //唤醒线程1

           c1.signal();

     rl.unlock();

    }

}

8 线程组

8.1 概述

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

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

3.public final ThreadGroup getThreadGroup() 通过线程对象获取他所属于的组

4.public final String getName() 通过线程组对象获取组的名字

5.我们也可以给线程设置分组ThreadGroup(String name) 创建线程组对象并给其赋值名字

8.2 创建线程对象

Thread(ThreadGroup?group, Runnable?target, String?name)

8.3 代码演示

 

/**

 * 掌握:

 * 1.如何获取一个线程所属的线程组

 * 2.如果在创建一个子线程时,设置它所属的线程组

 * @author gyf

 *

 */

public class Demo01 {

    public static void main(String[] args) {

        //主线程

        Thread mainThread = Thread.currentThread();

        /**

         * [main,5,main]

         * main:线程名称

         * 5:代先级

         * main:当前线程所属的组名

         */

        System.out.println("线程:" + mainThread);

       

        //获取线程的“线程组”对象

        ThreadGroup tg = mainThread.getThreadGroup();

        System.out.println("线程组:" + tg.getName());

 

       

        //创建子线程

        Thread t1 = new Thread(){

            @Override

            public void run() {

                System.out.println("线程A...");

            }

        };

        //t1.start();

        System.out.println("t1子线程的线程组:" + t1.getThreadGroup());

       

       //创建一个线程组

        ThreadGroup abcGroup = new ThreadGroup("abc组");

        //创建子线程对象

        Thread t2 = new Thread(abcGroup, new Runnable() {

           

            @Override

            public void run() {

                // TODO Auto-generated method stub

                System.out.println("线程B");

            }

        });

        System.out.println("t2子线程的线程组:" + t2.getThreadGroup());

    }

}

9 线程池

线程池概述

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

Java的内置线程池

1.JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法

         public static ExecutorService newFixedThreadPool(int nThreads)

         public static ExecutorService newSingleThreadExecutor()

2. 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,

           可以执行Runnable对象或者Callable对象代表的线程。

           它提供了如下方法

                  Future<?> submit(Runnable task)

                  <T> Future<T> submit(Callable<T> task)

        

3.使用步骤:

         1.创建线程池对象

         2.创建Runnable实例

         3.提交Runnable实例

         4.关闭线程池es.shutdown();

4.Runnable和Callable的区别

  Runnable的run方法没有返回值

  Callable的call方法有返回值,一般返回值也没用

案例演示

public class Demo01 {

         public static void main(String[] args) {

                  //案例:10个线程完成10个洗车任务

                  /*for(int i = 0;i<10;i++){

                          new Thread(){

                                   public void run() {

                                            System.out.println("洗车任务 " + Thread.currentThread());

                                   };

                          }.start();

                  }*/

                 

                  //案例:5个线程完成10个洗车的任务

                  //1.创建线程池

                  ExecutorService es = Executors.newFixedThreadPool(5);

                 

                  //2.添加任务-方式一

                  /*for(int i=0;i<10;i++){

                          es.submit(new Runnable() {

                                   @Override

                                   public void run() {

                                            System.out.println("洗车任务 " + Thread.currentThread());

                                   }

                          });

                  }*/

                 

                  //3.添加任务-方式二

                  for(int i=0;i<10;i++){

                          es.submit(new MyTask());

                  }

                 

         }

}

class MyTask implements Callable<Integer>{

         @Override

         public Integer call() throws Exception {

                  System.out.println("洗车任务 " + Thread.currentThread());

                  return 110;

         }

        

}

10 线程的五种状态

  • 新建,就绪,运行,阻塞,死亡
 

猜你喜欢

转载自www.cnblogs.com/2eggs/p/12579373.html