Java之------多线程(加强篇)

Posted 夏小弥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java之------多线程(加强篇)相关的知识,希望对你有一定的参考价值。

加强篇

1、线程互斥锁

a、多线程互斥共享“基本数据类型数据”资源,锁(用synchronized关键字)的必须是对象,基本数据类型的变量不能当作对象锁,同时,要保证多线程使用的是同一个互斥锁(对象锁),才能进行同步。

b、多线程互斥共享“栈”资源

举例:多窗口买票

package thread.ticket.v1;  
  
public class SellingTickets {  
  
    public static void main(String[] args) {  
        Window r1=new Window("窗口1");  
        Thread t1=new Thread(r1);  
        t1.start();  
          
        Window r2=new Window("窗口2");  
        Thread t2=new Thread(r2);  
        t2.start();  
          
        Window r3=new Window("窗口3");  
        Thread t3=new Thread(r3);  
        t3.start();  
          
        Window r4=new Window("窗口4");  
        Thread t4=new Thread(r4);  
        t4.start();  
    }  
}  
  
class Window implements Runnable{  
    private static int num=200;  
    //由于基本数据类型的资源无法用作对象锁,且它是类的静态成员,  
    //因此可新建一个与共享的"基本数据类型"资源平行的对象,来代替它来做对象锁  
    private static Object obj=new Object();  
      
    private String windowName=null;  
      
    public Window(String windowName) {  
        this.windowName = windowName;  
    }  
  
    @Override  
    public void run() {  
//      synchronized (obj) {  
        //这里如果加了锁的话就会变成只有一个窗口把所有的票全部卖完了,不加的话就会是所有的窗口一起卖,  
        //而且很少出现有重复的票,但是作为软件这样做很不安全,因为在其他机器上运行很有可能会出现有重复票的现象,  
        //于是应该像下面这样把锁放到while里面去  
          
            while (true){  
                //这里不能用this来代替obj  
                synchronized (obj) {//同步块---基本数据类型的变量不能当作互斥锁。因为互斥锁是对象锁  
                    if (num > 0) {  
                        System.out.println(windowName + ":" + num--);  
                    } else {  
                        break;  
                    }  
                }  
            }  
//      }  
    }  
}  

2、多线程调度

Java的多线程是抢占式的运行方式(先启动的线程抢占到资源的几率更大些)

1) setPriority()方法 :设置优先级

只要在一个线程启动之前为他调用这个方法就可以增加抢占到资源的概率,默认是5,越小抢占资源能力越强

2) sleep()方法和interrupt()方法 :Thread类的sleep()方法对当前线程操作,是静态方法,在执行sleep()方法时不释放对象锁。sleep()的参数指定以毫秒为单位的线程休眠时间。除非因为中断而提早恢复执行,否则线程不会在这段时间之前恢复执行。可以用interrupt()来提前中断sleep()方法,也可以用抛异常的方法中断。一个线程可以调用另外一个线程的interrupt()方法,这将向暂停的线程发出一个InterruptedException。变相起到唤醒暂停线程的功能。Thread类的方法interrupt(),是一种强制唤醒的技术。

package thread.schedule.v1;  
  
public class Schedule {  
  
    public static void main(String[] args) {  
        Thread t1=new MyThread();  
        Thread t2=new MyThread();  
  
        t1.start();  
        t2.start();  
          
        try {  
            Thread.sleep(2000);  
            t1.interrupt();//过两秒钟的时候强制唤醒t1线程  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}  
  
class MyThread extends Thread{  
    private static Object obj=new Object();  
    @Override  
    public void run() {  
        synchronized (obj) {  
            try {  
                Thread.sleep(5000);  
            } catch (InterruptedException e) {  
                System.out.println(this.getName()+"已经被唤醒");  
            }  
            for (int i = 1; i <= 100; i++) {  
                System.out.println(Thread.currentThread().getName() + "--NO--"  
                        + i);  
            }  
        }  
    }    
}  
3) yield() 方法:用来使具有相同优先级的线程获得执行的机会。如果具有相同优先级的其它线程是可运行的,yield()将把线程放到可运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则什么都不做。

注意:执行一次yield()方法,该线程只是放弃当前这一次机会,然后又会重新和其它线程一起抢占CPU,很可能又比其它线程先抢到。

package cn.hncu.thread.schedule.v2;  
  
public class Schedule {  
  
    public static void main(String[] args) {  
        Thread t1=new MyThread("t1");  
        Thread t2=new MyThread("t2");  
  
        t1.start();  
//      try {  
//          t1.join();//这里如果用了join()就体现不出yield()方法了  
//      } catch (InterruptedException e) {  
//          e.printStackTrace();  
//      }  
        System.out.println("main..........");  
        t2.start();  
          
    }  
}  
  
class MyThread extends Thread{  
    private static Object obj=new Object();  
    private String threadName=null;  
      
    public MyThread(String threadName) {  
        this.threadName = threadName;  
    }  
  
    @Override  
    public void run() {  
//      synchronized (obj) {  
            System.out.println(":::::::::" + threadName);  
            int num = 0;  
            while (this.threadName.equals("t1") && num++ < 50) {  
                this.yield();//yield不会释放对象锁,因此,即使在外围环绕了synchronized也无法使该线程放弃,要一直到该线程执行完,在没有加锁的时候使用这个yield()方法的话每次t1线程到这里放弃了,但是他又会重新和t2线程抢资源  
            }  
            for (int i = 1; i <= 100; i++) {  
                System.out.println(threadName + "--NO.--" + i);  
            }  
//      }  
    }  
      
}  
4) join()方法:调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行。它可以实现线程合并的功能,经常用于线程的绝对调度。
package thread.schedule.v2;  
  
public class Schedule {  
  
