> Java > java지도 시간 > 본문

Java 동시성--모든 스레드 메커니즘 예제에 대한 자세한 설명

Y2J
풀어 주다: 2017-04-25 09:20:46
원래의
1549명이 탐색했습니다.

JAVA 멀티스레딩은 단순한 지식 포인트가 아닌, 자잘한 내용들이 모여서 구성되어 있습니다. 이름을 지정할 수는 없지만 매우 중요한 메커니즘이 많이 있습니다. 여기서는 일반적으로 사용되는 모든 동시성 메커니즘을 살펴보겠습니다.

Sleep 및 Concession


스레드 작업에 객관적으로 영향을 미치는 간단한 방법은 sleep 메서드를 호출하는 것입니다. sleep 메서드는 실행을 일시 중지합니다. 이 기간이 지난 후에도 프로그램에서 계속 작업하세요. 이와는 달리, run 메소드가 한 사이클을 완료한 후에 항복 메소드를 사용합니다. yield 메소드는 이 스레드의 작업이 거의 완료되었으며 다른 스레드(동일한 우선순위를 가진)가 CPU를 사용할 수 있음을 CPU에 나타냅니다. ..

sleep

sleep 메소드는 스레드를 일정 시간 동안 휴면 상태로 만든 다음 깨어나서 코드 실행을 계속합니다. 이를 통해 스레드 스케줄러가 다른 스레드로 전환할 수 있으며, 이는 차례로 다른 작업을 구동합니다. 그러나 어떤 작업이 구동되는지는 기본 스레드 메커니즘과 운영 체제에 따라 다릅니다. 프로그램 보안을 위해 이 실행 순서를 신뢰할 수는 없습니다. 동기화 제어를 사용하거나 스레드를 전혀 사용하지 않고 협력 루틴만 사용합니다. 이러한 루틴은 지정된 순서에 따라 제어를 서로 전달합니다.

sleep 호출은 Run 메소드에서 포착되는 InterruptedException을 발생시킬 수 있습니다. 예외는 스레드를 통해 기본 함수로 다시 전파될 수 없기 때문입니다.

yield

run 메소드가 한 사이클을 완료한 후 Yield 메소드는 이 스레드의 작업이 거의 완료되었음을 CPU에 알리고 CPU를 사용하기 위해 동일한 우선순위를 가진 다른 ( ) 스레드가 사용할 수 있습니다. 그러나 CPU에 대한 수율 표현이 100% 채택되지 않을 수도 있다는 점에 유의해야 합니다. 실제로 수익률은 오용되는 경우가 많습니다.

스레드 우선순위


각 스레드에는 우선순위가 있습니다. 우리가 알아야 할 것은 특별한 처리 없이 모든 스레드의 우선순위가 동일하다는 것입니다.

기본적으로 우선순위를 1~10으로 나누어 우선순위가 높은 스레드부터 먼저 동작하게 됩니다. 하면 운영체제에서 프로세스 우선순위, 에이징 등의 용어를 떠올리지 않을 수 없다. 실제로 JVM(Java Virtual Machine)은 프로세스의 우선순위를 사용하여 스레드의 우선순위를 유추합니다. 하지만 이것의 가장 큰 문제는 운영체제마다 프로세스 우선순위를 다르게 처리하며, Java의 스레드 우선순위에도 플랫폼 가변성이 있다는 점입니다.

getPriority() 메소드를 통해 스레드의 우선순위를 얻을 수 있으며, setPriority()를 사용하여 언제든지 수정할 수도 있습니다.

Thread.currentThread().getPriority(); 
  //获取线程优先级 
  Thread.currentThread().setPriority(); 
  //修改线程优先级
로그인 후 복사

프로그램 정확성을 위해 스레드 우선순위에 의존해서는 안 되며, 스레드 우선순위를 가능한 한 적게 사용해야 합니다.

가디언/백그라운드(데몬) 스레드


데몬 스레드를 데몬 스레드나 백그라운드 스레드로 변환할 수 있습니다.

데몬 스레드의 역할은 다른 스레드에 서비스를 제공하는 것입니다. 다른 스레드가 모두 종료되고 데몬 스레드만 남으면 프로그램이 종료됩니다. 데몬 스레드를 별도로 실행할 필요가 없습니다. 예를 들어 다른 스레드의 타이머의 경우 이를 데몬 스레드로 설정할 수 있습니다. 그리고 데몬 스레드에서 파생된 하위 스레드도 데몬 스레드입니다.

