Java学习Day13之多线程

线程(Thread):程序执行流的最小单位

                            一个进程里是由一个或多个线程组成的

 

进程: 系统中正在运的一个应用程序(例如QQ...),由一个或多个线程组成的

          同一进程内的多个线程可共享资源

 

多线程的两种运行方式:

        1)并行 (parallel):同一时刻,有不同的线程在同时执行,根据操作系统中的任务调度器,

                                        将CPU分成冉的时间片,分配给各个程序使用

                                        微观上是串行的(单核CPU),宏观上是并行的(多核CPU)

                           

        2) 并发(concurrent) :  在同一时刻,只有一个线程在执行, 但多个线程交替执行

                                             宏观上是并行的效果

 

多线程的好处:

                    (1) 多线程,多进程可以让程序执行不被阻塞

                     (2) 充分利用多核CPU的优势,提高执行效率

 

创建线程的两种方式:

            1) 通过 继承( extends ) Thread 类,并重写它的 run() 方法(可以通过匿名内部类的方式实现)

                   格式:   Thread t = new  Thread ( new Thread () {

                                                           public void run(){

                                                                  //要执行的代码;

                                                           }

                                                });

                             //启动线程

                               t.start();

           2) 通过 实现(implements) Runnable 接口, 并重写它的 run() 方法(可以通过匿名内部类的方式实现)

                     格式:  Thread t = new Thread ( new  Runnable(){

                                                             public void run(){

                                                                         //要执行的带代码...

                                                             }                        

                                                    });

                                  

                              //启动线程

                                t.start();

 

线程中常见的方法:

                1) Thread.sleep(long n): 让当前线程 休眠  n毫秒

                2) Thread.currentThread(): 找到当前线程, main方法实际是由主线程(main)来调用的

                3)    .start() : 启动当前线程,只能调用当前线程一次,如果多次调用start(),会出现 IllegalThreadStateException 异常

                4)    .join() : 等待当前线程执行结束

                5)    .join(long n): 限时等待当前线程执行结束, 若等待 n 毫秒后,无论该线程是否执行结束,都结束等待

                6)    .wait(): 让当前线程进入等待结束,只有被notify()唤醒才能重新被执行

                7)   .wait(long n) 有时限的等待, 到n毫秒后结束等待,或是被notify

                8)   .getName() : 获取当期线程的名字

                9)   .yield() : 谦让,当前线程谦让别的线程先执行

                10)   .interrupt: 打断当前正在执行的线程( 包括 sleep() / join() 的等待 )

 

守护线程:    默认情况下,java进程需要等待所有线程都运行结束,才会结束.
                   有一种特殊的线程叫做守护线程(守护的是主线程),只要主线程运行结束,                          即使守护线程的代码没有执行完,也会跟着主线程一起结束,  

                          (  setDaemon(boolean b) : 设置当前线程为守护线程 )

 // 守护线程 
          Thread t1= new Thread(()->{
                  System.out.println("守护线程开始执行...");
                  try {
                       Thread.sleep(3000);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
                    System.out.println("守护线程结束");
            });
            t1.setDaemon(true); // 设置该线程为守护线程
            t1.start();
            Thread.sleep(1000);

    *****面试题:   直接调用 run() 方法 和 调用 start() 方法???

               直接调用 run() 方法时,是主线程调用的 run() 方法,相当于普通方法的调用,没有启动新的线程进行调用

               调用 start() 方法时,是先启动了一个新的线程,再由这个新的线程去调用 run() 方法

 

解决多线程的并发问题:  使用 sunchronized 同步机制

            格式:           

 synchronized ( 对象 ){
 
      // 要同步的代码块
 }

             每个对象都有一个自己的monitor(监视器),当一个线程调用synchronized(对象),就相当于进入了这个对象的监视器

             要检查有没有owner,如果没有,此线程成为owner;

             但如果已经有owner了,这个线程在entryset的区域等待owner的位置空出来。

             成为owner可以理解为获得了对象的锁,  在竞争的时候,是非公平的

                         注意:  synchronized必须是进入同一个对象的monitor 才有上述的效果

     

注意:  synchronized 语句块既可以保证代码块的原子性

          也可以保证代码块内变量的可见性

           但缺点是synchronized是属于重量级操作,性能会受到影响。

 

synchronized 也可以修饰用来方法    

//方式一:
        public synchronized void test() {

        }
           //等价于
        public void test() {
            synchronized(this) {// 获取的是调用此方法的 对象
    
            }
        }
   
//方式二: 
      class Test{
            public synchronized static void test() {

            }
      }
            //等价于
      public static void test() {
            synchronized(Test.class) { // 获取的是 类对象
        
            }
      }

 

volatile:  易变的,可用来修饰 成员变量 /  静态成员变量 (不能修饰局部变量)

                保证变量在多个线程之间的可见性, 但不能保证原子性

               可以防止线程从自己的高速缓存中查找变量的值,必须到主存中获取它的值

(volatile)static boolean run = true; // volatile 修饰,保证静态成员变量的可见性

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(()->{
        while(run){
            // ....
        }
    });
    t.start();

    Thread.sleep(1000);
    run = false; //一个线程对run变量的修改对于另一个线程不可见,导致了另一个线程无法停止
}

 