    public static void main(String[] args) {  
        Thread t1=new MyThread("t1");  
        Thread t2=new MyThread("t2");  
  
        t1.start();  
        try {  
            t1.join();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("main..........");  
        t2.start();  
        //注意,这里如果把t2放在t1之前启动的话那么t2照样会和t1抢资源,不会等t1运行完,所以没有调用join()方法的线程要后启动  
    }  
}  
class MyThread extends Thread{  
    private static Object obj=new Object();  
    private String threadName=null;  
      
    public MyThread(String threadName) {  
        this.threadName = threadName;  
    }  
  
    @Override  
    public void run() {  
        System.out.println(":::::::::" + threadName);  
  
        for (int i = 1; i <= 100; i++) {  
            System.out.println(threadName + "--NO.--" + i);  
        }  
    }  
}  
5) wait()方法:当前线程进入对象的wait pool。

6) notify()/notifyAll()方法:唤醒对象的wait pool中的一个/所有等待线程

注:wait和notify只能在它们被调用的实例的同步块内使用,而sleep()到处都可以用。

wait()和sleep()最大的区别:sleep()不释放对象锁,而wait()会释放,因此从效率方面考虑wait()方法更好。

3、死锁

死锁一:

package thread.deadLock.lock1;  
  
public class DeadLock {  
  
    public static void main(String[] args) {  
        S s=new S();  
        Thread b=new Thread(new ThreadB(s));  
        Thread a=new Thread(new ThreadA(s,b));  
          
        a.start();  
        b.start();  
    }  
}  
class S {  
    public int a=0;  
}  
class ThreadA implements Runnable{  
    private S s=null;  
    private Thread b=null;  
      
    public ThreadA(S s, Thread b) {  
        this.s = s;  
        this.b = b;  
    }  
  
  
    @Override  
    public void run() {  
        System.out.println("now start ThreadA------");  
        synchronized (s) {//线程a先启动在这里拿到锁  
            System.out.println(Thread.currentThread().getName()+"--A");  
            try {  
                b.join();//这里b线程调用join()方法,即原本是要等待b运行完其他线程才可以运行,可是这时候的锁还在a手中,因此出现了a线程在等待b线程,b线程在等待a线程的现象,这是一种死锁的现象  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
            System.out.println("a="+s.a);  
        }  
          
    }  
}  
class ThreadB implements Runnable{  
    private S s=null;  
  
    public ThreadB(S s) {  
        this.s = s;  
    }  
  
    @Override  
    public void run() {  
        System.out.println("new start ThreadB------");  
        synchronized (s) {  
            s.a=100;  
            System.out.println(Thread.currentThread().getName()+"--B ,a="+s.a);  
        }  
    }  
      
}  
死锁二:

package thread.deadLock.lock2;  
  
public class DeadLock {  
  
    public static void main(String[] args) {  
        //如果要解决这种多资源出现的死锁,可以把多个资源打包成一个综合资源,  
        //把综合资源变成一个对象锁,哪个线程一拿到锁就有全部资源了  
        //在设计阶段就应该考虑到----把多线程中的每个线程所用的互斥资源图画出来--从图中看出哪些线程存在共享互斥资源,  
        //然后分析是否可能存在死锁  
          
        S1 s1=new S1();  
        S2 s2=new S2();  
        Thread a=new Thread(new ThreadA(s1,s2));  
        Thread b=new Thread(new ThreadB(s1,s2));  
        a.start();  
        b.start();  
    }  
}  
class S1 {  
    public int a=1;  
}  
class S2 {  
    public int a=2;  
}  
class ThreadA implements Runnable{  
    private S1 s1=null;  
    private S2 s2=null;  
      
