• 多线程 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)

    案例:卖火车票

    • 需求,有ABCD4个窗口同时买票,只有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)

    案例:卖火车票

    • 需求,有ABCD4个窗口同时买票,只有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 线程的五种状态

    • 新建,就绪,运行,阻塞,死亡
     
  • 相关阅读:
    MyEclipse中无法将SVN检出来的项目部署到tomcat中
    Hibernate n+1问题
    Dubbox框架和Zookeeper 依赖的引入
    SpringSecurity安全框架
    order
    旅游网数据库
    教学所用
    权限系统设计五张表
    springMVC上传文件
    web 开发流程
  • 原文地址:https://www.cnblogs.com/2eggs/p/12579373.html
Copyright © 2020-2023  润新知