zoukankan      html  css  js  c++  java
  • java 线程详解

    Java线程:概念与原理


    一、进程与线程
            进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间)。进程不依赖于线程而独立存在,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

            线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,线程没有自己的虚拟地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。

            “同时”执行是人的感觉,在线程之间实际上轮换执行。

            进程在执行过程中拥有独立的内存单元,进程有独立的地址空间,而多个线程共享内存,从而极大地提高了程序的运行效率。

            线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

         多个线程或进程”同时”运行只是我们感官上的一种表现。事实上进程和线程是并发运行的,OS的线程调度机制将时间划分为很多时间片段(时间片),尽可能均匀分配给正在运行的程序,获取CPU时间片的线程或进程得以被执行,其他则等待。

    线程状态:

    New:当我们创建一个线程时,该线程并没有纳入线程调度,其处于一个new状态。

    Runnable:当调用线程的start方法后,该线程纳入线程调度的控制,其处于一个可运行状态,等待分配时间片段以并发运行。

    Running:当该线程被分配到了时间片段后其被CPU运行,这是该线程处于running状态。

    Blocked:当线程在运行过程中可能会出现阻塞现象,比如等待用户输入信息等。但阻塞状态不是百分百出现的,具体要看代码中是否有相关需求。

    Dead:当线程的任务全部运行完毕,或在运行过程中抛出了一个未捕获的异常,那么线程结束,等待GC回收

            进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。

            线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

            线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程包含以下内容:

            一个指向当前被执行指令的指令指针;
            一个栈;
            一个寄存器值的集合,定义了一部分描述正在执行线程的处理器状态的值
            一个私有的数据区。
            我们使用Join()方法挂起当前线程,直到调用Join()方法的线程执行完毕。该方法还存在包含参数的重载版本,其中的参数用于指定等待线程结束的最长时间(即超时)所花费的毫秒数。如果线程中的工作在规定的超时时段内结束,该版本的Join()方法将返回一个布尔量True。

            简而言之:

    一个程序至少有一个进程,一个进程至少有一个线程。
    线程的划分尺度小于进程,使得多进程程序的并发性高。
    另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
    线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
    从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

            在Java中,每次程序运行至少启动2个线程:一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程。

    二、Java中的线程


            在Java中,“线程”指两件不同的事情:

            1、java.lang.Thread类的一个实例;

            2、线程的执行。

            在 Java程序中,有两种方法创建线程:

            一是对 Thread 类进行派生并覆盖 run方法;

            二是通过实现Runnable接口创建。

            使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。

            一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。

            Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。

            一个Java应用总是从main()方法开始运行,main()方法运行在一个线程内,他被称为主线程。

            一旦创建一个新的线程,就产生一个新的调用栈。

            线程总体分两类:用户线程和守候线程。

            当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的。

    Java线程:创建(两种方式)与启动


    一、定义线程
            1、扩展java.lang.Thread类。

            此类中有个run()方法,应该注意其用法:public void run()

            如果该线程是使用独立的Runnable运行对象构造的,则调用该Runnable对象的run方法;否则,该方法不执行任何操作并返回。

            Thread的子类应该重写该方法。

           2、实现java.lang.Runnable接口。

            void run()

            使用实现接口Runnable的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的run方法。

            方法run的常规协定是,它可能执行任何所需的操作。
    第一种方式:

    /*
     * 多线程
     * 线程可以让我们"同时"运行多段代码
     * 线程运行代码是并发的,并不是真正的同时运行
     * 
     * 线程有两种创建方式:
     * 1.继承Thread并重写run 方法,在run方法中定义线程要执行的任务(需要并发指定的代码)
     * 2.实现Runnable接口
     */
    public class ThreadDemo1 {
    
        public static void main(String[] args) {
            Thread t1=new MyThread1();
            Thread t2=new MyThread2();
            
            t1.start();
            t2.start();
    
        } 
    }
    
    class MyThread1 extends Thread{
        public void run() {
            for(int i=0;i<1000;i++) {
                System.out.println("你是谁啊?");
            }
        }
    }
    
    
    class MyThread2 extends Thread{
        public void run() {
            for(int i=0;i<1000;i++) {
                System.out.println("我是查水表的!");
            }
        }
    }

    第二种方式:

    /*
     * 第二种创建线程的方式
     * 实现Runnable接口并重写run方法.
     * 实现Runnable接口并重写run方法。这种做法是单独定义线程要执行的任务。
     * 
     * 第一张创建线程的方式(直接继承Thread)有两个不足:
     * 1.由于java是单继承,这就导致若继承了Thread就无法继承其他类,这在实际开发时时非常不方便的,因为不能复用其他类的方法。
     * 2.直接重写Thread的run方法在当前线程中会导致线程要执行的任务与当前线程有一个必然的耦合关系,不利于线程复用。
    
     */
    public class ThreadDemo2 {
    
        public static void main(String[] args) {
            MyRunnable1 r1=new MyRunnable1();
            Runnable r2=new MyRunnable2();
            
            Thread t1=new Thread(r1);
            Thread t2=new Thread(r2);
            
            t1.start();
            t2.start();
    
        }
    
    }
    
    
    class MyRunnable1 implements Runnable{
        public void run() {
            for(int i=0;i<1000;i++) {
                System.out.println("你是谁啊?");
            }
        }
    }
    
    
    class MyRunnable2 implements Runnable{
        public void run() {
            for(int i=0;i<1000;i++) {
                System.out.println("我是查水表的!");
            }
        }
    }

    使用匿名内部类完成线程的两种创建方式:

    public class ThreadDemo3 {
    
        public static void main(String[] args) {
            //使用直接继承线程方式
            Thread t1=new Thread() {
                public void run() {
                    for(int i=0;i<1000;i++) {
                        System.out.println("你是谁啊?");
                    }
                }
            };
            
            
            //使用实现Runnable接口方式
            Thread t2=new Thread(new Runnable() {
                public void run() {
                    for(int i=0;i<1000;i++) {
                        System.out.println("我是查水表的!");
                    }
                }
            });
    
            t1.start();
            t2.start();
    
    }
    }

    获取线程信息的相关方方法:

            //获取运行main方法的线程
            Thread main=Thread.currentThread();
            
            //获取线程的名字
            String name=main.getName();
            System.out.println("name:"+name);
            
            //获取唯一标识
            long id=main.getId();
            System.out.println("id:"+id);
            
            //优先级
            int priority=main.getPriority();
            System.out.println("优先级:"+priority);
            
            //线程是否处于活动状态
            boolean isAlive=main.isAlive();
            System.out.println("isAlive:"+isAlive);
            
            //是否为守护线程
            boolean isDeamon=main.isDaemon();
            System.out.println("isDeamon:"+isDeamon);
            
            //是否被中断
            boolean isInterrupted=main.isInterrupted();
            System.out.println("isInterrupted:"+isInterrupted);

    运行结果:

    一些常见问题
            1、线程的名字,一个运行中的线程总是有名字的,名字有两个来源,一个是虚拟机自己给的名字,一个是你自己的定的名字。在没有指定线程名字的情况下,虚拟机总会为线程指定名字,并且主线程的名字总是mian,非主线程的名字不确定。

            2、线程都可以设置名字,也可以获取线程的名字,连主线程也不例外。

            3、获取当前线程的对象的方法是:Thread.currentThread();

            4、在上面的代码中,只能保证:每个线程都将启动,每个线程都将运行直到完成。一系列线程以某种顺序启动并不意味着将按该顺序执行。对于任何一组启动的线程来说,调度程序不能保证其执行次序,持续时间也无法保证。

            5、当线程目标run()方法结束时该线程完成。

            6、一旦线程启动,它就永远不能再重新启动。只有一个新的线程可以被启动,并且只能一次。一个可运行的线程或死线程可以被重新启动。

            7、线程的调度是JVM的一部分,在一个CPU的机器上上,实际上一次只能运行一个线程。一次只有一个线程栈执行。JVM线程调度程序决定实际运行哪个处于可运行状态的线程。

            众多可运行线程中的某一个会被选中做为当前线程。可运行线程被选择运行的顺序是没有保障的。

            8、尽管通常采用队列形式,但这是没有保障的。队列形式是指当一个线程完成“一轮”时,它移到可运行队列的尾部等待,直到它最终排队到该队列的前端为止,它才能被再次选中。事实上,我们把它称为可运行池而不是一个可运行队列,目的是帮助认识线程并不都是以某种有保障的顺序排列而成一个一个队列的事实。

            9、尽管我们没有无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。

    线程优先级

     void setPriority(int priority)

    线程的切换是由线程调度控制的,我们无法通过代码来干涉,但是我们可以通过提高线程的优先级来最大程度的改善线程获取时间片的几率。

    线程的优先级被划分为10级,值分别为1-10,其中1最低,10最高。线程提供了3个常量来表示最低,最高,以及默认优先级:

    /**
     * 程序优先级
     * 
     * 线程在并发运行的过程中不能干涉线程调度的工作,即:不能主动获取CPU时间片,也不能决定时间片长度,只能被动分配。
     * 
     * 调整线程的优先级可以最大程度的改善线程获取时间片的几率
     * 
     * 线程的优先级有10个等级,分别用整数1-10表示,其中1为最低优先级,10为最高 优先级,5为默认
     * 
     * 理论上线程优先级越高的线程获取CPU时间片的次数越多。
     * @author jiyiyuan
     *
     */
    public class Thread_setPriority {
    
        public static void main(String[] args) {
            
            Thread max=new Thread() {
                public void run() {
                    for(int i=0;i<10000;i++) {
                        System.out.println("max");
                    }
                }
            };
            
            Thread main=new Thread() {
                public void run() {
                    for(int i=0;i<10000;i++) {
                        System.out.println("main");
                    }
                }
            };
            
            Thread nor=new Thread() {
                public void run() {
                    for(int i=0;i<10000;i++) {
                        System.out.println("nor");
                    }
                }
            };
            
            max.setPriority(10);
            main.setPriority(1);
            
            max.start();
            main.start();
            nor.start();
    
        }
    
    }

    守护线程

    void setDaemon(boolean )

    守护线程与普通线程在表现上没有什么区别,我们只需要通过Thread提供的方法来设定即可:

    当参数为true时该线程为守护线程。

    守护线程的特点是,当进程中只剩下守护线程时,所有守护线程强制终止。

    GC就是运行在一个守护线程上的。

    需要注意的是,设置线程为后台线程要在该线程启动前设置。

    /**
     * 守护线程
     * 守护线程又称为后台线程
     * 默认创建的线程都是前台线程,而守护线程需要进行设置的,方法为:
     * void setDaemon(boolean tf)
     * 
     * 守护线程在使用上与前台线程并无区别,但是在线程结束实际上有一点不同,那就是进程结束时所有正在运行的守护线程都会强制被停止。
     * 
     * 进程结束:当一个进程中的所有前台线程都结束时,进程结束。
     * @author jiyiyuan
     *
     */
    public class Thread_setDaemon {
    
        public static void main(String[] args) {
            Thread rose=new Thread() {
                public void run() {
                    for(int i=0;i<5;i++) {
                        System.out.println("rose:let me go!");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {            
                            e.printStackTrace();
                        }
                    }
                    System.out.println("啊啊啊啊啊");
                    System.out.println("效果:噗通!");
                    
                }
            };
            
            Thread jack=new Thread() {
                public void run() {
                    while(true) {
                        System.out.println("jack:you jump! i jump");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                        
                            e.printStackTrace();
                        }
                    }
                }
            };
            //设置成守护线程
            jack.setDaemon(true);
            
            rose.start();
            jack.start();
        }
    
    }

    运算结果:

    sleep方法:

    /**
     * sleep 阻塞
     * 
     * 线程提供了一个方法:
     * static void sleep(long ms)
     * 可以让运行到该方法的线程阻塞指定毫秒,当超时后,线程会自动回到RUNNAGLE 状态等待再次被分配时间片并发运行。
     * 该方法会使当前线程进入阻塞状态指定毫秒,当指定毫秒阻塞后,当前线程会重新进入Runnable状态,等待分配时间片。
     * 该方法声明抛出一个InterruptException.所以需要捕获这个异常。
     */
    public class Thread_sleep {
    
        public static void main(String[] args) {
            //100秒的倒计时
            
            System.out.println("程序开始了");
            
            try {
                for(int i=100;i>=0;i--) {
                    System.out.println(i);
                    Thread.sleep(1000);
                }
                
            } catch (InterruptedException e) {
                e.printStackTrace();
            }  
            System.out.println("程序结束了");
        }
    }

    Java线程:线程状态的转换
    一、线程状态
            线程的状态转换是线程控制的基础。线程状态总的可以分为五大状态。用一个图来描述如下:

            1、新状态:线程对象已经创建,还没有在其上调用start()方法。

            2、可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。

            3、运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

            4、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。

            5、死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

    线程的优先级和线程让步yield()

            线程的让步是通过Thread.yield()来实现的。yield()方法的作用是:暂停当前正在执行的线程对象,并执行其他线程。

            要理解yield(),必须了解线程的优先级的概念。线程总是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况。

            注意:当设计多线程应用程序的时候,一定不要依赖于线程的优先级。因为线程调度优先级操作是没有保障的,只能把线程优先级作用作为一种提高程序效率的方法,但是要保证程序不依赖这种操作。

            当线程池中线程都具有相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操作有两种可能:一是选择一个线程运行,直到它阻塞或者运行完成为止。二是时间分片,为池内的每个线程提供均等的运行机会。

    join方法:

    /**
     * 线程提供了一个方法:
     * void join
     * 该方法允许一个线程在另一个线程上等待,直到该线程工作完毕后才继续后续工作。
     * 
     * join 常用在协调线程之间同步的工作。
     * 
     * 同步运行:执行代码有先后顺序
     * 异步运行:执行代码没有先后顺序,各自执行各自的(多线程就是异步执行代码的)
     * @author jiyiyuan
     *
     */
    public class Thread_join {
        //表示图片是否下载完毕
        public static boolean isFinish=false;
        public static void main(String[] args) {
            final Thread down=new Thread() {
                public void run() {
                  System.out.println("down:开始下载图片...");
                  for(int i=1;i<=100;i++) {
                      System.out.println("down:已完成"+i+"%");
                      try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                  }
                  System.out.println("图片下载完毕!");
                  isFinish=true;
                }
            };
            
            
            Thread show=new Thread() {
                public void run() {
                    System.out.println("show:准备显示图片...");
                    /*
                     * 在显示前应当等待下载线程将图片下载完毕
                     */
                    try {
                        down.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(!isFinish) {
                        throw new RuntimeException("加载图片失败");
                    }
                    System.out.println("显示图片完毕!");
                }
            };
            
            down.start();
            show.start();
    
        }
    
    }

    小结:

            到目前位置,介绍了线程离开运行状态的3种方法:

            1、调用Thread.sleep():使当前线程睡眠至少多少毫秒(尽管它可能在指定的时间之前被中断)。

            2、调用Thread.yield():不能保障太多事情,尽管通常它会让当前运行线程回到可运行性状态,使得有相同优先级的线程有机会执行。

            3、调用join()方法:保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。

            除了以上三种方式外,还有下面几种特殊情况可能使线程离开运行状态:

            1、线程的run()方法完成。

            2、在对象上调用wait()方法(不是在线程上调用)。

            3、线程不能在对象上获得锁定,它正试图运行该对象的方法代码。

            4、线程调度程序可以决定将当前运行状态移动到可运行状态,以便让另一个线程获得运行机会,而不需要任何理由。

    补充:

    /*
     * 线程提供了一些API
     * 
     * 其中提供了一个静态的方法:
     * static Thread currentThread()
     * 该方法可以获取运行该方法的线程
     * @author jiyiyuan
     *
     */
    public class Thread_currentThread {
    
        public static void main(String[] args) {
            Thread main=Thread.currentThread();
            System.out.println("运行main方法的线程是:"+main);
            dosome();
            
            //自定义线程
            Thread t=new Thread() {
                public void run() {
                    Thread t=Thread.currentThread();
                    System.out.println(t);
                    dosome();
                }
            };
            
            t.start();
    
        }
        
        public static void dosome() {
            Thread main=Thread.currentThread();
            System.out.println("运行dosome方法的线程是:"+main);
        }
    
    }

    运行结果:

    Java线程:线程的同步与锁
    线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。

    多个线程并发读写同一个临界资源时候会发生"线程并发安全问题“

    常见的临界资源:

    • 多线程共享实例变量
    • 多线程共享静态公共变量

    若想解决线程安全问题,需要将异步的操作变为同步操作。 何为同步?那么我们来对比看一下什么是同步什么异步。

    所谓异步操作是指多线程并发的操作,相当于各干各的。

    所谓同步操作是指有先后顺序的操作,相当于你干完我再干。

    java中有一个关键字名为:synchronized,该关键字是同步锁,用于将某段代码变为同步操作,从而解决线程并发安全问题。

    二、同步和锁定
            1、锁的原理

            Java中每个对象都有一个内置锁。

            当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。

            当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。

            一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。

            释放锁是指持锁线程退出了synchronized同步方法或代码块。

            关于锁和同步,有一下几个要点:

            1)只能同步方法,而不能同步变量和类;

            2)每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?

            3)不必同步类中所有的方法,类可以同时拥有同步和非同步方法。

            4)如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。

            5)如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。

            6)线程睡眠时,它所持的任何锁都不会释放。

            7)线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。

            8)同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。

            9)在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。

    /**
     * 多线程并发安全问题当多个线程并发操作同一资源时,由于线程切换时机不确定,可能会导致代码执行混乱从而导致程序出现错误,严重时可能
     * 导致系统瘫痪。
     * @author jiyiyuan
     *
     */
    public class SyncDemo01 {
    
        public static void main(String[] args) {
            final Table table=new Table();
            Thread t1=new Thread() {
                public void run() {
                    while(true) {
                        int bean=table.getBean();
                        Thread.yield();
                        System.out.println(getName()+":"+bean);
                    }
                }
            };
            
    
            Thread t2=new Thread() {
                public void run() {
                    while(true) {
                        int bean=table.getBean();
                        Thread.yield();
                        System.out.println(getName()+":"+bean);
                    }
                }
            };
            t1.start();
            t2.start();
        }
    
    }
    
    class Table{
        private  int beans=20;
        /*
         * 这个方法称为"同步方法"同步方法多个线程不能同时在方法内部运行 这就将多个线程"抢" 着执行改为了"排队"执行
         * 
         * 在方法上使用synchronized,那么上锁的对象就是当前方法所属对象,
         * 即:方法中看到的this
         */
        public synchronized  int getBean() {
            if(beans==0) {
                throw new RuntimeException("没有豆子了");
            }
            //模拟发生线程切换
            Thread.yield();//主动让出CPU时间
            return beans--;
        }
    }

    运行结果:

     同步块:

    /**
     * 有效的缩小同步范围可以在保证并发安全的前提下提高并发效率
     * 
     * 同步块
     * synchronized(同步监视器对象){
     *    需要同步运行的代码片段
     * }
     * @author jiyiyuan
     *
     */
    public class SyncDemo02 {
    
        public static void main(String[] args) {
            final Shop shop=new Shop();
            Thread t1=new Thread() {
                public void run() {
                    shop.buy();
                }
            };
            
            Thread t2=new Thread() {
                public void run() {
                    shop.buy();
                }
            };
    
            t1.start();
            t2.start();
        }
    
    }
    
    
    class Shop{
        public void buy() {
            try {
                Thread t=Thread.currentThread();
                System.out.println(t.getName()+":正在选衣服...");
                Thread.sleep(5000);
                /*
                 * 若希望多个线程可以同步运行,必须保证同步监视器对象(上锁的对象)
                 * 是同一个。
                 * 
                 */
                synchronized(this) {
                    System.out.println(t.getName()+":正在试衣服...");
                    Thread.sleep(5000);
                }
                
                
                System.out.println(t.getName()+":结账离开....");
            }catch(Exception e) {
                e.printStackTrace();
            }
            
        }
    }

    运行结果:

    静态方法同步:

    
    

    /**
    * 静态方法若使用synchronized修饰,那么这个方法一定具有同步效果。
    * 静态方法上使用的同步监视器对象为这个类的"类对象",每个java定义的类都只有唯一的一个类对象(Class类型的的实例),而类对象后面的反射知识中会介绍。
    * @author Administrator
    *
    */
     public class SyncDemo3 {
            public static void main(String[] args) {
            Thread t1 = new Thread() {
                  public void run() {
                    Foo.dosome();
              }
           };
           Thread t2 = new Thread() {
                 public void run() {
                    Foo.dosome();
              }
          };
             t1.start();
             t2.start();
          }
      }

    
    

      class Foo{
           public synchronized static void dosome() {
    /*
    * 静态方法中若使用同步监视器对象,也可以使用
    * 当前类的类对象,获取当前类的类对象可以直接
    * 通过:类名.class得到。如下:
    */
    // synchronized (Foo.class) {
             try {
                  Thread t = Thread.currentThread();
                  System.out.println(t.getName()+":正在执行dosome方法...");
                  Thread.sleep(5000);
                  System.out.println(t.getName()+":执行dosome方法完毕!");
            } catch (Exception e) {
         }
    // }
      }
    }



    运行结果:

    互斥锁:

    /**
     * 互斥锁
     * 当使用synchronized控制多段代码,而同步监视器对象相同时,这些代码之间就形成了互斥关系。多个线程不能同时在这些代码间同时运行。
     * @author jiyiyuan
     *
     */
    public class SyncDemo05 {
    
        public static void main(String[] args) {
            final Boo boo=new Boo();
            Thread t1=new Thread() {
                public void run() {
                    boo.methodA();
                }
            };
            
            Thread t2=new Thread() {
                public void run() {
                    boo.methodB();
                }
            };
    
            t1.start();
            t2.start();
        }
    
    }
    
    class Boo{
        public synchronized void methodA() {
            try {
                Thread t=Thread.currentThread();
                System.out.println(t.getName()+":正在执行A方法....");
                Thread.sleep(5000);
                System.out.println(t.getName()+":执行A方法完毕....");
            } catch (Exception e) {
            
                e.printStackTrace();
            }
        }
        
        
        public synchronized void methodB() {
            try {
                Thread t=Thread.currentThread();
                System.out.println(t.getName()+":正在执行B方法....");
                Thread.sleep(5000);
                System.out.println(t.getName()+":执行B方法完毕....");
            } catch (Exception e) {
            
                e.printStackTrace();
            }
        }
    }

    运行结果:

    线程池:

    线程池有两个主要作用:

    1.控制线程数量

    2.重用线程

    线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。

    在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务

    /**
     * 线程池
     * 线程池主要用来:
     * 1:控制线程数量(防止CPU过度切换和过多的资源消耗)
     * 2:重用线程
     * 
     * @author Administrator
     *
     */
    public class ThreadPoolDemo {
        public static void main(String[] args) {
            ExecutorService threadPool
                = Executors.newFixedThreadPool(2);
            
            for(int i=0;i<5;i++) {
                Runnable runn = new Runnable() {
                    public void run() {
                        try {
                            Thread t = Thread.currentThread();
                            System.out.println(t.getName()+"正在执行任务...");
                            Thread.sleep(5000);
                            System.out.println(t.getName()+"执行任务完毕!");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                };
                threadPool.execute(runn);
                System.out.println("将一个任务交给了线程池.");
            }
            
            threadPool.shutdownNow();
            System.out.println("线程池停止了!");
            
            
        }
    }

    运行结果:

    线程安全类:


            当一个类已经很好的同步以保护它的数据时,这个类就称为“线程安全的”。

            即使是线程安全类,也应该特别小心,因为操作的线程之间仍然不一定安全。

            举个形象的例子,比如一个集合是线程安全的,有两个线程在操作同一个集合对象,当第一个线程查询集合非空后,删除集合中所有元素的时候。第二个线程也来执行与第一个线程相同的操作,也许在第一个线程查询后,第二个线程也查询出集合非空,但是当第一个执行清除后,第二个再执行删除显然是不对的,因为此时集合已经为空了。

    StringBuffer是线程安全的,而StringBuilder并不是,多线程下修改字符串要用StringBuffer

     对于集合而言,常用的:ArrayList,LinkedList以及HashSet,它们都不是
     线程安全的,但是可以通过Collections的方法将它们转换为线程安全的。

            List<String> list=new ArrayList<String>();
            list.add("one");
            list.add("two");
            list.add("three");
            list=Collections.synchronizedList(list);
            /*
             * 将给定的Set集合转换为线程安全的
             */
            Set<String> set=new HashSet<String>(list);
            Collections.synchronizedSet(set);
            
            /*
             * HashMap不是线程安全的
             */
            Map<String,String>map=new HashMap<String,String>();
            //转为线程安全的
            map=Collections.synchronizedMap(map);
    **
     * 双缓冲队列
     * 双缓冲队列是一个线程安全的队列,并且由于内部使用两条线程
     * 交替完成存取元素工作,实际上解决了并发存取元素时的互斥问题 更高效.
     * @author jiyiyuan
     *
     */
    public class BlockingQueueDemo {
    
        public static void main(String[] args) throws InterruptedException {
            BlockingQueue<String> queue =new LinkedBlockingDeque<String>();
            queue.offer("one");
            queue.offer("two");
            queue.size();
            
            BlockingQueue<String> queuea=new ArrayBlockingQueue<String>(100);
            queuea.offer("one",5,TimeUnit.SECONDS);
            queuea.size();
    
        }
    
    }

    如果线程不能获得锁会怎么样?


            如果线程试图进入同步方法,而其锁已经被占用,则线程在该对象上被阻塞。实质上,线程进入该对象的一种池中,必须在那里等待,直到其锁被释放,该线程再次变为可运行或运行为止。

            当考虑阻塞时,一定要注意哪个对象正被用于锁定:

            1、调用同一个对象中非静态同步方法的线程将彼此阻塞。如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。

            2、调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上。

            3、静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。

            4、对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会彼此阻塞。

    何时需要同步?


            在多个线程同时访问互斥(可交换)数据时,应该同步以保护数据,确保两个线程不会同时修改更改它。

            对于非静态字段中可更改的数据,通常使用非静态方法访问。

            对于静态字段中可更改的数据,通常使用静态方法访问。

            如果需要在非静态方法中使用静态字段,或者在静态字段中调用非静态方法,问题将变得非常复杂。

    线程同步小结:


            1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。

            2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。

            3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。

            4、对于同步,要时刻清醒在哪个对象上同步,这是关键。

            5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。

            6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。

            7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。

    2019-12-22 23:12:36

  • 相关阅读:
    约束constraint
    多表查询
    多表关系
    vue 页面跳转的两种方式
    Java三大特性
    如何搭建vue搭建手脚架(vue-cli 3.0以上版本)
    Mysql高版本不兼容group by解决方案
    springboot整合shiro 报 This application has no explicit mapping for /error, so you are seeing this as a fallback. 错误
    使用Springboot整合redis与mysql
    Springboot登录拦截器
  • 原文地址:https://www.cnblogs.com/jyy599/p/12081628.html
Copyright © 2011-2022 走看看