Java整理(四)并发

线程简介

线程生命周期

l  新建状态:

使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

l  就绪状态:

当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

l  运行状态:

如果就绪状态的线程获取CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

l  阻塞状态:

如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

l  死亡状态:

一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

优先级

调度器倾向于先执行优先级高的线程。

Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY) - 10 (Thread.MAX_PRIORITY )。

默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)

Thread.currentThread().getPriority();
Thread.currentThread().setPriority(10);

后台线程

后台线程并不属于程序中不可或缺的部分,当所有非后台线程结束时,程序也就终止,同时会杀死进程中所有后台线程。

线程启动之前调用setDaemon(true)方法,设置为后台线程。

isDaemon()方法确定线程是否是后台线程。

后台线程创建的线程也是后台线程。

常用方法sleep、yield、join

public static void sleep(long millisec)

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

public static void yield()

暂停当前正在执行的线程对象,并执行其他线程。

public final void join(long millisec)

等待该线程终止的时间最长为millis 毫秒。超时参数可以为空

 

创建一个线程

Java 提供了三种创建线程的方法:

通过实现 Runnable 接口;

通过继承 Thread 类本身;

通过 Callable 和 Future 创建线程。

实现Runnable 接口

创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。

重写public void run()方法,实例化一个线程对象Thread(RunnablethreadOJ, String threadName),调用start()方法。

必须显式地将一个任务附着到线程上。

如:

public class Main {

    public static void main(String[] args) {
        RunnableDemo R1 = new RunnableDemo("Thread1");
        R1.start();
//        也可以不覆盖start()函数,在类外调用
//        Thread t = new Thread(R1,"1");
//        t.start();
//        new Thread(R1,"2").start();
    }
}

class RunnableDemo implements Runnable {
    private Thread t;
    private String threadName;

    RunnableDemo(String name) {
        threadName = name;
        System.out.println("Creating " + threadName);
    }

    public void run() {
        System.out.println("Running " + threadName);
        try {
            for (int i = 4; i > 0; i--) {
                System.out.println("Thread: " + threadName + ", " + i);
                Thread.sleep(50);
            }
        } catch (InterruptedException e) {
            System.out.println(threadName + " interrupted.");
        }
        System.out.println("Thread " + threadName + " exiting.");
    }

    public void start() {
        System.out.println("Starting " + threadName);
        if (t == null) {
            t = new Thread(this, threadName);
            t.start();
        }
    }
}

 

继承Thread类

创建一个新类继承Thread类,创建该类实例。

重写run()方法,调用父类start()方法。

public class Main {

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new ThreadDemo("" + i).start();
        }
        Thread th = new ThreadDemo("Thread2");
        th.start();
    }
}

class ThreadDemo extends Thread {
    private String threadName;

    ThreadDemo(String name) {
        threadName = name;
        System.out.println("Creating " + threadName);
    }

    public void run() {
        System.out.println("Running " + threadName);
        try {
            for (int i = 4; i > 0; i--) {
                System.out.println("Thread: " + threadName + ", " + i);
                Thread.sleep(50);
            }
        } catch (InterruptedException e) {
            System.out.println(threadName + " interrupted.");
        }
        System.out.println("Thread " + threadName + " exiting.");
    }

    public void start() {
        System.out.println("Starting " + threadName);
        super.start();
    }
}

Thread方法

下表列出了Thread类的一些重要方法:

序号

方法描述

1

public void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。

2

public void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。

3

public final void setName(String name)
改变线程名称,使之与参数 name 相同。

4

public final void setPriority(int priority)
 更改线程的优先级。

5

public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。

6

public final void join(long millisec)
等待该线程终止的时间最长为 millis 毫秒。

7

public void interrupt()
中断线程。

8

public final boolean isAlive()
测试线程是否处于活动状态。

测试线程是否处于活动状态。上述方法是被Thread对象调用的。下面的方法是Thread类的静态方法。

序号

方法描述

1

public static void yield()
暂停当前正在执行的线程对象,并执行其他线程。

2

public static void sleep(long millisec)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

3

public static boolean holdsLock(Object x)
当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true

4

public static Thread currentThread()
返回对当前正在执行的线程对象的引用。

5

public static void dumpStack()
将当前线程的堆栈跟踪打印至标准错误流。

Callable和Future创建线程

Callabel是一种具有类型参数的泛型,call()方法的返回值。

1. 创建Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。

2. 创建 Callable 实现类的实例,使用FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。

3. 使用 FutureTask 对象作为Thread 对象的 target 创建并启动新线程