t.setDaemon(ture);  
  //将线程转换为守护线程 
  t.isDaemon();  
  //判断线程是否为守护线程
로그인 후 복사

데몬 스레드의 리소스를 열거나 사용하지 마세요! 데몬이 아닌 스레드가 모두 종료되면 프로그램이 종료됩니다. 그런 다음 데몬 스레드의 finally 절을 실행하지 않고 백그라운드에서 run 메서드를 종료한다고 생각해야 합니다. 믿기지 않으실 수도 있지만 이는 사실입니다. System.exit(0);은 finally 절이 실행되지 않는 유일한 상황입니다.

Join Threads


스레드는 다른 스레드에서 Join 메소드를 호출할 수 있습니다. 그 결과는 호출된 스레드가 끝날 때까지 일정 기간 기다린 다음 이 스레드로 돌아와 아래쪽으로 계속 진행하는 것입니다.

먼저 이 스레드에서는 다른 스레드에서 참조를 가져와야 하며 이 참조를 사용하여 Join() 메서드를 호출해야 합니다. 호출 후 이 스레드는 대상 스레드가 끝날 때까지 일시 중단됩니다. Join을 호출할 때 시간 초과 매개변수를 가져올 수도 있습니다. 그러면 이 기간이 만료될 때 대상 스레드가 종료되지 않은 경우 Join 메서드도 반환될 수 있습니다.

JAVA SE5의 java.util.concurrent 클래스 라이브러리에는 CyclicBarrier와 같은 도구가 새로 추가되었으며 이는 원래 스레드 클래스 라이브러리에 조인하는 것보다 더 효과적일 수 있습니다.

스레드 그룹


스레드 그룹은 JDK 1.2 버전에서도 여전히 큰 인기를 끌었지만 후속 버전이 도입되면서 스레드 그룹은 인기가 없어졌습니다. 사용하기 매우 쉽기 때문에 스레드 그룹에 대해서는 무시할 수 있습니다.

스레드 그룹은 그냥 무시할 수 있는 실패한 시도라고 생각하는 것이 가장 좋습니다. ——조슈아 블로흐

捕获异常


因为多线程的机制,我们不能在main函数中捕获其他线程的异常。这就说明,如果我们不能在run方法中自己捕获异常,那么异常会被抛出到控制台上。除非我们采用特殊的机制来捕获。

package AllThread;/**
 * 
 * @author QuinnNorris
 * 
 *         捕获异常
 */public class ExceptionThread {

    /**
     * @param args
     */
    public static void main(String[] args) {        // TODO Auto-generated method stub

        Thread th = new Thread(new Runnable() {            @Override
            public void run() {                // TODO Auto-generated method stub
                throw new RuntimeException();
            }
        });
        th.start();
    }

}
로그인 후 복사

这是一段简单的代码,它会抛出一个运行时异常:

Exception in thread “Thread-0” java.lang.RuntimeException 
          at AllThread.ExceptionThread$1.run(ExceptionThread.java:15) 
          at java.lang.Thread.run(Thread.java:745)
로그인 후 복사

我们可以看出, 由于没有去设计捕获异常,它被直接输出到控制台上。对于这种情况,为main函数加上try-catch语句是没有用的。

为了解决这种不能捕获未检查异常的情况,在JAVA SE5中引入了使用Executor的一种解决方法。

package AllThread;import java.lang.Thread.UncaughtExceptionHandler;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadFactory;/**
 * 
 * @author QuinnNorris
 * 
 *         使用UncaughtExceptionHandler捕获异常
 */public class UEHThread {

    /**
     * @param args
     */
    public static void main(String[] args) {        // TODO Auto-generated method stub
        ExecutorService es = Executors.newCachedThreadPool(new ThreadFactory() {            @Override
            public Thread newThread(Runnable r) {                // TODO Auto-generated method stub
                Thread th = new Thread(r);
                th.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {                    @Override
                    public void uncaughtException(Thread t, Throwable e) {                        // TODO Auto-generated method stub
                        System.out.println("catch it " + e);
                    }
                });                return th;
            }
        });
        es.execute(new Runnable() {            @Override
            public void run() {                // TODO Auto-generated method stub
                throw new RuntimeException();
            }
        });
    }
}
로그인 후 복사

