Java_总结十三(多线程数据安全之线程同步、线程间通信)

一、多线程数据安全

  线程同步

  当两个或多个线程需要访问同一资源时,需要以某种顺序来确保该资源某一时刻只能被一个线程使用

 

同步方法

  1同步非静态方法:synchronized放在方法声明中,表示整个方法为同步方法,锁定this对象

    如果有一个线程进入了该方法,其他线程要想使用当前this对象的任何同步方法,都必须等待前一个线程执行完该同步方法之后

 

 

  2同步static方法:synchronized放在static方法声明中,表示锁定该类的class对象(Xxx.class,Class类型的,是描述一个类的信息的对象)

        --如果有一个线程进入了该方法,其他线程要想使用当前类中的任何同步静态方法,都必须等待前一个线程执行完该同步方法之后;其他非同步方法及非静态的同步方法的执行不受影响

 

 

同步代码块:synchronized放在对象前面限制一段代码的执行

  

同步条件:

    --必须要有两个或者两个以上的线程 

    --必须是多个线程使用同一个锁 

 

缺点:

  多个线程需要判断锁,较为消耗资源

 

同步线程例子(在main方法中,创建多个线程)

    public static void main(String[] args) {
        TicketRes tr = new TicketRes();
        Thread t1 = new Thread(tr);
        Thread t2 = new Thread(tr);
        Thread t3 = new Thread(tr);
        t1.start();
        t2.start();
        t3.start();
    }

 

 

同步代码块的应用(单例模式中懒汉式是线程不安全的,同步可以解决不安全问题)

  ①懒汉式单例(相对于之前学过的"饿汉式单例")

 

二、锁的选择

  锁的选择:

    --多个线程共享资源,为了保证数据安全,需要同步

    --一般情况下选择资源作为锁(必须为引用类型)即可,也可以选择其他唯一的对象

  死锁:

    死锁发生的条件:

      --同步中嵌套同步

    --锁不同

 

三、守护线程

  Java中有两类线程:用户线程 (User Thread)、守护线程 (Daemon Thread)

      --所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。

      --用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。

 

  将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点:

     --thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 

         --Daemon线程中产生的新线程也是Daemon的。

--守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

 

 

四、线程间通信

  Java实现线程通信的方法

    ①wait()方法

       --挂起当前线程,并释放共享资源的锁

    ②notify()方法

       --在因调用该对象的wait()方法而阻塞的线程中随机选择一个解除阻塞,但要等到获得锁后才可真正执行

    ③notifyAll()方法

       --将因调用该对象的wait()方法而阻塞的所有线程一次性全部解除阻塞

package com.qf.demo2;
// 12A34B56C
/**
 *  线程间通信  需要放在线程同步中
 * 1 资源类中方法  是同步的
 * 2  boolean标志   标志状态
 * 3  方法里面 先判断状态(是否需要等待)
 * 4  如果不需要等待 执行 打印 加减操作
 * 5  执行完操作以后需要切换状态
 * 6  唤醒对象线程 
 *
 */
public class Resource {

    boolean flag = true;  // true  打印了字母还没有打印数字      字母等着  数字打印
                          // false  打印了数字了 还没有打印字母     数字等着  字母打印
    
    // 先打印两个数字
    public synchronized void printNum(int num){
        if(flag==false){// 打印了数字  还没打印字母
            try {
                this.wait();// 数字等着
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        //  true 打印了字母 还没打印数字    打印数字
        System.out.print(num+""+(num+1));// 1   12  34
        //已经打印完了数字了    切换状态
        flag = false;
        //唤醒字母线程
        this.notify();
        
    }
    // 在打印一个字母
    public synchronized void printLetter(int letter){
        if(flag == true){//打印了字母还没有打印数字   
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        // false  打印了数字还没打印字母   打印字母
        System.out.print((char)(letter+65));
        // 打印完了字母了 ,需要切换状态
        //切换成  打印完了字母,还没打印数字  true
        flag = true;
        // 唤醒对方线程
        this.notify();
    }
}

 

package com.qf.demo2;

public class Test {

    public static void main(String[] args) {
        Resource resource = new Resource();
        
        Number number = new Number(resource);
        Letter letter = new Letter(resource);
        
        Thread thread = new Thread(number);
        Thread thread2 = new Thread(letter);
        
        thread.start();
        thread2.start();
    }
}

class Number implements Runnable{
    Resource resource ;
    public Number(Resource resource) {
        this.resource = resource;
    }
    @Override
    public void run() {
        for (int i = 1; i < 52; i+=2) {
            // 1  3   5  7  9 
            resource.printNum(i);
        }
        
    }
}

class Letter implements Runnable{
    Resource resource ;
    public Letter(Resource resource) {
        this.resource = resource;
    }
    @Override
    public void run() {
        for (int i = 0; i < 26; i++) {
            resource.printLetter(i);
        }
        
    }
}

 

 

总结:

    ①wait( )notify( )notifyAll( )都不属于Thread类,而是属于Object基础类,也就是每个对象都有wait( )notify( )notifyAll( ) 的功能,因为每个对象都有锁,锁是每个对象的基础,当然操作锁的方法也是最基础了。

 

    ②当需要调用以上的方法的时候,一定要对竞争资源进行加锁,如果不加锁的话,则会报 IllegalMonitorStateException 异常

 

    ③当想要调用wait( )进行线程等待时,必须要取得这个锁对象的控制权(对象监视器),一般是放到synchronized(obj)代码中。

④在while循环里而不是if语句下使用wait,这样,会在线程暂停恢复后都检查wait的条件,并在条件实际上并未改变的情况下处理唤醒通知

 

    ⑤调用obj.wait( )释放了obj的锁,否则其他线程也无法获得obj的锁,也就无法在synchronized(obj){ obj.notify() } 代码段内唤醒A

 

    ⑥notify( )方法只会通知等待队列中的第一个相关线程(不会通知优先级比较高的线程)

 

    ⑦notifyAll( )通知所有等待该竞争资源的线程(也不会按照线程的优先级来执行)

 

    ⑧假设有三个线程执行了obj.wait( ),那么obj.notifyAll( )则能全部唤醒tread1thread2thread3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,tread1thread2thread3只有一个有机会获得锁继续执行,例如tread1,其余的需要等待thread1释放obj锁之后才能继续执行。

 

    ⑨当调用obj.notify/notifyAll后,调用线程依旧持有obj锁,因此,thread1thread2thread3虽被唤醒,但是仍无法获得obj锁。直到调用线程退出synchronized块,释放obj锁后,thread1thread2thread3中的一个才有机会获得锁继续执行

 

 

posted @ 2017-05-14 10:19  Java_皮卡丘漏电  阅读(128)  评论(0编辑  收藏  举报