zoukankan      html  css  js  c++  java
  • java多线程(2) 线程同步

    我们对线程访问同一份资源的多个线程之间,来进行协调的这个东西,就是线程同步。
     
    例子1:模拟了多个线程操作同一份资源,可能带来的问题:              
    package com.cy.thread;
    
    public class TestSync implements Runnable{
        Timer timer = new Timer();
        public static void main(String[] args) {
            TestSync test = new TestSync();
            Thread t1 = new Thread(test);
            Thread t2 = new Thread(test);
            t1.setName("t1");
            t2.setName("t2");
            t1.start();
            t2.start();
        }
        
        @Override
        public void run() {
            timer.add(Thread.currentThread().getName());
        }
    
    }
    
    class Timer{
        private static int num = 0;
        public void add(String name){
            num++;
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name+",你是第"+num+"个使用timer的线程");
        }
    }
    View Code
    两个线程访问的都是time对象;访问的都是time对象中的add方法;
    但是console打印出来是:
     为什么会出现上述问题呢?
    解释:
    第一个线程访问time对象的add方法,将num++,num变为1了;然后睡眠了;
    这时候第二个线程开始执行,访问的仍然是同一份对象timer,肯定也是同一个num,num原来是1,执行num++,num变为2了;然后开始睡眠;
    第一个线程醒过来了,num这时候是2,打印:t1,你是第2个使用timer的线程
    第二个线程醒过来,打印:t2,你是第2个使用timer的线程
    问题出在,这个线程在执行add这个方法的过程之中,被另外一个线程打断了;
    num++;和
    System.out.println(name+",你是第"+num+"个使用timer的线程");
    这句话本来应该作为一个原子性的输出;你不能在中间给我打断了;
    例子中写Thread.sleep(1)给另外一个线程执行的机会,方便看到效果;
    但是即便是不写sleep(1),你很有可能看见的是正确的结果,但是当这个程序执行起来的过程之中,它还是有可能出问题;
    中间还是有可能被打断;
    怎么解决呢?
    在执行
    num++;
    try {
        Thread.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(name+",你是第"+num+"个使用timer的线程");
    这句话的过程之中,请你把我当前的对象锁住就行了;

    于是代码修改为:

    class Timer{
        private static int num = 0;
        public void add(String name){
            synchronized (this) {
                num++;
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name+",你是第"+num+"个使用timer的线程");
            }
        }
    }
    synchronized (this):
    锁定当前对象;在执行synchronized 后面大括号之间语句的过程之中,一个线程执行的过程之中,不会被另外一个线程打断;
    一个线程已经进入到synchronized (this){}这个锁定的区域里面了,你放心,不可能有另外一个线程也在这里边;
    既然锁定当前对象time了,它里面的成员变量num跟着也就锁定了;
    这就是锁的机制;(互斥锁)

    上面的还有一种简便的写法是这样的:

    package com.cy.thread;
    
    public class TestSync implements Runnable{
        Timer timer = new Timer();
        public static void main(String[] args) {
            TestSync test = new TestSync();
            Thread t1 = new Thread(test);
            Thread t2 = new Thread(test);
            t1.setName("t1");
            t2.setName("t2");
            t1.start();
            t2.start();
        }
        
        @Override
        public void run() {
            timer.add(Thread.currentThread().getName());
        }
    
    }
    
    class Timer{
        private static int num = 0;
        public synchronized void add(String name){
            num++;
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name+",你是第"+num+"个使用timer的线程");
        }
    }
    View Code
    public synchronized void add(String name)相当于在执行这个方法的过程之中,锁定当前对象,锁定this;
    是在执行这个方法的过程之中,当前对象被锁定;
    分析现在的执行过程:
    t1开始执行,调用add方法,num++,num变为1了,然后sleep,它睡着了没关系,睡着的时候还抱着这把锁呢。
    别人进不来,你必须得等它执行完了,你才可以继续执行;
    必须等它醒了,把
    System.out.println(name+",你是第"+num+"个使用timer的线程");
    这句话执行完了,另外一个线程才有执行的机会。
    
    synchronized这个关键字会锁定某一段代码,它的内部的含义是当执行这段代码的过程之中,锁定当前对象;
    另外一个人如果也想访问我这对象的话,他只能等着,等我这段代码执行完了,我的锁自然而然也就打开了。
    你才能进的来。你才能访问我这对象;
     
    二、当我们讲了锁之后,多线程还会带来其他的问题,一个典型的问题就是死锁。
     死锁的原理:
    死锁的原理:
    当某一个线程执行的过程之中,需要锁定某个对象A,它已经把这个对象A锁住了;它还需要锁定另外的一个对象B,才能把整个操作执行完。
    接下来另外一个线程,他也需要锁定两个对象,他首先锁定的是B;
    第一个线程,它锁定了A,然后再拥有B对象的锁,它就能完成了;
    第二个线程,他锁定了B,如果再能拥有A对象的锁,他就能完成了;
    第一个线程锁定了A,但是执行不下去了,它等待的东西B被其他线程锁住了;
    第二线程也执行不下去了,他等的东西A被另外的线程锁住了;
    就是死锁了。
    用程序来模拟:
    package com.cy.thread;
    
    public class TestDeadLock implements Runnable{
        public int flag = 1;
        static Object o1 = new Object(), o2 = new Object();
        
        @Override
        public void run() {
            System.out.println("flag= " + flag);
            if(flag == 1){
                synchronized (o1) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    
                    synchronized (o2) {
                        System.out.println("1");
                    }
                }
            }
            
            if(flag == 0){
                synchronized (o2) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    
                    synchronized (o1) {
                        System.out.println("0");
                    }
                }
            }
        }
        
        public static void main(String[] args) {
            TestDeadLock td1 = new TestDeadLock();
            TestDeadLock td2 = new TestDeadLock();
            td1.flag = 1;
            td2.flag = 0;
            Thread t1 = new Thread(td1);
            Thread t2 = new Thread(td2);
            t1.start();
            t2.start();
        }
    
    }
    View Code
    程序一运行,打印
    flag= 0
    flag= 1
    然后就死在那了...
    解决线程死锁的问题:
    你最好只锁住一个,不要锁住两,也就是把锁的粒度加粗一些;
    锁定当前整个对象不就完了吗,干嘛锁定这个对象下面两个小对象啊。
    把锁的粒度加粗一些,是办法之一,还有很多其他的办法...

    三、生产者消费者问题                                  

    package com.cy.thread;
    
    public class ProducerConsumer {
        public static void main(String[] args) {
            SyncStack ss = new SyncStack();
            Producer p = new Producer(ss);
            Consumer c = new Consumer(ss);
            new Thread(p).start();
            new Thread(c).start();
        }
    }
    
    /**
     * 馒头类
     * id: 每一个馒头的id
     */
    class WoTou{
        int id;
        
        WoTou(int id){
            this.id = id;
        }
    
        @Override
        public String toString() {
            return "WoTou : " + id;
        }
    }
    
    /**
     * 装馒头的篮子  用栈来模拟,先装进去的,最后拿出来
     * 开始的时候装了0个馒头
     * 一共能装arrWT.length =   6个馒头
     */
    class SyncStack{
        int index = 0;
        WoTou[] arrWT = new WoTou[6];
        
        /**
         * 装馒头
         */
        public synchronized void push(WoTou wt){
            while(index == arrWT.length){
                try {
                    /**
                     * 这里说的不是让当前对象wait;wait是指:
                     * 锁定在我当前对象上的这个线程,停止住。注意:
                     * wait方法只有你锁定了,锁定了我这个线程之后,才能wait,才有资格wait。 你要锁不住我这个对象就别谈wait这事。
                     * 如果这个方法不是synchronized的,调用wait立马出错。
                     * 而且一旦wait了,就死过去了,这个对象的锁就不再归我所有,只有在我醒过来的时候我才再找这把锁。
                     * (这是wait和sleep的巨大的区别)
                     * 
                     * 一个线程访问我这个push方法的时候,它已经拿到我这个对象的锁了,
                     * 拿到对象锁的这个线程在执行的过程之中,它遇到一个事件(已经满了,不能再往里装馒头了)必须阻塞住,必须停止,
                     * 必须等清洁工把馒头清走了才能继续办事。所以目前只能等着。不能再生产了。
                     */
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            /**
             * wait方法和notify方法一般是一一对应的
             * notify:叫醒一个现正正在在wait在我这个对象上的线程。
             * 谁现在正在我的对象上等待,我就叫醒一个线程,让它继续执行。
             * notifyAll:叫醒等待在这个对象上的多个线程
             * 例子中只有两个线程,用notify是可以的。
             */
            this.notify();
            arrWT[index] = wt;
            index ++;
        }
        
        /**
         * 吃馒头
         */
        public synchronized WoTou pop(){
            while(index==0){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            this.notify();
            index --;
            return arrWT[index];
        }
    }
    
    /**
     * 生产者  一次生产20个馒头
     */
    class Producer implements Runnable{
        SyncStack ss = null;
        Producer(SyncStack ss){
            this.ss = ss;
        }
        
        @Override
        public void run() {
            for(int i=0; i<20; i++){
                WoTou wt = new WoTou(i);
                ss.push(wt);
                System.out.println("生产了: " + wt);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
        }
    }
    
    /**
     * 消费者 一次吃20个馒头
     */
    class Consumer implements Runnable{
        SyncStack ss = null;
        Consumer(SyncStack ss){
            this.ss = ss;
        }
        
        @Override
        public void run() { 
            for(int i=0; i<20; i++){
                WoTou wt = ss.pop();
                System.out.println("消费了:" + wt);
                
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    
    console打印:
    生产了: WoTou : 0
    消费了:WoTou : 0
    生产了: WoTou : 1
    消费了:WoTou : 1
    生产了: WoTou : 2
    消费了:WoTou : 2
    生产了: WoTou : 3
    消费了:WoTou : 3
    生产了: WoTou : 4
    消费了:WoTou : 4
    生产了: WoTou : 5
    消费了:WoTou : 5
    生产了: WoTou : 6
    消费了:WoTou : 6
    消费了:WoTou : 7
    生产了: WoTou : 7
    生产了: WoTou : 8
    消费了:WoTou : 8
    生产了: WoTou : 9
    消费了:WoTou : 9
    生产了: WoTou : 10
    消费了:WoTou : 10
    生产了: WoTou : 11
    消费了:WoTou : 11
    生产了: WoTou : 12
    消费了:WoTou : 12
    消费了:WoTou : 13
    生产了: WoTou : 13
    生产了: WoTou : 14
    消费了:WoTou : 14
    生产了: WoTou : 15
    消费了:WoTou : 15
    生产了: WoTou : 16
    消费了:WoTou : 16
    生产了: WoTou : 17
    消费了:WoTou : 17
    生产了: WoTou : 18
    消费了:WoTou : 18
    消费了:WoTou : 19
    生产了: WoTou : 19
     
     
     
     
     
     
     
     --------------------------
  • 相关阅读:
    go timer
    go语言函数作为参数传递
    gomail发送附件
    统计和图片来说话
    一个发邮件的demo 用golang
    Shelled-out Commands In Golang
    阅读笔记
    语言和人收藏
    在线学习网址
    通用首席娱乐官:了解自己是成功的基础
  • 原文地址:https://www.cnblogs.com/tenWood/p/7113646.html
Copyright © 2011-2022 走看看