java多线程基本概述(三)——同步

非线程安全其实是在多个线程对同一个对象实例的变量进行并发访问的时候发生,产生的后果就是脏读,也就是取到的数据是修改过的。而线程安全就是获得的实例变量的值是经过同步处理的,从而不会出现脏读现象。

1.1.1、实例变量非线程安全

如果我们把多个线程并发访问的实例变量转化成方法里面的局部变量,那么就不会产生线程不安全的情况了。因为每个线程拿到的变量都是该线程自己拥有,类似于ThreadLocal类的思想。下面这个例子将变量变为局部变量从而实现线程安全。

 1 package soarhu;
 2 import java.util.concurrent.TimeUnit;
 3 class ObjectMonitor{
 4     void add(String name){
 5         try {
 6             int num = 0;  //方法里面的局部变量,不会出现并发竞争的情况。
 7             if(name.equals("a")){
 8                 num = 100;
 9                 System.out.println("a set finish");
10                 TimeUnit.MILLISECONDS.sleep(2);
11             }else{
12                 num = 200;
13                 System.out.println("b set finish");
14             }
15             System.out.println("user: "+name+" num: "+num);
16         }catch (InterruptedException e){
17             e.printStackTrace();
18         }
19     }
20 }
21
22 class ThreadA extends Thread{
23     private ObjectMonitor monitor ;
24
25     ThreadA(ObjectMonitor monitor) {
26         super();
27         this.monitor = monitor;
28     }
29
30     @Override
31     public  void run() {
32            monitor.add("a");
33     }
34 }
35
36 class ThreadB extends Thread{
37     private ObjectMonitor monitor ;
38
39     ThreadB(ObjectMonitor monitor) {
40         super();
41         this.monitor = monitor;
42     }
43
44     @Override
45     public  void run() {
46         monitor.add("B");
47     }
48 }
49 public class Test {
50     public static void main(String[] args) throws InterruptedException {
51         ObjectMonitor objectMonitor = new ObjectMonitor();
52         ThreadA threadA = new ThreadA(objectMonitor);
53         ThreadB threadB = new ThreadB(objectMonitor);
54         threadA.start();
55         threadB.start();
56     }
57 }

输出结果:

a set finish
b set finish
user: B num: 200
user: a num: 100

如果将第6行的代码移到方法外面,则会出现线程安全的问题,改完后,运行结果:

a set finish
b set finish
user: B num: 200
user: a num: 200

因为此时add()方法访问的变量num为类成员变量,而且add方法没有进行同步,那么a,b两个线程就会同时进入add()方法对num变量进行修改。当a线程把num设置成100后。执行打印语句之前,这时候b线程进入了add()方法,设置num的值为200,那么最终就出现了脏读。解决方法是在add()方法上加入synchronized。使其成为同步方法。这样就会同步访问该方法,从而避免线程不安全的问题了。加入后程序的执行结果为:

a set finish
user: a num: 100
b set finish
user: B num: 200

1.1.2、多个对象多个锁

稍微修改一下上面方法,使其a,b两个线程拥有不同的锁对象,那么add()同步方法的同步意义对现在a,b两个线程没有意义了。因为锁对象不同。

