线程基础

1.线程的几种状态

         线程共包括以下5种状态。
        1. 新建状态(New)         : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
        2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
        3. 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
        4. 阻塞状态(Blocked)  : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
            (01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
            (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
            (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
        5. 死亡状态(Dead)    : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
        这5种状态涉及到的内容包括Object类, Thread和synchronized关键字。
        Object类,定义了wait(), notify(), notifyAll()等休眠/唤醒函数。
        Thread类,定义了一些列的线程操作函数。例如,sleep()休眠函数, interrupt()中断函数, getName()获取线程名称等。
        synchronized,是关键字;它区分为synchronized代码块和synchronized方法。synchronized的作用是让线程获取对象的同步锁。

2.实现线程的两种方式

      1.实现Runnable 接口   
        如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口,如下:
        public class MyThread extends OtherClass implements Runnable {  
          public void run() {  
           System.out.println("MyThread.run()");  
          }        
        } 
     2.继承Thread类
         Thread 是一个类。Thread本身就实现了Runnable接口。如下:
            public class MyThread extends Thread {  
              public void run() {  
               System.out.println("MyThread.run()");  
              }  
                public static void main(String[] args) {
                MyThread myThread1 = new MyThread();  
                MyThread myThread2 = new MyThread();  
                myThread1.start();  
                myThread2.start();
                }
        }  
        
    
    3.Thread和Runnable的异同点
        Thread 和 Runnable 的相同点:都是“多线程的实现方式”。
        Thread 和 Runnable 的不同点:
        Thread 是类,而Runnable是接口;Thread本身是实现了Runnable接口的类。我们知道“一个类只能有一个父类,但是却能实现多个接口”,因此Runnable具有更好的扩展性。
        此外,Runnable还可以用于“资源的共享”。即,多个线程都是基于某一个Runnable对象建立的,它们会共享Runnable对象上的资源。
        
3.Thread中start()和run()的区别

    start() : 它的作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。
    run()   : run()就和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程!
    例如:
        class MyThread extends Thread{  
            public MyThread(String name) {
                super(name);
            }
        
            public void run(){
                System.out.println(Thread.currentThread().getName()+" is running");
            } 
        }; 
        
        public class Demo {  
            public static void main(String[] args) {  
                Thread mythread=new MyThread("mythread");
        
                System.out.println(Thread.currentThread().getName()+" call mythread.run()");
                mythread.run();
        
                System.out.println(Thread.currentThread().getName()+" call mythread.start()");
                mythread.start();
        }  
        运行结果:
        main call mythread.run()
        main is running
        main call mythread.start()
        mythread is running
4.synchronized关键字

1. synchronized原理
    在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。
    当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj)就获取了“obj这个对象”的同步锁。
    不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 
    例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。
    假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。
    
    synchronized关键字最主要有以下3种应用方式,
    
    修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
        所谓的实例对象锁就是用synchronized修饰实例对象中的实例方法,注意是实例方法不包括静态方法,如下
    public class AccountingSync implements Runnable{
        //共享资源(临界资源)
        static int i=0;
     
        /**
         * synchronized 修饰实例方法
         */
        public synchronized void increase(){
            i++;
        }
        @Override
        public void run() {
            for(int j=0;j<1000000;j++){
                increase();
            }
        }
        public static void main(String[] args) throws InterruptedException {
            AccountingSync instance=new AccountingSync();
            Thread t1=new Thread(instance);
            Thread t2=new Thread(instance);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
        /**
         * 输出结果:
         * 2000000
         */
    }
    注:如果将上述的main函数写成如下,便不是线程安全的,因为两个线程对应着两个不同的实例对象锁。
     public static void main(String[] args) throws InterruptedException {
            //new新实例
            Thread t1=new Thread(new AccountingSyncBad());
            //new新实例
            Thread t2=new Thread(new AccountingSyncBad());
            t1.start();
            t2.start();
            //join含义:当前线程A等待thread线程终止之后才能从thread.join()返回
            t1.join();
            t2.join();
            System.out.println(i);
     }

    修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
        当synchronized作用于静态方法时,其锁就是当前类的class对象锁。由于静态成员不专属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态成员的并发操作。需要注意的是,如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态synchronized方法,是允许的,不会发生互斥现象,因为访问静态synchronized方法占用的锁时当前类的class对象,而访问非静态synchronized方法占用的锁时当前实例对象锁。
    public class AccountingSyncClass implements Runnable{
    static int i=0;

        /**
         * 作用于静态方法,锁是当前class对象,也就是
         * AccountingSyncClass类对应的class对象
         */
        public static synchronized void increase(){
            i++;
        }
        /**
         * 非静态,访问时锁不一样不会发生互斥
         */
        public synchronized void increase4Obj(){
            i++;
        }
        @Override
        public void run() {
            for(int j=0;j<1000000;j++){
                increase();//调用的是静态同步方法,所以该程序是线程安全的。
            }
        }
        public static void main(String[] args) throws InterruptedException {
            //new新实例
            Thread t1=new Thread(new AccountingSyncClass());
            //new心事了
            Thread t2=new Thread(new AccountingSyncClass());
            //启动线程
            t1.start();
            t2.start();
            t1.join();t2.join();
            System.out.println(i);
        }
    }

    
    修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
        class MyRunable implements Runnable {    
             @Override
             public void run() {
                 synchronized(this) {
                     try {  
                         for (int i = 0; i < 5; i++) {
                             Thread.sleep(100); // 休眠100ms
                             System.out.println(Thread.currentThread().getName() + " loop " + i);  
                         }
                     } catch (InterruptedException ie) {  
                     }
                 }  
             }
         }
         public class Demo1_1 {
             public static void main(String[] args) {  
                 Runnable demo = new MyRunable();     // 新建“Runnable对象”
                 Thread t1 = new Thread(demo, "t1");  // 新建“线程t1”, t1是基于demo这个Runnable对象
                 Thread t2 = new Thread(demo, "t2");  // 新建“线程t2”, t2是基于demo这个Runnable对象
                 t1.start();                          // 启动“线程t1”
                 t2.start();                          // 启动“线程t2” 
             } 
         }
         运行结果:
        t1 loop 0
        t1 loop 1
        t1 loop 2
        t1 loop 3
        t1 loop 4
        t2 loop 0
        t2 loop 1
        t2 loop 2
        t2 loop 3
        t2 loop 4
5.线程等待与唤醒
    1.wait(), notify(), notifyAll()等方法介绍
        在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

        Object类中关于等待/唤醒的API详细信息如下:
        notify()        -- 唤醒在此对象监视器上等待的单个线程。
        notifyAll()   -- 唤醒在此对象监视器上等待的所有线程。
        wait()                                         -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
        wait(long timeout)                    -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
        wait(long timeout, int nanos)  -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
        
6.线程让步
    1. yield()介绍
·        yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
    2. yield() 与 wait()的比较
        我们知道,wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。而yield()的作用是让步,它也会让当前线程离开“运行状态”。它们的区别是:
        (01) wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,而不yield()是让线程由“运行状态”进入到“就绪状态”。
        (02) wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁。
7.线程休眠
    1. sleep()介绍
        sleep() 定义在Thread.java中。
        sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。
    2. sleep() 与 wait()的比较
        我们知道,wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。而sleep()的作用是也是让当前线程由“运行状态”进入到“休眠(阻塞)状态”。
        但是,wait()会释放对象的同步锁,而sleep()则不会释放锁。
8.join()
    1. join()介绍
        join() 定义在Thread.java中。
        join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行。这句话可能有点晦涩,我们还是通过例子去理解:
        // 主线程
        public class Father extends Thread {
            public void run() {
                Son s = new Son();
                s.start();
                s.join();
                ...
            }
        }
        // 子线程
        public class Son extends Thread {
            public void run() {
                ...
            }
        }
9.interrupt()和线程终止方式
    1. interrupt()说明
        interrupt()的作用是中断本线程。
        本线程中断自己是被允许的;其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。
        如果本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也会让它进入阻塞状态。若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。
        如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。
        如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为“true”。
        中断一个“已终止的线程”不会产生任何操作。

10.线程优先级和守护线程
    1. 线程优先级的介绍
    java 中的线程优先级的范围是1~10,默认的优先级是5。“高优先级线程”会优先于“低优先级线程”执行。
    java 中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。
    用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。需要注意的是:Java虚拟机在“用户线程”都结束后会后退出。

    JDK 中关于线程优先级和守护线程的介绍如下:
        每个线程都有一个优先级。“高优先级线程”会优先于“低优先级线程”执行。每个线程都可以被标记为一个守护进程或非守护进程。在一些运行的主线程中创建新的子线程时,子线程的优先级被设置为等于“创建它的主线程的优先级”,当且仅当“创建它的主线程是守护线程”时“子线程才会是守护线程”。
        当Java虚拟机启动时,通常有一个单一的非守护线程(该线程通过是通过main()方法启动)。JVM会一直运行直到下面的任意一个条件发生,JVM就会终止运行:
        (01) 调用了exit()方法,并且exit()有权限被正常执行。
        (02) 所有的“非守护线程”都死了(即JVM中仅仅只有“守护线程”)。
        每一个线程都被标记为“守护线程”或“用户线程”。当只有守护线程运行时,JVM会自动退出。
    例:
         class MyThread extends Thread{  
            public MyThread(String name) {
                super(name);
            }
        
            public void run(){
                for (int i=0; i<5; i++) {
                    System.out.println(Thread.currentThread().getName()
                            +"("+Thread.currentThread().getPriority()+ ")"
                            +", loop "+i);
                }
            } 
        }; 
        
        public class Demo {  
            public static void main(String[] args) {  
        
                System.out.println(Thread.currentThread().getName()
                        +"("+Thread.currentThread().getPriority()+ ")");
        
                Thread t1=new MyThread("t1");    // 新建t1
                Thread t2=new MyThread("t2");    // 新建t2
                t1.setPriority(1);                // 设置t1的优先级为1
                t2.setPriority(10);                // 设置t2的优先级为10
                t1.start();                        // 启动t1
                t2.start();                        // 启动t2
            }  
        }    
        
        运行结果:
        main(5)
        t1(1), loop 0
        t2(10), loop 0
        t1(1), loop 1
        t2(10), loop 1
        t1(1), loop 2
        t2(10), loop 2
        t1(1), loop 3
        t2(10), loop 3
        t1(1), loop 4
        t2(10), loop 4
        结果说明:
        (01) 主线程main的优先级是5。
        (02) t1的优先级被设为1,而t2的优先级被设为10。cpu在执行t1和t2的时候,根据时间片轮循调度,所以能够并发执行
        
        3. 守护线程的示例
     class MyThread extends Thread{  
         public MyThread(String name) {
             super(name);
         }
     
         public void run(){
             try {
                 for (int i=0; i<5; i++) {
                     Thread.sleep(3);
                     System.out.println(this.getName() +"(isDaemon="+this.isDaemon()+ ")" +", loop "+i);
                 }
             } catch (InterruptedException e) {
             }
         } 
     }; 
     
     class MyDaemon extends Thread{  
         public MyDaemon(String name) {
             super(name);
         }
     
         public void run(){
             try {
                 for (int i=0; i<10000; i++) {
                     Thread.sleep(1);
                     System.out.println(this.getName() +"(isDaemon="+this.isDaemon()+ ")" +", loop "+i);
                 }
             } catch (InterruptedException e) {
             }
         } 
     }
     public class Demo {  
         public static void main(String[] args) {  
     
             System.out.println(Thread.currentThread().getName()
                     +"(isDaemon="+Thread.currentThread().isDaemon()+ ")");
     
             Thread t1=new MyThread("t1");    // 新建t1
             Thread t2=new MyDaemon("t2");    // 新建t2
             t2.setDaemon(true);                // 设置t2为守护线程
             t1.start();                        // 启动t1
             t2.start();                        // 启动t2
         }  
     }
    运行结果:
    main(isDaemon=false)
    t2(isDaemon=true), loop 0
    t2(isDaemon=true), loop 1
    t1(isDaemon=false), loop 0
    t2(isDaemon=true), loop 2
    t2(isDaemon=true), loop 3
    t1(isDaemon=false), loop 1
    t2(isDaemon=true), loop 4
    t2(isDaemon=true), loop 5
    t2(isDaemon=true), loop 6
    t1(isDaemon=false), loop 2
    t2(isDaemon=true), loop 7
    t2(isDaemon=true), loop 8
    t2(isDaemon=true), loop 9
    t1(isDaemon=false), loop 3
    t2(isDaemon=true), loop 10
    t2(isDaemon=true), loop 11
    t1(isDaemon=false), loop 4
    t2(isDaemon=true), loop 12
    结果说明:
    (01) 主线程main是用户线程,它创建的子线程t1也是用户线程。
    (02) t2是守护线程。在“主线程main”和“子线程t1”(它们都是用户线程)执行完毕,只剩t2这个守护线程的时候,JVM自动退出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值