    public ThreadA(S1 s1, S2 s2) {  
        this.s1 = s1;  
        this.s2 = s2;  
    }  
  
  
    @Override  
    public void run() {  
        System.out.println("now start ThreadA------");  
        synchronized (s1) {//这里a线程拿到锁  
            System.out.println(Thread.currentThread().getName()+"--A");  
            System.out.println("线程A输出,s1.a="+s1.a);  
            System.out.println("线程A拿到锁s1,但在等待锁s2");  
            synchronized (s2) {//在这里假如前面拿到锁s1的时候下面的b线程也拿到了锁s2那么这里就会出现死锁现象,下面的b线程也会出现死锁,因为a和b线程各握着彼此需要的一部分不放,因此无法继续进行下去,但也有可能在b线程没有拿到锁s2时a线程就一口气拿到锁s1和锁s2运行完了不出现死锁  
                System.out.println("线程A输出,s2.a="+s2.a);  
            }  
        }  
    }  
}  
class ThreadB implements Runnable{  
    private S1 s1=null;  
    private S2 s2=null;  
      
    public ThreadB(S1 s1, S2 s2) {  
        this.s1 = s1;  
        this.s2 = s2;  
    }  
  
  
    @Override  
    public void run() {  
        System.out.println("now start ThreadB------");  
        synchronized (s2) {  
            System.out.println(Thread.currentThread().getName()+"--B");  
            System.out.println("线程B输出,s2.a="+s2.a);  
            System.out.println("线程B拿到锁s2,但在等待锁s1");  
            synchronized (s1) {  
                System.out.println("线程B输出,s1.a="+s1.a);  
            }  
        }  
    }  
}  

4、相关概念

1、创建线程和启动线程并不相同:在一个线程对新线程的Thread对象调用start()方法之前,这个线程并没有真正开始执行。Thread对象在其线程真正启动之前就已经存在了,而且其线程退出之后仍然存在。因此,仍可以控制或获取关于已创建的线程的信息,即使线程还没有启动或已经完成了。

2、结束线程:

1)线程到达其run()方法的末尾,推荐这种方法,自然结束。
2)线程抛出一个未捕获到的Exception或Error。
3)另一个线程调用一个弃用的stop()方法(不建议使用)。

3、守护程序线程(简称守护线程):我们提到过当Java程序的所有线程都完成时,该程序就退出,但这并不完全正确,因为程序中还隐藏的系统线程。随着程序的启动而启动,在运行期间一直捕捉符合它条件的处理,这样的线程就是守护线程。

5、注意问题

1、synchronized必须锁的是对象,基本数据类型的变量不能当作对象锁。

2、要保证多线程使用的是同一个互斥锁(对象锁),才能进行同步。

3、死锁的两种情况:

1)多个线程共用同一个对象锁,互相等待。
2)互相持有对方所需的资源(即每个线程都需要同时拿到多个资源才能继续执行,而多个线程都处于:各持有一部分,在等待另一部分。)

4、死锁的解决:要从设计方面去解决避免,即在设计时就考虑不能出现死锁。
罗列出所有临界资源,画分布图,从图中观察其中的死锁情况,改变其中线程的(临界)资源的获取方式。
设计原则:尽量让程序中少出现临界资源。

5、wait/notify 和 sleep方法:wait和notify只能在它们被调用的实例的同步块内使用,而sleep()到处都可以用。wait()和sleep()最大的区别:sleep()不释放对象锁,而wait()会释放,因此从效率方面考虑wait()方法更好。

6、同步设计的基本原则:同步块中(synchronized修饰)的代码越小越好!
 同步块中不要写阻塞性代码(如,InputStream.read() )!
 在持有锁的时候,不要对其它对象调用方法。(如果做到,可以消除最常见的死锁源头。)

7、同步概述:

同步的原理:将需要同步的代码进行封装,并在该代码上加了一个锁。
同步的好处:解决多线程的安全问题。
同步的弊端:会降低性能。
同步的前提:必须要保证有多个线程且它们在同步中使用的是同一个锁。



以上是关于Java之------多线程(加强篇)的主要内容,如果未能解决你的问题,请参考以下文章

Java基础加强之并发常用的多线程实现方式

Java基础加强之并发基本概念介绍

Nginx学习笔记之加强篇

那些年做过的ctf之加密篇(加强版)

WPF 加强记忆

Java基础加强之并发Thread中start()和run()的区别