4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableThreadTest implements Callable<Integer> {
    public static void main(String[] args) throws InterruptedException {
        CallableThreadTest ctt = new CallableThreadTest();
        FutureTask<Integer> ft = new FutureTask<>(ctt);
        for(int i = 0;i < 100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
            if(i==20)
            {
                new Thread(ft,"有返回值的线程").start();
            }
            Thread.sleep(10);
        }
        try
        {
            System.out.println("子线程的返回值:"+ft.get());
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        } catch (ExecutionException e)
        {
            e.printStackTrace();
        }

    }
    @Override
    public Integer call() throws Exception
    {
        int i = 0;
        for(;i<100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" "+i);
            Thread.sleep(10);
        }
        return i;
    }
}

创建线程的三种方式的对比

1. 采用实现 Runnable、Callable 接口的方式创见多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。

2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

使用Executor

java.util.concurrent包中的Executor可以管理Thread对象。如:

importjava.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService exec = Executors.newCachedThreadPool();
for(int i=0;i<5;i++){
    exec.execute(new RunnableDemo("Thread"+i));
}
exec.shutdown();

 

exec = Executors.newFixedThreadPool(5);

可以一次性预先执行代价高昂的线程分配,限制线程数量。

exec = Executors.newSingleThreadExecutor();

就像是数量为1的FixedThreadPool,可以使所有任务使用同一个线程,例如监听进入的套接字连接的任务。

 

在Callable接口的实现中,可以使用ExecutorService.submit(new CallableThreadTest())方法调用,产生Future对象。

在Java5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的submit(Callable<T> task) 方法来执行,并且返回一个 <T>Future<T>,是表示任务等待完成的Future。

Callable中的call()方法类似Runnable的run()方法,就是前者有返回值,后者没有。

当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。

同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。

/**
 * 线程池使用Callable参数 submit()执行
 */
CallableThreadTest ctt = new CallableThreadTest();
ExecutorService exec = Executors.newFixedThreadPool(10);
Future<Integer>[] fts = new Future[10];
for(int i=0;i<10;i++){
     fts[i]= exec.submit(ctt);
}
Thread.sleep(1000);
for(int i=0;i<10;i++){
    System.out.println(fts[i].get());
}
exec.shutdown();

 

execute和submit区别

1、接收的参数不一样

2、submit有返回值,而execute没有

个人觉得cancelexecution这个用处不大,很少有需要去取消执行的。

3、submit方便Exception处理

如果你的task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。

同步

volatile关键字

当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。

volatile可以保证可见性(并发三大特性可见性、原子性、顺序性),但无法保证原子性操作,如i++操作。

 “观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”——《深入理解Java虚拟机》。

1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

2)它会强制将对缓存的修改操作立即写入主存;

3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作(Java操作)的原子性。用到volatile情况举例:

1、状态标记量:

volatile boolean inited = false;
//线程1:
context = loadContext();
inited = true;

//线程2:
while(!inited ){
    sleep()
}
doSomethingwithconfig(context);

2、double check

class Singleton{
    private volatile static Singleton instance = null;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

 

使用Synchronized

Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

五、以上规则对其它对象锁同样适用。

public class Test_synchronized {
    public static void main(String[] args) {
        final SynchDemo sd = new SynchDemo();
        new Thread(){public void run(){
            sd.a();
        }}.start();
        new Thread(){public void run(){
            sd.b();
        }}.start();
        new Thread(){public void run(){
            sd.c();
        }}.start();
    }
}

class SynchDemo{
    private Object syncObject = new Object();
    public synchronized  void a(){
        for(int i=0;i<5;i++){
            System.out.println("a"+i);
            Thread.yield();
        }
    }
    public void b(){
        synchronized (syncObject){
            for(int i=0;i<5;i++){
                System.out.println("b"+i);
                Thread.yield();
            }
        }
    }

