资讯专栏INFORMATION COLUMN

Java多线程:Java多线程同步与对象控制权

joyqi / 1009人阅读

摘要:辅助线程执行主线程正在执行函数函数如果对象调用了方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。当同步方法执行完毕或者执行时,其他某个线程将获得对象的访问权。

join()函数
Join的含义是:将某一线程加入成为另一个线程的流程之一,换言之就是等待另一个线程执行完毕。

public class JoinTest {
        public static void main(String[] args) throws InterruptedException {
              Thread otherThread = new Thread( new Runnable() {
                      public void run() {
                            try {
                                   for ( int i = 1; i <= 5; i++) {
                                         Thread. sleep(1000);
                                         System. out.println(i + ":辅助线程执行.." );
                                  }
                           } catch (InterruptedException e) {
                                  e.printStackTrace();
                           }
                     }
              });
              otherThread.start();

               try {
                     otherThread.join();
              } catch (InterruptedException e) {
                     e.printStackTrace();
              }
               for ( int i = 1; i <= 5; i++) {
                     Thread. sleep(500);
                     System. out.println(i + ": 主线程正在执行..." );
              }
       }
}

wait()函数notify()函数

如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。

如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。

wait方法阻塞本线程,等待其他线程调用notify方法通知自己可以继续执行,也就是说这一对方法应该结合在一起使用,只有当A线程在wait之后出让资源是其他线程有机会前进,另外的B线程notifyA才能使A线程恢复执行。在Java多线程任务里经常会出现多个线程争夺同一个资源,如果任由其争夺可能会造成问题,所以有序的争夺离不开阻塞和唤醒线程,可以先对线程已经争得的资源加锁,这时其他资源将无法争夺这个加锁的资源,在试用完资源后对资源进行解锁,使得其他线程能够重新获得这个资源的争夺权。(其实这个过程类似于进程的信号量加锁解锁)。阐明这个问题的最好例子莫过于生产者消费者的模拟:

在这个过程中,生产产品、消费产品时所需要的容器是争夺的资源,对这个这多资源在访问时需要加解锁:

synchronized(container):对容器加锁以阻塞其他线程同时访问,亦即使得其他线程处于等待状态;

container.wait():在容器满时阻塞本线程把容器解锁将容器的控制权交出去,本线程处于等待状态;

container.notify():在容器空时通知正在等待容器控制权的线程恢复运行,亦即解锁容器;

public class ThreadTest {
    private List container = new ArrayList();

    public static void main(String[] args) {
        PCTest m = new PCTest();
        new Thread(new Consume(m.getContainer()), "消费者1").start();
        new Thread(new Product(m.getContainer()), "生产者1").start();
        new Thread(new Consume(m.getContainer()), "消费者2").start();
        new Thread(new Product(m.getContainer()), "生产者2").start();
    }

    public List getContainer() {
        return container;
    }

    public void setContainer(List container) {
        this.container = container;
    }
}

class Product implements Runnable {
    private List container = null;

    public Product(List lst) {
        this.container = lst;
    }

