zoukankan      html  css  js  c++  java
  • 线程同步

    一、线程同步

    1、线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏。
    2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。
    二、实现同步机制的两个方法
    1。同步代码块: 
    synchronized(同一个数据){} 同一个数据:就是N条线程同时访问一个数据。 
    2。 
    同步方法: 
    public synchronized 数据返回类型 方法名(){} 
    就是使用 synchronized 来修饰某个方法,则该方法称为同步方法。对于同步方法而言,无需显示指定同步监视器,同步方法的同步监视器是 this 也就是该对象的本身(这里指的对象本身有点含糊,其实就是调用该同步方法的对象)通过使用同步方法,可非常方便的将某类变成线程安全的类,具有如下特征: 
    1,该类的对象可以被多个线程安全的访问。 
    2,每个线程调用该对象的任意方法之后,都将得到正确的结果。 
    3,每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。 
    注:synchronized关键字可以修饰方法,也可以修饰代码块,但不能修饰构造器,属性等。 
    实现同步机制注意以下几点: 安全性高,性能低,在多线程用。性能高,安全性低,在单线程用。 
     
    关键代码如下:
    编写打印机类:Printer  定义两个方法
    package cn.d.happy;
    
    public class Printer {
    
        Object o=new Object();
        
        //或在void前加synchronized
        public void print1(){
            //同步代码块
             synchronized (o){
            System.out.print("线");
            System.out.print("程");
            System.out.print("同");
            System.out.print("步");
            System.out.println();
        }
        }
        public void print2(){
            synchronized (o){
            System.out.print("噢");
            System.out.print("呵");
            System.out.println();
        }
        }
    }

    定义两个线程类 并重写run方法。继承Thread 和 实现Runnable接口 通过for循环遍历次数

    package cn.d.happy;
    
    public class MyThread extends Thread{
    
        public Printer print;
        
        @Override
    public void run() {
        //必须有该类的对象实例
        for (int i = 1; i <=10; i++) {
            print.print1();
        }
    }
    }
    package cn.d.happy;
    
    public class MyThread2 implements Runnable{
        public  Printer print;
        @Override
        public void run() {
             for (int i = 1; i <=10; i++) {
                 print.print2();
            }
            
        }
    
    }

    测试类 创建打印机对象  以及两个线程对象并进行赋值

    package cn.d.happy;
    
    public class Test {
    public static void main(String[] args) {
        //购买一个打印机
        Printer p=new Printer();
        
        //创建第一个线程对象  并且给属性赋值
        MyThread t1=new MyThread();
        t1.print=p;
        t1.start();
        
        
        //03.创建第二个线程对象 并且给属性赋值
          MyThread2 t2=new MyThread2();
          t2.print=p;
          Thread tt=new Thread(t2);
          tt.start();
    }
    }

    实现效果:

    三、Java多线程之yield()、wait()、Notify()、Notifyall()

    yield()、

    1)    通过yield ()函数,可使线程进入可执行状态,排程器从可执行状态的线程中重新进行排程。所以调用了yield()的函数也有可能马上被执行。
    2)    当调用yield ()函数后,线程不会释放它的“锁标志”。

    class TestThreadMethod extends Thread{
            public static int shareVar = 0;
            public TestThreadMethod(String name){
                super(name);
            }
            public synchronized void run(){
                for(int i=0; i<4; i++){
                    System.out.print(Thread.currentThread().getName());
                    System.out.println(" : " + i);
                    Thread.yield();
                }
            }
        }
        public class TestThread{
            public static void main(String[] args){
                TestThreadMethod t1 = new TestThreadMethod("t1");
                TestThreadMethod t2 = new TestThreadMethod("t2");
                t1.start();
                t1.start(); //(1)
                //t2.start(); (2)
            }

    运行结果为:
    t1 : 0
    t1 : 1
    t1 : 2
    t1 : 3
    t1 : 0
    t1 : 1
    t1 : 2
    t1 : 3
    从结果可知调用yield()时并不会释放对象的“锁标志”。
    如果把代码(1)注释掉,并去掉代码(2)的注释,结果为:
    t1 : 0
    t1 : 1
    t2 : 0
    t1 : 2
    t2 : 1
    t1 : 3
    t2 : 2
    t2 : 3
    从结果可知,虽然t1线程调用了yield(),但它马上又被执行了。 

     wait与notify是java同步机制中重要的组成部分。结合与synchronized关键字使用,可以建立很多优秀的同步模型。
     synchronized(this){ }等价于publicsynchronized void method(){.....}
     同步分为类级别和对象级别,分别对应着类锁和对象锁。类锁是每个类只有一个,如果static的方法被synchronized关键字修饰,则在这个方法被执行前必须获得类锁;对象锁类同。
       首先,调用一个Object的wait与notify/notifyAll的时候,必须保证调用代码对该Object是同步的,也就是说必须在作用等同于synchronized(obj){......}的内部才能够去调用obj的wait与notify/notifyAll三个方法,否则就会报错:
      java.lang.IllegalMonitorStateException:current thread not owner
      在调用wait的时候,线程自动释放其占有的对象锁,同时不会去申请对象锁。当线程被唤醒的时候,它才再次获得了去获得对象锁的权利。
      所以,notify与notifyAll没有太多的区别,只是notify仅唤醒一个线程并允许它去获得锁,notifyAll是唤醒所有等待这个对象的线程并允许它们去获得对象锁,只要是在synchronied块中的代码,没有对象锁是寸步难行的。其实唤醒一个线程就是重新允许这个线程去获得对象锁并向下运行。

       notifyAll,虽然是对每个wait的对象都调用一次notify,但是这个还是有顺序的,每个对象都保存这一个等待对象链,调用的顺序就是这个链的顺序。其实启动等待对象链中各个线程的也是一个线程,在具体应用的时候,需要注意一下。

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

    wait():

    等待对象的同步锁,需要获得该对象的同步锁才可以调用这个方法,否则编译可以通过,但运行时会收到一个异常:IllegalMonitorStateException。

    调用任意对象的 wait() 方法导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。

    notify():

    唤醒在等待该对象同步锁的线程(只唤醒一个,如果有多个在等待),注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

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

    notifyAll():

    唤醒所有等待的线程,注意唤醒的是notify之前wait的线程,对于notify之后的wait线程是没有效果的。

    通常,多线程之间需要协调工作:如果条件不满足,则等待;当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于wait/notify。等待机制与锁机制是密切关联的。

    例如:

    synchronized(obj) {
      while(!condition) {
      obj.wait();
      }
      obj.doSomething();
      }

    当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait()。
    在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A :

    synchronized(obj) {
      condition = true;
      obj.notify();
      }

    synchronized和wait()、notify()等的关系: 

    1.有synchronized的地方不一定有wait,notify

    2.有wait,notify的地方必有synchronized.这是因为wait和notify不是属于线程类,而是每一个对象都具有的方法,而且,这两个方法都和对象锁有关,有锁的地方,必有synchronized。

    另外,注意一点:如果要把notify和wait方法放在一起用的话,必须先调用notify后调用wait,因为如果调用完wait,该线程就已经不是currentthread了。

  • 相关阅读:
    大部分人都会做错的经典JS闭包面试题
    20071109 QQ群:ITIL和ITSM 聊天记录,欢迎大家加入QQ群:48132184
    模式窗口window.open造成Session丢失的解决方法
    Server.Transfer 方法如何传递复杂的参数
    小游戏测试你的情商
    VS.Net 开发 MSN一样缓慢出来的提示信息的方法
    Javascript 技巧大全
    ASP.NET 2.0,无刷新页面新境界! 【转】
    ASP.NET AJAX入门系列【转】
    asp.net2.0+ajax开发的无刷新聊天室Demo【转】
  • 原文地址:https://www.cnblogs.com/Zhangmin123/p/5770376.html
Copyright © 2011-2022 走看看