因为我比较懒全部用内部类来表示,所以这段程序可能略有些难懂。首先我们创建了一个线程池,然后为这个创建线程池的静态方法赋予一个参数。这个参数是一个ThreadFactory类,这个类是用来描述在线程池中的线程具有的共性的。ThreadFactory有一个方法需要我们覆盖就是newThread方法,这个方法的参数是我们要处理的Runnable任务,也就是我们要加入到线程池中的Runnable任务。我们在这个方法中用一个th对象包含r对象,然后设置th对象的UncaughtExceptionHandler属性。这个setUncaughtExceptionHandler方法的参数是一个UncaughtExceptionHandler对象(这里我们第二次用内部类),UncaughtExceptionHandler类的唯一一个方法是uncaughtException。这个方法用来表示对线程未检查异常的处理方式,我们让他在控制台输出一句话。到这里我们对线程池的部署就完成了。

然后我们在这个线程池中添加一个Runnable任务,这个任务会抛出一个未检查异常。现在我们运行程序,控制台输出:

catch it java.lang.RuntimeException

到此,对于线程run方法中的未检查异常的处理就结束了。需要注意的是,我们向线程池中添加线程的方法要调用execute方法而不要使用submit方法,submit方法会把异常吞掉。从而控制台将会什么都不输出。

竞争条件


在操作系统中有一张让人印象深刻的图片。上面画的是一块块并排的进程,在这些进程里面分了几个线程,所有这些线程齐刷刷统一的指向进程的资源。资源会在线程间共享而不是每个线程都有一份独立的资源。在这种共享的情况下,很有可能有多个线程同时在访问一个资源,这种现象我们叫做竞争条件。

在一个银行系统中,每个线程分别管理一个账户,这些线程可能会进行转账的操作。
在一个线程进行操作的时候,他首先,会把账户余额存放到寄存器中,第二步,它将寄存器中的数字减少要转出的钱数,第三步,它将结果写回余额中。
问题在于,这个线程在执行完1、2步时,另外一个线程被唤醒并且修改了第一个线程的账户余额值,但是这个时候第一个线程并不知情。第一个线程等待第二个线程执行完毕后,继续他的第三步:将结果写回余额中。这个时候,它把第二个线程的操作刷掉了,所以钱数发生错误。

同步规则


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

ReentrantLock


上面的例子告诉我们:如果我们的操作不是原子操作,被打断是肯定会发生的。我们没办法把代码变成原子操作,但是能将其上锁来保证安全性。在并发程序中,在访问资源或数据之前,要先给代码套一个锁。在锁被使用的期间,代码中涉及的资源不能被其他的线程访问,直到程序结束时再将锁打开。

ReentrantLock构造器

ReentrantLock类提供了两个构造器:一个是默认构造器,一个是带有公平策略的构造器。

首先,带有公平策略的锁会比正常的锁要慢很多。其次,在某些情况下公平策略并不能保证真正公平的。
如果我们没有特殊的理由真的需要公平策略的时候,尽量不要去使用这种锁。

锁的获取与释放

ReentrantLock myLock = new ReentrantLock();
//创建对象
myLock.lock();
//获取锁try{...}
finally{
myLock.unlock();
//释放锁
}
로그인 후 복사

一定要在finally中释放锁。如果不在finally中释放锁,锁确实将一直得不到释放。正如同我们在调用资源后会使用close()方法。值得一提的,当我们使用锁的时候,我们不能使用try-with-resource,因为这个锁并不是用close来关闭的。

ReentrantLock具有可重入性

如果你要在递归或者循环程序中使用锁,那么就放心的用吧。ReentrantLock锁具有可重入性,他会在每次调用lock()的时候维护一个计数记录着被调用的次数,在每一次的lock调用都必须要用unlock来释放。

条件对象


if(a>b) a.set(b-1);
로그인 후 복사

上面是一个很简单的条件判断,但是我们在并发程序中不能直接这样书写。如果在这个线程刚刚做完判断之后,另外一个线程被唤醒,并且另外一个线程在操作之后使得a小于b(if语句中的条件已经不再正确)。但我们还会执行if中的语句,这是不正确的。