    public void run() {
        while (true) {
            synchronized (container) {
                if (container.size() >= 5) {
                    try {
                        container.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                container.add(new Object());
                System.out
                        .println(Thread.currentThread().getName() + "生产了一个产品");
                System.out.println("现在容器中一共有" + container.size() + "个产品");
                container.notify();

            }
        }
    }
}

class Consume implements Runnable {
    private List container = null;

    public Consume(List lst) {
        this.container = lst;
    }

    public void run() {

        while (true) {
            synchronized (container) {
                if (container.size() == 0) {
                    try {
                        container.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                container.remove(0);
                System.out
                        .println(Thread.currentThread().getName() + "消费了一个产品");
                System.out.println("现在容器中一共有" + container.size() + "个产品");
                container.notify();

            }
        }
    }
}

线程死锁的问题
线程和线程如果在运行的过程中保有同样的资源,如果这些资源的占用在某一时刻无法良好分配,那么就有可能出现死锁:

class MyRunnable implements Runnable{
    Object a;
    Object b;
    public MyRunnable(Object a, Object b) {
        this.a = a;
        this.b = b;
    }
    @Override
    public void run() {
        while(true){
            synchronized (a) {
                synchronized (b) {
                    System.out.println(Thread.currentThread().getName()+" is running");
                }
            }
        }
    }
}
class ThreadTest{
    public static void main(String[] args) {
        Object a = new Object();
        Object b = new Object();
        MyRunnable myRunnable1 = new MyRunnable(a, b);
        MyRunnable myRunnable2 = new MyRunnable(b, a);
        Thread threadA = new Thread(myRunnable1,"t1");
        Thread threadB = new Thread(myRunnable2,"t2");
        threadA.start();
        threadB.start();
    }
}

在这个例子中两个线程在运行的过程中必须同时保有两个对象,那么当对象A被一个线程锁定而被另一个线程需要,同时对象B被一个线程锁定而被另一个线程需要的时候就会出现死锁。比如t1拿到A和B后锁定它们运行,t2因为没有A和B处于等待状态,t1运行后解锁先解锁B还没有解锁A,t2拿到这个B后锁定B继而需要A,t1解锁A后t2没有拿到A,t1的下一次循环拿到了这个A并锁定A,这个时候t2需要的A被t1锁定,t1需要的B被t2锁定,最终A和B产生死锁。

附上Java线程的同步原理

线程同步的基本原理

java会为每个object对象分配一个monitor,当某个对象的同步方法(synchronized methods )或同步块被多个线程调用时,该对象的monitor将负责处理这些访问的并发独占要求。
当一个线程调用一个对象的同步方法时,JVM会检查该对象的monitor。如果monitor没有被占用,那么这个线程就得到了monitor的占有权,可以继续执行该对象的同步方法;如果monitor被其他线程所占用,那么该线程将被挂起,直到monitor被释放。
当线程退出同步方法调用时,该线程会释放monitor,这将允许其他等待的线程获得monitor以使对同步方法的调用执行下去。
注意:java对象的monitor机制和传统的临界检查代码区技术不一样。java的一个类一个同步方法并不意味着同时只有一个线程独占执行(不同对象的同步方法可以同时执行),但临界检查代码区技术确会保证同步方法在一个时刻只被一个线程独占执行。
java的monitor机制的准确含义是:任何时刻,对一个指定object对象的某同步方法只能由一个线程来调用。
java对象的monitor是跟随object实例来使用的,而不是跟随程序代码。两个线程可以同时执行相同的同步方法,比如:一个类的同步方法是xMethod(),有a,b两个对象实例,一个线程执行a.xMethod(),另一个线程执行b.xMethod(). 互不冲突。
wait()、notify(),notifyAll()的使用
obj.wait()方法使本线程挂起,并释放obj对象的monitor,只有其他线程调用obj对象的notify()或notifyAll()时,才可以被唤醒。
obj.notifyAll()方法唤醒所有阻塞在obj对象上的沉睡线程,然后被唤醒的众多线程竞争obj对象的monitor占有权,最终得到的那个线程会继续执行下去,但其他线程继续等待。
obj.notify()方法是随机唤醒一个沉睡线程,过程更obj.notifyAll()方法类似。
wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,
如:

synchronized(x){
    x.notify()
    //或者wait()
}

以上内容说明了为什么调用wait(),notify(),notifyAll()的线程必须要拥有obj实例对象的monitor占有权。
每个对象实例都有一个等待线程队列。这些线程都是等待对该对象的同步方法的调用许可。对一个线程来说,有两种方法可以进入这个等待线程队列。一个是当其他线程执行同步方法时,自身同时也要执行该同步方法;另一个是调用obj.wait()方法。
当同步方法执行完毕或者执行wait()时,其他某个线程将获得对象的访问权。当一个线程被放入等待队列时,必须要确保可以通过notify()的调用来解冻该线程,以使其能够继续执行下去。
wait()与sleep()的区别
sleep()方法是Thread类的静态方法,不涉及到线程间同步概念,仅仅为了让一个线程自身获得一段沉睡时间。sleep可以在任何地方使用。(所以sleep只跟当前线程自己有关)
wait()方法是object类的方法,解决的问题是线程间的同步,该过程包含了同步锁的获取和释放,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify()方法才会重新激活调用者。(所以wait适用于多个线程同步协调时才使用的)

注意:线程调用notify()之后,只有该线程完全从 synchronized代码里面执行完毕后,monitor才会被释放,被唤醒线程才可以真正得到执行权。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64614.html

相关文章

  • java学习(十) —— java中的线程概述

    摘要:进程一般由程序数据集进程控制块三部分组成。线程概述线程的出现是为了降低上下文切换的消耗,提高系统的并发性。线程突破了一个进程只能干一件事的缺陷,使到进程内并发成为可能。进程与线程的关系进程是计算机中的程序关于某数据集合上的一次运行活动。 进程概述 进程:正在运行的程序,是系统进行资源分配和调用的独立单位。 进程就是一个程序在一个数据集上的一次动态执行过程。 进程一般由程序、数据集、进...

    Sanchi 评论0 收藏0
  • 40道阿里巴巴JAVA研发岗线程面试题详解,你能答出

    摘要:但是单核我们还是要应用多线程,就是为了防止阻塞。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。 1、多线程有什么用?一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡。所谓知其然知其所以然,会用只是知其然,为什么用才是知其所以然,只有达到知其然知其所以然的程度才可以说是把一个知识点...

    lpjustdoit 评论0 收藏0
  • 想进大厂?50个线程面试题,你会少?(一)

    摘要:下面是线程相关的热门面试题,你可以用它来好好准备面试。线程安全问题都是由全局变量及静态变量引起的。持有自旋锁的线程在之前应该释放自旋锁以便其它线程可以获得自旋锁。 最近看到网上流传着,各种面试经验及面试题,往往都是一大堆技术题目贴上去,而没有答案。 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员...

    wow_worktile 评论0 收藏0
  • JAVA 线程和并发基础

    摘要:线程可以被称为轻量级进程。一个守护线程是在后台执行并且不会阻止终止的线程。其他的线程状态还有,和。上下文切换是多任务操作系统和多线程环境的基本特征。在的线程中并没有可供任何对象使用的锁和同步器。 原文:Java Multi-Threading and Concurrency Interview Questions with Answers 翻译:并发编程网 - 郑旭东 校对:方腾飞 多...

    vboy1010 评论0 收藏0
  • JAVA 线程和并发基础面试问答

    摘要:多线程和并发问题是技术面试中面试官比较喜欢问的问题之一。线程可以被称为轻量级进程。一个守护线程是在后台执行并且不会阻止终止的线程。其他的线程状态还有,和。上下文切换是多任务操作系统和多线程环境的基本特征。 多线程和并发问题是 Java 技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题。(...

    dreamans 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<