多线程同步 synchronized

几个重要的概念:
并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。让一段程序交替进行,并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果
同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。 多个线程同步就意味着多个线程并发执行的时候得到的结果跟顺序执行多个线程得到的最后的结果相同。

关于成员变量与局部变量
如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,他们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程) 。 如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。

public class MultThreadTest {
    public static void main(String[] args) {

        HelloRunnable r = new HelloRunnable();
        // 使用一个对象创建了两个线程
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        // 问两种情况,如果i为成员变量,输出多少次? 10次
        // 如果i为局部变量,输出多少次? 20次
        t1.start();
        t2.start();
    }
}

class HelloRunnable implements Runnable {
    int i = 0;   //  如果i被定义为成员变量,那么i这个值是属于该类的一个对象的,
    // 当我们用这一个对象去创建两个线程时,两个线程时共同拥有的这一个变量,因此一个线程对i的修改
    // 也会影响到另外一个线程
    @Override
    public void run() {
        int i = 0;   // 如果i被定义为局部变量,那么每个线程会单独保存一个对i的使用,两个线程间对i的使用
    //  是独立的,因此每个线程都会执行10次,因此是20次
        while (true) {
            System.out.println("Hello  " + i++);
            try {
                Thread.sleep((long) (Math.random() * 1000));
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if (10 == i) break;
        }
    }
}

volatile 关键字
多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。当一个变量使用了这个关键字后,就意味着它被线程使用的时候不被允许在线程内部保存一个副本或者拷贝,每一某个线程对该变量进行读写之后,都要重新从内存中读写该数据。
主要注意的是: 使用了volatile 关键字,并不意味这你就可以实现线程安全了,因为这个关键字的作用是让你每次读取的该变量数据都是内存中的最新的数据,当有两个线程同时读取了该数据,当时第一个线程优先完成了对其的修改,它的值变了,但是在另一个线程中的值还没变,因此会出现不同步问题。

多线程同步中synchronized的用法

第一: synchronized 修饰方法

synchronized 修饰成员方法:当synchronized关键字修饰一个方法的时候,该方法叫做同步方法
Java 中的每个对象都有一个锁(lock)或者叫做监视器(monitor) ,当访问某个对象的 synchronized 方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该 synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常) ,那么将该对象的锁释放掉,其他线程才有可能再去访问该 synchronized方法。
如果一个对象有多个 synchronized 方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何 synchronized方法的。 但是是可以访问其他的 非synchronized 方法

synchronized 修饰静态成员方法
如果某个 synchronized 方法是 static 的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的 Class对象,因为 Java 中无论一个类有多少个对象,这些对象会对应唯一一个 Class 对象,因此当线程分别访问同一个类的两个对象的两个 static synchronized 方法时,他们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行。 但是需要注意的是,因为它锁住的是 class类,而并不是对象,所以其他线程对于任何一个实例化对象 的 synchronized 方法和 非synchronized 方法 是可以访问的。

public class SynchornizedTest {
    public static void main(String[] args) {
         Print  p = new Print();
         // ThreadPrint1 里的run方法调用的是Print里面的同步静态方法
        // 现在的情况比较复杂, ThreadPrint2里的run方法调用的是Print里面的同步方法
        // ThreadPrint3里的run方法调用的是Print里面的非同步方法
        ThreadPrint tp1 = new ThreadPrint(p);
        ThreadPrint2 tp2 = new ThreadPrint2(p);
        ThreadPrint3 tp3 = new ThreadPrint3(p);
        tp1.start();
        tp2.start();
        tp3.start();
        // 输出结果,线程结果 tp1 和 tp3 和 tp2 是交替执行的

    }
}
class Print{
    public  static synchronized void printNum1(){
        for(int i = 0; i < 10; i++ ){
            System.out.println("hello" + i);
        }
    }
    public  synchronized void printNum2(){
        for(int i = 0; i < 10; i++ ){
            System.out.println("world" + i);
        }
    }
    public  void printNum3(){
        for(int i = 0; i < 10; i++ ){
            System.out.println("helloworld" + i);
        }
    }
}
class ThreadPrint extends Thread{
    private Print print;
    public ThreadPrint(Print print) {
         this.print = print;
    }
    public void run(){
        print.printNum1();
    }
}
class ThreadPrint2 extends Thread{
    private Print print;
    public ThreadPrint2(Print print) {
         this.print = print;
    }
    public void run(){
        print.printNum2();
    }
}
class ThreadPrint3 extends Thread{
    private Print print;
    public ThreadPrint3(Print print) {
         this.print = print;
    }
    public void run(){
        print.printNum3();
    }
}

第二: synchronized 修饰代码块
格式如下, 分为两种情况:
括号里锁定的是一个对象,如果要锁定当前对象,直接用this
synchronized ( Object o){ 锁定的代码 }
用法如synchronized 修饰成员方法的规则一样,同样也是给对象上锁,其他线程不允许访问该对象的其他synchronized ( Object o) 修饰的代码块

另外括号里也可以锁定一个类
synchronized ( Class c){ 锁定的代码 }
用法如synchronized 修饰静态方法的规则一样,同样也是给对象对应的class对象上锁,其他线程不允许访问class对象的的其他synchronized( Class c) 修饰的代码块

    public void printNum1() {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                System.out.println("hello" + i);
            }
        }
    }

使用synchronized 修饰代码块和修饰方法的区别:
修饰方法的时候整个方法里的代码都被同步了,但是修饰修饰代码块的时候只有修饰的块里面的代码被同步了,其他代码的访问是不受限的。

synchronized 方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法; synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的。 这两种方式在执行效率上的差别是十分巨大的,所以通常建议使用synchronized 修饰代码块这种做法。但是前提是 非synchronized 修饰的那部分代码是不会产生同步问题的。

你可能感兴趣的:(java)