`

Java基础笔记 – 线程同步问题 解决同步问题的方法 synchronized方法 同步代码块

 
阅读更多
1、多线程的同步:
1.1、同步机制:

在多线程中,可能有多个线程试图访问一个有限的资源,必须预防这种情况的发生。所以引入了同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问。

1.2、共享成员变量的例子:
成员变量与局部变量:

成员变量:

如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作,这多个线程是共享一个成员变量的。

局部变量:

如果一个变量是局部变量,那么多个线程对同一个对象进行操作,每个线程都会有一个该局部变量的拷贝。他们之间的局部变量互不影响。

下面举例说明:

实现了Runnable的线程类:

class MyThread3 implements Runnable{

    //两个线程操作同一个对象,共享成员变量
    //int i;
    @Override
    public void run() {
        //两个线程操作同一个对象,各自保存局部变量的拷贝
        int i = 0;
        while(i<100){
            System.out.println(i);
            i++;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在main方法中用两个线程操作同一个对象:

public static void main(String[] args) {

    MyThread3 myThread = new MyThread3();
    //下面两个线程对同一个对象(Runnable的实现类对象)进行操作
    Thread thread = new Thread(myThread);
    Thread thread2 = new Thread(myThread);
    //各自保存局部变量的拷贝,互不影响,输出200个数字
    thread.start();
    thread2.start();
}

这里如果把i变成成员变量,则输出100个数字。

1.3、共享资源导致的读取错误

下面举个例子,两个线程共用一个Number对象,通过Number类的getNumber方法获取数据,读取数据并改写时,发现了重复读操作:

首先创建一个Number类:

class Number{
    private int number = 10;
    public String getNumber(int i){
        if(number > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            number -= i;
            return "取出"+i+"成功,剩余数量:"+number;
        }
        return "取出"+i+"失败,剩余数量:"+number;
    }
}

线程类,在线程类中的私有属性包含了Number类的引用:

class MyThread4 extends Thread{

    //两个线程操作同一个对象,共享成员变量
    Number number;
    public MyThread4(Number number){
        this.number = number;
    }
    @Override
    public void run() {
        System.out.println(number.getNumber(8));
    }
}

在main函数中创建两个线程类,包含了同一个Number类实例的引用:

public static void main(String[] args) {

    Number number = new Number();
    //两个线程操作同一个对象,共享对象number的成员变量number
    MyThread4 myThread = new MyThread4(number);
    MyThread4 myThread2 = new MyThread4(number);
    myThread.start();
    myThread2.start();
}

这样,当第一个线程读取Number中的number变量时先保存下来再休眠0.1秒,然后第二个线程再读取number变量并保存,此时两个线程保存了同样的数字,在修改时,也就导致修改了同一个数字两次。

2、同步机制的实现:
2.1、使用synchronized关键字创建synchronized方法:

使用synchronized关键字,该关键字修饰的方法叫做同步方法。

Java中每个对象都有一个锁或者称为监视器,当访问某个对象的synchronized方法时,表示将该对象上锁,而不仅仅是为该方法上锁。

这样如果一个对象的synchronized方法被某个线程执行时,其他线程无法访问该对象的任何synchronized方法(但是可以调用其他非synchronized的方法)。直至该synchronized方法执行完。

静态的synchronized方法调用情况:

当调用一个对象的静态synchronized方法时,它锁定的并不是synchronized方法所在的对象,而是synchronized方法所在对象对应的Class对象。这样,其他线程就不能调用该类的其他静态synchronized方法了,但是可以调用非静态的synchronized方法。

结论:执行静态synchronized方法锁方法所在对象,执行非静态synchronized方法锁方法所在对象对应的Class对象。

下面是多线程调用静态的方法的例子,由于锁定了方法所在对象对应的Class对象,其他线程无法调用该方法所在对象其他的静态synchronized方法:

/**
 * 定义一个类,包含了线程类需要调用的方法
 */
class Compute1{
    //这时如果某个线程调用该方法,
    //将锁定synchronized方法所在对象对应的class对象,
    //而不是锁定synchronized方法所在对象
    public synchronized static void execute(){
        for(int i = 0; i<100; i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("compute1:execute1 " + i++);
        }
    }
    public synchronized static void execute2(){
        for(int i = 0; i<100; i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("compute1:execute2 " + i++);
        }
    }
}

main方法中两个线程分别调用同一个对象的两个static synchronized方法:

public static void main(String[] args) {
    Compute1 com = new Compute1();
    Thread thread1 = new Thread1(com);
    Thread thread2 = new Thread2(com);
    thread1.start();
    thread2.start();
}

一次只能调用一个静态方法,直到执行完成。

2.2、使用synchronized创建同步代码块:

通过使用synchronized同步代码块,锁定一个对象,该对象作为可执行的标志从而达到同步的效果:

/**
 * 定义一个类,包含了线程类需要调用的方法
 */
class Compute1{
    //通过同步代码块锁定object1对象进行锁定了其他同样的synchronized代码块
    private Object object1 = new Object();
    public void execute(){
        synchronized(object1){
            for(int i = 0; i<100; i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("compute1:execute1 " + i++);
            }
        }

    }
    public synchronized void execute2(){
        synchronized(object1){
            for(int i = 0; i<100; i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("compute1:execute2 " + i++);
            }
        }
    }
}

如果想要使用synchronized同步代码块达到和使用synchronized方法同样的效果,可以锁定this引用:

synchronized(this){
    …
}
2.3、synchronized方法和synchronized同步代码块的区别:

synchronized同步代码块只是锁定了该代码块,代码块外面的代码还是可以被访问的。

synchronized方法是粗粒度的并发控制,某一个时刻只能有一个线程执行该synchronized方法。

synchronized同步代码块是细粒度的并发控制,只会将块中的代码同步,代码块之外的代码可以被其他线程同时访问。

分享到:
评论

相关推荐

    Java中线程同步和线程协作学习笔记

    线程同步的主要目标是解决线程安全问题,即在多线程访问共享资源时避免数据的混乱,保证程序的可再现性。这通常涉及到临界资源的管理,如打印机、共享变量或数据结构。在Java中,线程同步可以通过使用同步锁来实现,...

    Java多线程详解(超详细)_狂神说笔记完整版_项目代码_适合小白随课程学习

    - `synchronized`关键字可实现锁机制,保证同一时刻只有一个线程执行特定代码块或方法。 - `volatile`关键字确保线程间变量的可见性,但不保证原子性。 - `wait()`, `notify()`和`notifyAll()`方法用于线程间通信...

    java多线程笔记

    线程同步是为了避免多线程环境下的数据竞争问题,Java提供了多种同步机制。同步方法通过`synchronized`关键字修饰,确保同一时间只有一个线程能访问该方法。同步块(Synchronized Block)更灵活,可以指定同步的代码...

    JAVA 多线程学习笔记

    2. synchronized:用于修饰方法或代码块,实现互斥访问。synchronized锁住的是对象,而不是代码。 3. volatile:保证变量在多线程环境下的可见性,但不保证原子性。 4. Lock接口:提供更细粒度的锁控制,比如可重...

    传智博客JAVA基础笔记个人总结

    笔记会解释线程的创建、同步和通信,包括synchronized关键字、wait/notify机制、ThreadLocal等。 7. **反射与注解**:反射允许在运行时检查类、接口、字段和方法的信息,而注解为代码提供元数据。这两部分都是Java...

    java多线程笔记全手打

    Java提供了synchronized关键字,用于实现方法同步和代码块同步。 2. volatile关键字:确保多线程环境下变量的可见性和有序性,但不具备互斥性。 3. Lock接口与ReentrantLock类:提供比synchronized更细粒度的锁控制...

    java线程学习笔记

    synchronized关键字用于控制方法或者代码块对共享资源的互斥访问,确保在任何时候只有一个线程可以执行同步代码块。lock是一个接口,通过显式调用lock()和unlock()方法可以实现更灵活的锁定机制。 原子性与易变性...

    Java分布式应用学习笔记03JVM对线程的资源同步和交互机制

    当线程试图访问一个由synchronized修饰的方法或代码块时,它必须首先获得该对象的监视器锁。如果锁已被其他线程持有,则当前线程将进入等待状态,直到锁被释放。 2. **偏向锁(Biased Locking)**:这是一种轻量级...

    java多线程代码笔记

    `synchronized`关键字可以用于方法或代码块,确保同一时间只有一个线程访问特定资源,避免数据不一致问题。 ```java public synchronized void someMethod() { // 代码块 } ``` `wait()`, `notify()`, 和 `...

    李兴华老师Java基础笔记

    8. **多线程**:理解线程的概念,学习Thread类和Runnable接口的使用,以及同步机制(synchronized关键字和Lock接口)。 9. **反射机制**:探讨如何在运行时获取类的信息,动态调用方法和访问字段,以及Class类和...

    java基础的详细案例笔记

    10. **多线程**:Java提供了丰富的API支持并发编程,如Thread、Runnable接口,以及synchronized关键字,笔记可能涉及到线程的创建、同步和通信。 11. **枚举与注解**:枚举是Java中的特殊数据类型,而注解则是一种...

    Java任小龙版基础笔记.zip

    7. **多线程**:Java内置对多线程的支持,学习如何创建和管理线程,理解线程同步和互斥的概念,如synchronized关键字和wait/notify机制。 8. **反射与注解**:反射机制允许程序在运行时动态地获取类的信息并调用其...

    day06 【线程、同步】-笔记.pdf

    - **同步方法**:同步方法是一种特殊的同步代码块,其同步代码块范围为整个方法。方法的调用对象被自动当作锁对象。 ### 线程状态 Java线程在生命周期内会经历六种状态: 1. **NEW**:线程已创建,但尚未启动。即...

    Java基础笔记_Java基础笔记_Java笔记_worthnwg_

    9. **多线程**:线程的创建(通过Thread类或实现Runnable接口),线程状态,同步机制(synchronized关键字、wait/notify、Lock接口)。 10. **网络编程**:Socket编程基础,理解客户端和服务器端的交互过程。 11. ...

    Java语言基础笔记

    Thread类和Runnable接口是实现并发编程的基础,理解线程同步机制(如synchronized关键字、wait()、notify()方法)可以帮助你编写出更加健壮的并发代码。 总结来说,Java语言基础涵盖了变量、数据类型、控制流、类和...

    day17 【线程、同步】-笔记1

    Java提供了两种线程同步机制,即同步代码块和同步方法。同步代码块可以使用synchronized关键字来实现,而同步方法可以使用synchronized关键字来修饰方法。 四、Thread类 Thread类是Java中用于创建和管理线程的基本...

    java基础核心学习笔记

    - 线程同步:synchronized关键字、wait()、notify()和notifyAll()方法用于控制并发访问共享资源。 9. **网络编程** - Socket编程:通过Socket类进行客户端和服务器之间的通信。 - URL和URLConnection:访问网络...

    黑马java教程知识点笔记整理

    线程同步是通过synchronized关键字、wait()、notify()和notifyAll()方法来实现的。 【输入输出与文件操作】 Java的I/O流体系提供了处理输入输出的机制,包括字符流和字节流。File类用于文件的创建、删除、重命名等...

    java基础知识笔记

    同步机制(synchronized关键字、wait()、notify()等)用于解决线程间的通信和协作问题,防止数据竞争。 10. **输入/输出流**:Java的I/O流系统提供了处理数据输入和输出的能力,支持从磁盘、网络、内存等不同来源...

Global site tag (gtag.js) - Google Analytics