线程死锁                                                                                                                                            如果一组进程(或线程)中的每一个进程(或线程)都在等待仅由该组进程中的                      其他进程(或线程)才能引发的事件,那么该组进程(或线程)是死锁的(Deadlock)

                例:    A线程已经获取到了 a 锁,  B线程已经获取到了 b 锁, 此时A线程想要获取 b 锁,而B线程也想要获取 a 锁

                         两个线程都各自占着自己的锁不释放,而又想获取对方的锁,所以就导致了死锁的出现

               

Object A = new Object();
Object B = new Object();


Thread a = new Thread(()->{
    synchronized (A) { // A线程已经获取 A锁
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (B) { // A线程想要获取 B锁
            System.out.println("操作...");
        }
    }
});

Thread b = new Thread(()->{
    synchronized (B) { // B线程已经获取 B锁
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (A) { // B线程想要获取 A锁
            System.out.println("操作...");
        }
    }
});
a.start();
b.start();

 

避免死锁出现的方法:

           obj.wait():        让object监视器的线程进入 WaitSet 区域等待
           obj.notify():      让object上正在 WaitSet 区域等待的线程中随机一个唤醒
           obj.notifyAll():  让object上正在等待的线程全部唤醒

    注意:  1) wait() , notify() , notifyAll() 全部都是 Object 类的方法

              2) wait() 和 notify() 方法是多线程之间进行协作的手段,必须获取到该对象的锁( synchronized )才能使用

             3)  wait() 会释放对象的锁,进入WaitSet等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify为止

 

Object obj = new Object();

new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-0线程执行....");
        try {
            obj.wait();    // 让线程在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-0其它代码....");
    }
}).start();

new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-1线程执行....");
        try {
            obj.wait();   // 让线程在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-1其它代码....");
    }
}).start();

try {
    Thread.sleep(2000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("唤醒obj上其它线程");
synchronized (obj){
     obj.notify();    // 在obj的等待区 WaitSet 随机唤醒一个线程
    obj.notifyAll();  //将obj的等待区 WaitSet中的所有线程都唤醒
}

}

*****面试题:sleep(long n) 睡眠n毫秒, wait(long n) 等待n毫秒

         1)  sleep(long n ):是 Thread类的方法, wait(long n): 是 Object类的方法

         2) sleep(long n) 可以不配合 synchronized 使用,可单独使用; 

             wait(long n) 必须强制与 synchronized 一起使用

         3) sleep(long n): 让当前线程休眠时,该线程不会释放已经获得的对象锁

             wait(long n): 让当前该线程进入等待区时,该线程会将已经获得的对象锁释放,让别的线程有机会去得到这个锁                   

 

线程状态:    

       1) NEW(新建)  :  线程刚被创建,但是还没有调用 start方法

       2) RUNNABLE(可运行) : 当调用了start() 方法之后

       3) BLOCKED(阻塞):  当线程进入了monitor监视器区,处于entrySet里准备竞争锁的时候,处于阻塞状态

       4) WAITING(等待): 当调用了对象的wait方法,或调用了线程对象的join方法,进入了WaitSet,处于等待状态

       5) TIMED_WAITING :  当调用wait(long n) join(long n) 进入了WaitSet,处于有限时的等待状态
                                            当调用sleep(long n) 是让当前线程放弃cpu的时间片,睡眠一会

      6) TERMINATED (终止)当线程代码运行结束

五种状态:
NEW(新建), RUNNABLE(可运行) , RUNNING(正在运行), 阻塞(BLOCKED,WAITING, TIMED_WAITING )TERMINATED(终止)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

阅读更多
个人分类: javaSE学习
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