Java 读书笔记21.3 并发(重点)

并发

不自己写真是不知道,其实是有好多问题的,而java编程思想的代码带过太严重了;

下面是重点了
1.线程中的对象; 我们会发现,每次有一个新的线程对象初始化的线程是一个独立的;

public class Mytest3 implements  Runnable {
    int a=0;
    @Override
    public void run() {
        System.out.println(this);
        System.out.println(a);
        a+=10;
    }
    public  static  void main(String []arg)
    {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i=0;i<10;i++)
            executorService.execute(new Mytest3());
    }
}
output:
Thinking_in_java.Thread.Synchronized.Mytest3@78b8f730
Thinking_in_java.Thread.Synchronized.Mytest3@22ef0305
0
Thinking_in_java.Thread.Synchronized.Mytest3@f2dabac
0
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
0
0
Thinking_in_java.Thread.Synchronized.Mytest3@39bdd410
0
Thinking_in_java.Thread.Synchronized.Mytest3@394b0894
0
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
0
Thinking_in_java.Thread.Synchronized.Mytest3@6840b323
0
Thinking_in_java.Thread.Synchronized.Mytest3@5f89ad23
0
Thinking_in_java.Thread.Synchronized.Mytest3@520fddc3
0

很显然这是10个Mytest3对象创建了各自的线程,a是每个对象自己的;除非你定义为static;
于是我们就可以改成这么写,一个对象初始化10个线程;

public class  Mytest3 implements  Runnable {
   static int a=0;      //一个对象的时候,static是可以不写的啦
    @Override
    public void run() {
        System.out.println(this);
        System.out.println(a);
        a+=10;
    }
    public  static  void main(String []arg)
    {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Mytest3 mytest3 = new Mytest3();
        for (int i=0;i<10;i++)
            executorService.execute(mytest3);

    }
}
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
0
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
10
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
20
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
30
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
40
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
50
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
60
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
70
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
80
Thinking_in_java.Thread.Synchronized.Mytest3@778a8851
90

那么线程同步是什么情况呢?我们将run方法,改为这样

    public void run() {
        System.out.println(this);
        System.out.println(a);
        a+=10;
        Thread.yield();
        a+=10;
    }
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
0
0
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
40
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
60
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
70
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
100
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
120
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
140
130
Thinking_in_java.Thread.Synchronized.Mytest3@5e7f1b08
180

我们可以发现他出现了问题,发现了什么问题呢? 同时输出0,输出了奇数,后面输出比前面小;
我们可以归为两类
1. 线程抢占资源,a+=10线程A做了一下,然后被线程B抢过去了,也做了一下,所以总会存在奇数的情况;宏观上不该访问的时候访问了
2. 操作非原子性,简单地说,不管线程怎么抢,你应该是一直增大的吧;但是事实是会出现后输出的前面输出的小,因为操作不是原子性的,+=操作进行的中途,被线程抢跑了;微观上,我们的一个++操作,或者return操作,资源中途被抢跑了;

先说说第二个,原子性,对我们来说+1这种操作好像就是一下,一个操作,但是对于jvm不是的,(差不多应该是这样)它得把数字从储存位置拿出来,+1,再放回去;这中间线程是可以把储存位置的对象读取出来的,然后你就知道会发生了;
volatile就是保护这个的;但是其实作用不是很大,因为对于我们来说宏观的操作更多,保证他们的正确性才是正确的
将a 的定义加上volatile,就会发现不会存在大小问题了(大概,嘿嘿);

对于第一个问题,我们可以这样:

public class  Mytest3 implements  Runnable {
  volatile static int a=0;

    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 100; i++) {
                System.out.println(this);
                System.out.println(a);
                a += 10;
                Thread.yield();
                a += 10;
            }
        }
    }
    public  static  void main(String []arg)
    {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Mytest3 mytest3 = new Mytest3();
        for (int i=0;i<10;i++)
            executorService.execute(mytest3);

    }
}
ouptut:
反正顺序对了;

或者将其拿出去当一个函数

    public synchronized void  a()
    {
        for (int i = 0; i < 100; i++) {
            System.out.println(this);
            System.out.println(a);
            a += 10;
            Thread.yield();
            a += 10;
        }
    }
    @Override
    public void run() {
            a();
    }