package soarhu;
import java.util.concurrent.TimeUnit;
class ObjectMonitor{
    int num = 0;
    synchronized void add(String name){
        try {
            if(name.equals("a")){
                num = 100;
                System.out.println("a set finish");
                TimeUnit.MILLISECONDS.sleep(2);
            }else{
                num = 200;
                System.out.println("b set finish");
            }
            System.out.println("user: "+name+" num: "+num);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

class ThreadA extends Thread{
    private ObjectMonitor monitor ;

    ThreadA(ObjectMonitor monitor) {
        super();
        this.monitor = monitor;
    }

    @Override
    public  void run() {
           monitor.add("a");
    }
}

class ThreadB extends Thread{
    private ObjectMonitor monitor ;

    ThreadB(ObjectMonitor monitor) {
        super();
        this.monitor = monitor;
    }

    @Override
    public  void run() {
        monitor.add("B");
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        ObjectMonitor objectMonitor = new ObjectMonitor();
        ObjectMonitor objectMonitor2 = new ObjectMonitor();
        ThreadA threadA = new ThreadA(objectMonitor);
        ThreadB threadB = new ThreadB(objectMonitor2);//使用不同的同步对象
        threadA.start();
        threadB.start();
    }
}

输出结果:

a set finish
b set finish
user: B num: 200
user: a num: 100

结果正确,且执行方式不是同步方式,而是异步方式。那么如何使这个方式仍然按照同步的方式进行访问呢?很简单,用同步代码块

修改部分代码,如下:

class ObjectMonitor{
    int num = 0;
    void add(String name){
         synchronized (Integer.TYPE) { //这里使用同步代码块,这里的监视器jvm里只有一份,故可以起到监视器同步的作用
             try {
                 if (name.equals("a")) {
                     num = 100;
                     System.out.println("a set finish");
                     TimeUnit.MILLISECONDS.sleep(2);
                 } else {
                     num = 200;
                     System.out.println("b set finish");
                 }
                 System.out.println("user: " + name + " num: " + num);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
    }
}

1.1.3、synchronized方法与锁对象

有上面的代码可以知道,synchronized方法的锁监视器对象就是该方法所属的对象。下面看看对多个方法的调用。

package soarhu;
import java.util.concurrent.TimeUnit;
class ObjectMonitor{
   synchronized void a(String name){
         try {
                 System.out.println("a method start: "+Thread.currentThread().getName()+" begin time "+System.currentTimeMillis());
                 TimeUnit.MILLISECONDS.sleep(5);
             System.out.println("a end time: " + System.currentTimeMillis());
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
    }

     void b(String name){
        try {
            System.out.println("b method start: "+Thread.currentThread().getName()+" begin time "+System.currentTimeMillis());
            TimeUnit.MILLISECONDS.sleep(5);
            System.out.println("b end time: " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadA extends Thread{
    private ObjectMonitor monitor ;

    ThreadA(ObjectMonitor monitor) {
        super();
        this.monitor = monitor;
    }

    @Override
    public  void run() {
           monitor.a("a");
    }
}

class ThreadB extends Thread{
    private ObjectMonitor monitor ;

    ThreadB(ObjectMonitor monitor) {
        super();
        this.monitor = monitor;
    }

    @Override
    public  void run() {
        monitor.b("B");
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        ObjectMonitor objectMonitor = new ObjectMonitor();
        ThreadA threadA = new ThreadA(objectMonitor);
        ThreadB threadB = new ThreadB(objectMonitor);
        threadA.start();
        threadB.start();
    }
}

输出结果:

a method start: Thread-0 begin time 1492413285921
b method start: Thread-1 begin time 1492413285921
b end time: 1492413285927
a end time: 1492413285927

可以看到a,b两个线程同步访问。虽然a线程拥有objectMonitor的锁,但是b方法并没有加同步块,所以b线程任然可以访问该监视器对象的其他非同步方法。

在b()方法加上同步关键字后,  

synchronized void b(String name){
        try {
            System.out.println("b method start: "+Thread.currentThread().getName()+" begin time "+System.currentTimeMillis());
            TimeUnit.MILLISECONDS.sleep(5);
            System.out.println("b end time: " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

输出结果:

a method start: Thread-0 begin time 1492413504482
a end time: 1492413504489
b method start: Thread-1 begin time 1492413504489
b end time: 1492413504494

已经同步了,那么我们可以得到结论:

A线程现持有监视器对象的锁,B线程可以异步的调用该监视器对象中的非同步方法,如果B线程需要调用该监视器对象的同步方法则需要等待A线程释放锁。

1.1.4、脏读

时间: 2024-10-09 19:08:07

java多线程基本概述(三)——同步的相关文章

java多线程之线程的同步与锁定(转)

一.同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 例如:两个线程ThreadA.ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据. publicclass Foo { privateint x = 100; publicint getX() { return x;     } publicint fix(int y) {         x = x - y; return x;     } } publicclass MyRunnable i

Java多线程——线程之间的同步

Java多线程——线程之间的同步 摘要:本文主要学习多线程之间是如何同步的,以及如何使用synchronized关键字和volatile关键字. 部分内容来自以下博客: https://www.cnblogs.com/hapjin/p/5492880.html https://www.cnblogs.com/paddix/p/5367116.html https://www.cnblogs.com/paddix/p/5428507.html https://www.cnblogs.com/liu

Java多线程之线程的同步

Java多线程之线程的同步 实际开发中我们也经常提到说线程安全问题,那么什么是线程安全问题呢? 线程不安全就是说在多线程编程中出现了错误情况,由于系统的线程调度具有一定的随机性,当使用多个线程来访问同一个数据时,非常容易出现线程安全问题.具体原因如下:   1,多个线程同时访问一个数据资源(该资源称为临界资源),形成数据发生不一致和不完整.   2,数据的不一致往往是因为一个线程中的多个关联的操作(这几个操作合成原子操作)未全部完成. 关于线程安全问题,有一个经典的情景:银行取钱.代码如下: /

java多线程开启的三种方式

1.继承Thread类,新建一个当前类对象,并且运行其start()方法 1 package com.xiaostudy.thread; 2 3 /** 4 * @desc 第一种开启线程的方式 5 * @author xiaostudy 6 * 7 */ 8 public class Demo1_Thread extends Thread { 9 10 public void run() { 11 for (int i = 0; i < 10; i++) { 12 System.out.pri

java多线程二之线程同步的三种方法

java多线程的难点是在:处理多个线程同步与并发运行时线程间的通信问题.java在处理线程同步时,常用方法有: 1.synchronized关键字. 2.Lock显示加锁. 3.信号量Semaphore. 线程同步问题引入: 创建一个银行账户Account类,在创建并启动100个线程往同一个Account类实例里面添加一块钱.在没有使用上面三种方法的情况下: 代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

Java多线程——&lt;一&gt;概述、定义任务

一.概述 为什么使用线程?从c开始,任何一门高级语言的默认执行顺序是“按照编写的代码的顺序执行”,日常开发过程中写的业务逻辑,但凡不涉及并发的,都是让一个任务顺序执行以确保得到想要的结果.但是,当你的任务需要处理的业务比较多时,且这些业务前后之间没有依赖(比如, a执行的过程中b也可以执行,b没有必要必须等待a执行完毕再去执行),那么此时,我们可以将一个任务拆分成多个小任务. 例如,任务a负责接收键盘的输入,b负责将一些参数及计算提前做好(假设计算量比较大),c负责将a的输入和b的结果做和.此时

Java多线程编程核心技术(三)多线程通信

线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控与监督.在本章中需要着重掌握的技术点如下: 使用wait/notify实现线程间的通信 生产者/消费者模式的实现 方法join的使用 ThreadLocal类的使用 1.等待 / 通知机制 通过本节可以学习到,线程与线程之间不是独立的个体,它们彼此

第五周作业(Java多线程创建的三个方法)

我最近在学习Java中多线程,并且觉得多线程这块在以后的Java开发中显得极为重要,就谈一下Java实现多线程的三种方式. JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程.其中前两种方式线程执行完后都没有返回值,只有第三种是带返回值的,这种方式一般要求比较高,并且较前两种难一些. 1.继承Thread类实现多线程继承Thread类的本质上也是实现了Runnable接口的一个实

JAVA多线程实现的三种方式

JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程. 当中前两种方式线程运行完后都没有返回值,仅仅有最后一种是带返回值的. 1.继承Thread类实现多线程 继承Thread类的方法虽然被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,而且,启动线程的唯一方法就是通过Thread类的start()实例方法.start(

AJPFX关于JAVA多线程实现的三种方式

JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程.其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的. 1.继承Thread类实现多线程继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法.start()方法