    public void c(){
        synchronized (this){
            for(int i=0;i<5;i++){
                System.out.println("c"+i);
                Thread.yield();
            }
        }
    }
}

ac的用法相同,公用this的锁,b的用法与ac互不影响,因为锁不同。

显示Lock对象

在ava.util.concurrent.locks中,Lock对象必须被显式创建、锁定和释放。

importjava.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test_lock {
    Lock lock = new ReentrantLock();
    public void fuc(){
        lock.lock();
        try {
            //todo
            return;
        }finally {
            lock.unlock();
        }
    }
}

在使用lock对象时,调用lock(),必须在try-finally的finally子句中加入unlock()。return必须在try子句中出现,确保unlock()不会过早发生。

wait()、notify()、notifyAll()

调用sleep()yield()时锁没有释放,调用wait()时,线程被挂起释放锁

Object类中wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。

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

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

如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。

其中wait方法有三个over load方法:

wait()/wait(long)/wait(long,int)

wait方法通过参数可以指定等待的时长。如果没有指定参数,默认一直等待直到被通知。

Condition

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,在阻塞队列那一篇博文中就讲述到了,阻塞队列实际上是使用了Condition来模拟线程间协作。

Condition是个接口,基本的方法就是await()和signal()方法;

Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()

调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

Conditon中的await()对应Object的wait();

Condition中的signal()对应Object的notify();

Condition中的signalAll()对应Object的notifyAll()。

举个生产者消费者的例子:

class BoundedBuffer {
    final Lock lock = new ReentrantLock();//锁对象
    final Condition notFull  = lock.newCondition();//写线程条件
    final Condition notEmpty = lock.newCondition();//读线程条件

    final Object[] items = new Object[100];//缓存队列
    int putptr/*写索引*/, takeptr/*读索引*/, count/*队列中存在的数据个数*/;

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)//如果队列满了
                notFull.await();//阻塞写线程
            items[putptr] = x;//赋值
            if (++putptr == items.length) putptr = 0;//如果写索引写到队列的最后一个位置了,那么置为0
            ++count;//个数++
            notEmpty.signal();//唤醒读线程
        } finally {
            lock.unlock();
        }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)//如果队列为空
                notEmpty.await();//阻塞读线程
            Object x = items[takeptr];//取值
            if (++takeptr == items.length) takeptr = 0;//如果读索引读到队列的最后一个位置了,那么置为0
            --count;//个数--
            notFull.signal();//唤醒写线程
            return x;
        } finally {
            lock.unlock();
        }
    }
}

 

终止任务

调用t.interrupt(),将线程t的中断标志位置为True,不会终止线程(当线程遇到可中断阻塞的时候才会抛出InterruptedException异常,抛出异常后线程的中断标志位会重置为false)。

------------ Sleep,wait(),可中断IO属于可中断的阻塞,调用t.interrupt()可以响应中断,抛出异常。

------------ 而不可中断IO和Synchronized属于不可中断阻塞,调用t.interrupt(),需要等到阻塞后才会响应中断抛出异常。

调用Thread.interrupted(),返回当前线程的中断标志位,并将中断标志位置为false

调用t.isInterrupted(),返回当前线程的中断标志位,不会重置标志位

使用interrupt()中断线程

当一个线程运行时,另一个线程可以调用对应的Thread对象的interrupt()方法来中断它,该方法只是在目标线程中设置一个标志,表示它已经被中断,并立即返回。这里需要注意的是,如果只是单纯的调用interrupt()方法,线程并没有实际被中断,会继续往下执行,需要线程遇到可中断阻塞才可以抛出异常终止线程。

public class SleepInterrupt implements Runnable{

    public void run(){

        try{

            System.out.println("in run() - about to sleep for 20 seconds");

            Thread.sleep(20000);

            System.out.println("in run() - woke up");

        }catch(InterruptedException e){

            System.out.println("in run() - interrupted while sleeping");

            //处理完中断异常后,返回到run()方法入口,

            //如果没有return,线程不会实际被中断,它会继续打印下面的信息

            return;

        }

        System.out.println("in run() - leaving normally");

    }



    public static void main(String[] args) {

        SleepInterrupt si = new SleepInterrupt();

        Thread t = new Thread(si);

        t.start();

        //主线程休眠2秒,从而确保刚才启动的线程有机会执行一段时间

        try {

            Thread.sleep(2000);

        }catch(InterruptedException e){

            e.printStackTrace();

        }

        System.out.println("in main() - interrupting other thread");

        //中断线程t

        t.interrupt();

        System.out.println("in main() - leaving");

    }

}

例子2

public class Main{

    synchronized void f(Thread t) throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"start f()...");