一个神奇的问题
如果不是这个问题我才懒得写代码呢;

      public synchronized void a()
        {
            ++a;
            Thread.yield();
            ++a;
        }
    @Override
    public void run() {

            for (int i = 0; i < 5000; i++) {
                a();
                if (a % 2 != 0) {
                    System.out.println("wrong");
                }

        }

    }
    output:反正一大堆wrong

我找了一晚上错误,把前面所有特性都试验了;然后我发现,run方法,run方法里面的这段代码,它是可以随意访问的;所以只要把代码改为:

    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 5000; i++) {
                a();
                if (a % 2 != 0) {
                    System.out.println("wrong");
                }
            }
        }

    }

划重点
当任务要执行被synchronized关键字保护的代码片段的时候,它将检查锁是否可用,然后获取锁,执行代码,释放锁。

所有对象都自动含有单一的锁(也称为监视器)。当在对象上调用其任意synchronized方法的时候,此对象都被加锁,这时该对象上的其他synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。

注意,在使用并发时,将域设置为private是非常重要的。否则,synchronized关键字就不能防止其他任务直接访问域,这样就会产生冲突。

JVM负责跟踪对象被加锁的次数。如果一个对象被解锁(即锁被完全释放),其计数变为0。在任务第一次给对象加锁的时候,计数的任务才能允许继续获取多个锁。每当任务离开一个synchronized方法,计数递减,当计数为零的时候,锁被完全释放,此时别的任务就可以使用此资源。

Brian同步规则:如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被一个线程写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步。

为了让临界共享资源正确地工作,每个访问临界共享资源的方法必须被同步。

我们来看一下所谓一个对象共享一把锁

  public synchronized void a() throws InterruptedException {
        public synchronized void a() throws InterruptedException {
            Thread.sleep(1000);
        }
        public    void  b()
        {

        }
    @Override
    public void run() {
        int a = (int) (Math.random()*10);

        //synchronized (this) {   //此处加锁就没有意义了
            for (int i = 0; i < 5; i++) {
                if (a>5) {
                    System.out.println(Thread.currentThread().getName()+" 先做a");
                    try {
                        a();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"a完成");
                    b();
                }
                else
                {
                    System.out.println(Thread.currentThread().getName()+" 先做b");
                    b();
                    System.out.println("b完成");
                    try {
                        a();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
                //}
        }

    }
output:
pool-1-thread-7 先做a         //a表示它要先做,但是b实际上是可以随便访问的;
pool-1-thread-6 先做b
b完成
pool-1-thread-8 先做b
b完成
pool-1-thread-9a完成          //线程7此时其实才拿到资源; 因为a是加锁的
pool-1-thread-9 先做a
pool-1-thread-3 先做b
b完成
pool-1-thread-5a完成
pool-1-thread-5 先做a
pool-1-thread-4 先做b
b完成
pool-1-thread-1 先做b
b完成
pool-1-thread-4 先做b
b完成
pool-1-thread-5a完成
pool-1-thread-5 先做a
pool-1-thread-3 先做b
b完成
pool-1-thread-9a完成
pool-1-thread-9 先做a
pool-1-thread-8 先做b
b完成
pool-1-thread-6 先做b
b完成
pool-1-thread-7a完成
 public   synchronized void  b()     //b加锁
        {

        }
output
pool-1-thread-2 先做a        //2和8要先做a
pool-1-thread-7 先做b
pool-1-thread-6 先做b
pool-1-thread-5 先做b
pool-1-thread-4 先做b
pool-1-thread-8 先做a
pool-1-thread-9 先做b
pool-1-thread-3 先做b
pool-1-thread-1 先做b
b完成
pool-1-thread-10 先做b
b完成
b完成
b完成
b完成
b完成
b完成
pool-1-thread-8a完成      //在b完成后才可以a完成;
a完成
pool-1-thread-2a完成
a完成

临界区

synchronized (Object),套住的的代码,这样就可以忽略方法上的锁,转为代码上的锁,但其实锁住的不是代码,而是对象,就像是this;不同对象的锁互不干涉

线程本地储存

简单来说,有些时候你是需要线程的;一个对象实例化的多个线程;那么对象里有很多的属性(域),有些是要进行++,return操作的,那么访问必须同步,否则会发生错误,但是假如不是呢?所以有些变量是要共享,但是互不冲突;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值