或许会想把整个if语句直接放在锁里面,确保自己的代码不会被打断。但是这样又存在一个问题,如果if判断是false,那么if中的语句不会被执行。但如果我们需要去执行if中的语句,甚至我们要一直等待if判断变的正确之后去执行if中的语句的情况下,这时if语句再也不会变得正确了,因为我们的锁把这个线程锁死,其他的线程没办法访问临界区并修改a和b的值让if判断变得正确。这时候我们只能放弃锁,等待其他线程使用,再获得锁,进行判断,如果判断仍未false就重复之前的操作。这种繁琐的过程是我们不希望的。

通常,线程在上锁进入临界区之后存在一个问题:线程所需的资源,在别的线程中使用或并不满足他们能执行的条件,这个时候我们需要用一个条件对象来管理这些得到了一个锁,但是不能做有用工作的线程

Condition


Condition类在临界区起到了条件对象的作用。

我们用ReentrantLock类中的newCondition方法来获取一个条件对象。

Condition cd = myLock.newCondition();
로그인 후 복사

我们在if语句下面直接跟上await方法,这个方法表示这个线程被阻塞,并放弃了锁,进入等待状态等其他的线程来操作。其他的线程在顺利执行if语句内容之后,调用signalAll方法,这个方法将会重新去激活所有的因为这个条件被阻塞的线程,让这些线程重新获得机会,这些线程被允许从被阻塞的地方继续进行。此时,线程应该再次测试该条件,如果还是不能满足条件,需要再次重复上述操作。

ReentrantLock myLock = new ReentrantLock();//创建锁对象myLock.lock();//给下面的临界区上锁Condition cd = myLock.newCondition();//创建一个Condition对象,这个cd对象表示条件对象while(!(a>b))
    cd.await();//上面的while循环和await方法调用是标准写法//如果不能满足if的条件,那么他将进入阻塞状态,放弃锁,等待别人去激活它a.set(b-1);//一直等到从while循环出来,满足了判断的条件,我们执行自己的功能cd.signalAll();//调用signalAll方法去激活其他的被阻塞的线程。如果所有的线程都在等待其他线程signalAll,则进入死锁
로그인 후 복사

总结来说,Condition对象和锁有这样几个特点。

  1. 锁可以用来保护代码片段,任何时刻只能有一个线程进入被保护的区域

  2. 锁可以管理试图进入临界区的线程

  3. 锁可以拥有一个或多个条件对象

  4. 每个条件对象管理那些因为前面所描述的原因而不能被执行但已经进入被保护代码段的线程

synchronized


ReentrantLock和Condition对象是一种用来保护代码片段的方法。还可以通过使用关键字synchronized来修饰方法,从而给方法添加一个内部锁。java的每一个对象都有一个内部锁,每个内部锁会保护那些被synchronized修饰的方法。也就是说,如果想调用这个方法,首先要获得内部的对象锁。

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

与ReentrantLock比较

我们先拿出上面的代码:

public void function(){
    ReentrantLock myLock = new ReentrantLock();
    myLock.lock();

    Condition cd = myLock.newCondition();    while(!(a>b))
        cd.await();

    a.set(b-1);

    cd.signalAll();
}
로그인 후 복사

如果我们用synchronized来实现这段代码,将会变成下面的样子:

public synchronized void function(){
    while(!(a>b))
        wait();

    a.set(b-1);

    notifyAll();
}
로그인 후 복사

需要我们注意的是,在使用synchronized关键词时,无需再去用ReentrantLock和Condition对象,我们用wait方法替换了await方法,notifyAll方法替换了signalAll方法。这样写确实比之前的简单了很多。

静态synchronized

将静态方法声明为synchronized也是合法的。如果调用这种方法,将会获取相关的类对象的内部锁。比如我们调用Test类中的静态方法,这时,Test.class对象的锁将被锁住。

내부 잠금 및 조건의 제한

내부 잠금은 간단하지만 많은 제한 사항이 있습니다.

  1. 진행 중인 프로세스를 중단할 수 없습니다. 잠금을 획득하려는 스레드

  2. 는 조건을 통해 조건을 인스턴스화할 수 없기 때문에 잠금을 획득하려고 할 때 시간 초과

  3. 를 설정할 수 없습니다. 잠금당 하나의 조건만 갖는 것만으로는 충분하지 않을 수 있습니다

위 내용은 Java 동시성--모든 스레드 메커니즘 예제에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!