//        System.out.println(Thread.interrupted());//执行这一句会重置中断标志位不会抛出中断异常
        for (int i=0;i<100000;i++){
            if (i==50000){
                System.out.println("t.isInterrupted():"+t.isInterrupted());
            }
        }
        try {
            Thread.sleep(3000);
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("t.isInterrupted():"+t.isInterrupted());
        }
        System.out.println(Thread.currentThread().getName()+"over f()...");
    }

    public static void main(String[] args) throws InterruptedException {
        Main m = new Main();
        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                try {
                    m.f(this);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread t2 = new Thread("t2"){
            @Override
            public void run() {
                try {
                    m.f(this);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        t1.start();
        Thread.sleep(100);
        t2.start();
        Thread.sleep(100);
        t2.interrupt();

    }
}

看一下interrupt()源码

public void interrupt() {

    if (this != Thread.currentThread())

        checkAccess();



    synchronized (blockerLock) {

        Interruptible b = blocker;

        if (b != null) {

            interrupt0();           // Just to set the interrupt flag

            b.interrupt(this);

            return;

        }

    }

    interrupt0();

}

有一个步骤是设置interrupt的标志位。

使用isInterrupted()方法判断中断状态

可以在Thread对象上调用isInterrupted()方法来检查任何线程的中断状态。线程一旦被中断,isInterrupted()方法便会返回true,而一旦sleep()方法抛出异常,它将清空中断标志,此时isInterrupted()方法将返回false

public boolean isInterrupted() {

    return isInterrupted(false);//这里没有重置标志位

}

返回上面设置interrupt的标志位,这里false表示是否ClearInterrupted,也就是是否清除标志位(否),和下面的Thread.interrupter()区别。

使用Thread.interrupted()方法判断中断状态

可以使用Thread.interrupted()方法来检查当前线程的中断状态(并隐式重置为false)。又由于它是静态方法,因此不能在特定的线程上使用,而只能报告调用它的线程的中断状态,如果线程被中断,而且中断状态尚不清楚,那么,这个方法返回true。与isInterrupted()不同,它将自动重置中断状态为false,第二次调用Thread.interrupted()方法,总是返回false,除非中断了线程。

public static boolean interrupted() {
    //这里重置了中断标志位,这里的True表示ClearInterrupted=True
    //看变量名就知道啥意思,而不是中断标志位为True,不要弄乱了
    return currentThread().isInterrupted(true);

}

这里true表示清除标志位,也就是再次调用时会返回false。

使用Executor执行任务中断

调用interrupt()必须持有Thread对象,而如果使用Executor,那么通过调用submit()而不是executor()来启动任务,就可以持有该任务的上下文。submit()将返回一个泛型Future<?>,持有这种Future关键在于可以在其上调用cancel(),可以使用它来中断某个特定任务。

 

Java内存模型

Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。每个线程都有自己的工作内存,如何将自己的工作内存同步到主内存中从而达到与其他线程进行良好的交互、协同工作,则是Java内存模型要解决的问题。Java内存模型定义了8种操作来完成。

lock:作用于主内存的变量,把变量标识位一条线程独占的状态。

unlock:作用于主内存的变量,把处于锁定状态的变量释放出来,后可被其他线程lock。

read:作用于主内存的变量,把变量的值传输到线程的工作内存中,以便load。

load:作用于工作内存的变量,把read后的变量值load到工作内存的变量副本中。

use:作用于工作内存的变量,把变量值传递给执行引擎。

assign(赋值):作用于工作内存的变量,将执行引擎的赋值给工作内存的变量。

store:作用于工作内存的变量,把工作内存中的一个变量的值传送的主内存中,以便write。

write:作用于主内存的变量,把store操作从工作内存中的变量值放入主内存的变量中。

其中,store和write,read和load需要前后顺序执行(中间可以插入其他操作)。

原子性、可见性和有序性

Java内存模型是围绕着在并发过程中如何处理这三个特征来建立的。

原子性

Java内存模型直接保证的原子性变量操作包括read、load、assign、use、store和write。而虽然64位的long和double不是原子性读写,但几乎不会发生,因为商用JVM几乎都把64位数据的读写操作作为原子操作对待。

如果一些应用场景需要更大范围的原子性操作(如一些事务),Java内存还提供了lock和unlock操作,这两个操作反映在synchronized关键字中。

可见性

可见性指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。Java内存模型通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性。volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。

有序性

Java程序中,如果在本线程内观察,所有的操作都是有序的(指令串行);如果一个线程观察另一个线程,所有操作都是无序的(指令重排)。Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性。volatile关键字本身就包含禁止指令重排的语义,而synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”获得的。

指令重排的基本原则

程序顺序原则:一个线程内保证语义的串行性

volatile规则:volatile变量的写,先发生于读

锁规则:解锁(unlock)必然发生在随后的加锁(lock)前

传递性:A先于B,B先于C 那么A必然先于C

线程的start方法先于它的每一个动作

线程的所有操作先于线程的终结(Thread.join())

线程的中断(interrupt())先于被中断线程的代码

对象的构造函数执行结束先于finalize()方法

看来synchronized关键字在并发上是万能的解决方式,的确是这样,但万能的滥用将会影响性能。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值