Java并发编程

一、基础篇

 

 进程是系统进行资源分配和调动的基本单位,一个进程中至少有一个线程,进程中多个线程共享进程的资源。

线程是进程中的一个实体,线程是不会独立存在的,没有进程就没有线程。

对于CPU资源比较特殊,线程才是CPU分配的基本单位。

main函数启动->JVM进程->main函数线程称为主线程

 

 

内存与线程

内存与线程的关心,主要指JVM内存模型与线程之间的关系,它也是线程安全问题的主要诱因。

 

 

 

 

 

使用JDK工具观察线程

jcmd

jstack

jvisualVM

jconsole

 

 

线程创建的三种方法

  • 继承Thread类
  • 实现Runnable接口

  • 实现Callable接口

Account类

package com.aidata.concurrency;

public class Account {

    private String accountNo;
    private String accountName;
    private boolean valid;

    public Account(){}

    public Account(String accountNo, String accountName, boolean valid){
        this.accountNo = accountNo;
        this.accountName = accountName;
        this.valid = valid;
    }

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

    public boolean isValid() {
        return valid;
    }

    public void setValid(boolean valid) {
        this.valid = valid;
    }

    @Override
    public String toString() {
        return "Account{" +
                "accountNo='" + accountNo + '\'' +
                ", accountName='" + accountName + '\'' +
                ", valid=" + valid +
                '}';
    }
}

 

继承Thread

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
package com.aidata.concurrency;

// 1.继承Thread
public class CreateThreadExtendsThread extends Thread {
    private Account account;

    public CreateThreadExtendsThread(){}

    public CreateThreadExtendsThread(Account account){
        this.account = account;
    }

    public void setAccount(Account account){
        this.account = account;
    }

    // 2.覆写run方法
    public void run(){
        System.out.println(this.getName() + " Account's information:" + this.account +"," + this.getState());
    }

    public static void main(String[] args) {

        Account account = new Account("999999", "Wang", true);
        CreateThreadExtendsThread thread0 = new CreateThreadExtendsThread();
        // 线程传参方式1
        thread0.setAccount(account);
        // 线程没有调用start,状态是NEW
        System.out.println(thread0.getState());
        // 调用start()方法会执行线程中的run()方法,状态是RUNNABLE
        thread0.start();

        try {
            // 休眠一秒钟,保证thread0执行完
            Thread.sleep(1000);
        }catch (Exception e){
            e.printStackTrace();
        }
        //TERMINATED
        System.out.println(thread0.getState());

        // 线程传参方式2
        CreateThreadExtendsThread thread1 = new CreateThreadExtendsThread(account);
        thread1.start();
    }
}

结果

NEW
Thread-0 Account's information:Account{accountNo='999999', accountName='Wang', valid=true},RUNNABLE
TERMINATED
Thread-1 Account's information:Account{accountNo='999999', accountName='Wang', valid=true},RUNNABLE

如果注释掉上面休眠的代码,结果

NEW
RUNNABLE
Thread-1 Account's information:Account{accountNo='999999', accountName='Wang', valid=true},RUNNABLE
Thread-0 Account's information:Account{accountNo='999999', accountName='Wang', valid=true},RUNNABLE

优点:简单,Thread类中有大量的方法,可以通过this获得线程的丰富信息

缺点:Java是单继承的,继承了Thread就无法继承其他类了

 

多线程实现图片下载

package com.aidata.thread;

import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;

public class TestThread extends Thread{

    private String url;
    private String fileName;

    public TestThread(String url, String fileName){
        this.url = url;
        this.fileName = fileName;
    }


    @Override
    public void run() {
        PicDownloader picDownloader = new PicDownloader();
        picDownloader.downloader(this.url, this.fileName);
        System.out.println("下载了图片,名为:" + fileName);
    }

    public static void main(String[] args) {
        TestThread t1 = new TestThread("https://img2018.cnblogs.com/i-beta/1755628/202002/1755628-20200213225816807-610773901.png", "1.png");
        TestThread t2 = new TestThread("https://img2018.cnblogs.com/i-beta/1755628/202002/1755628-20200213230257947-343445373.png", "2.png");
        TestThread t3 = new TestThread("https://img2018.cnblogs.com/i-beta/1755628/202002/1755628-20200214161105678-97811026.png", "3.png");
        t1.start();
        t2.start();
        t3.start();
    }
}

class PicDownloader{
    public void downloader(String url, String fileName){
        try {
            FileUtils.copyURLToFile(new URL(url), new File(fileName));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

结果

 

 

 

实现Runnable接口

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

实现接口

package com.aidata.concurrency;

public class CreateThreadImplementsRunnable implements Runnable{

    private Account account;

    public CreateThreadImplementsRunnable(){}

    public CreateThreadImplementsRunnable(Account account){
        this.account = account;
    }

    public void setAccount(Account account) {
        this.account = account;
    }

    public void run() {
        // 接口中只有run()方法,要获取名称使用下面的方法
        System.out.println(Thread.currentThread().getName() + " Account's information: " + this.account);
    }

    public static void main(String[] args) {
        final Account account = new Account("888888", "Wang", true);
        CreateThreadImplementsRunnable thread0 = new CreateThreadImplementsRunnable();
        // 创建线程方式1
        thread0.setAccount(account);
        new Thread(thread0).start();
        // 创建线程方式2
        CreateThreadImplementsRunnable thread1 = new CreateThreadImplementsRunnable(account);
        new Thread(thread1).start();
        // 创建线程方式3 接口方便之处是可以使用匿名内部类来实现
        new Thread(new Runnable() {
            public void run() {
                System.out.println(account);
            }
        }).start();
    }
}

接口中没有start()方法,必须借助Thread类才能启动,以及获取线程相关的信息。

 

 

实现Callable接口

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

实现接口

package com.aidata.concurrency;

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

public class CreateThreadImplementsCallable implements Callable<Account> {

    private Account account;

    public CreateThreadImplementsCallable(){}

    public CreateThreadImplementsCallable(Account account){
        this.account = account;
    }

    public void setAccount(Account account) {
        this.account = account;
    }

    // 返回值和泛型设置有关
    public Account call() throws Exception {
        System.out.println(Thread.currentThread().getName() + " Account's information: " + this.account);
        this.account.setValid(false);
        Thread.sleep(3000);
        return this.account;
    }

    public static void main(String[] args) {
        Account account = new Account("66666666", "Wang", true);
        CreateThreadImplementsCallable call = new CreateThreadImplementsCallable();
        call.setAccount(account);
        // 异步阻塞模型
        FutureTask<Account> ft = new FutureTask<Account>(call);
        new Thread(ft).start();
        try {
            // get得到的值类型和泛型有关
            Account result = ft.get();
            System.out.println("result:" + result);
        } catch (Exception e){
            e.printStackTrace();
        }
    }

}

结果

Thread-0 Account's information: Account{accountNo='66666666', accountName='Wang', valid=true}
result:Account{accountNo='66666666', accountName='Wang', valid=false}

三秒后,才会打印result,因为

Account result = ft.get();

是阻塞的,线程中的执行完,即休眠三秒后,才继续执行下面的

 

 

JOIN等待线程执行终止

使用场景:等待线程执行终止之后,继续执行

易混淆知识点:join方法为Thread类直接提供的方法,而wait和notify为Object类中的方法

可以使用CountDownLatch达到同样的效果

IDEA 双击shift,搜索Objec,使用快捷键Alt+7

 

 Thread的三个join方法

public final void join() throws InterruptedException {
    join(0);
}

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
// 还是使用的Object的wait方法 wait(delay); now
= System.currentTimeMillis() - base; } } }

// nanos 纳秒值,四舍五入的概念,0-500000之间不管,只用毫秒,500000-999999就毫秒加1
public final synchronized void join(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } join(millis); }

 

例子

package com.aidata.concurrency;

public class JoinDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(2000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " run over!");
            }
        });

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                try{
                    Thread.sleep(2000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " run over!");
            }
        });
        t1.start();
        t2.start();
        System.out.println(Thread.currentThread().getName() + " wait " + t1.getName() + " and " + t2.getName() + " run over!");

        // 打开和关闭此段注释观察执行效果来理解join的用途
        try{
            t1.join();
            t2.join();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        // 打开和关闭此段注释观察执行效果来理解join的用途
//        try{
//            t1.join(1000);
//            t2.join(1000);
//            // t1.join(1000, 500);
//            // t2.join(1000, 500);
//        }catch (InterruptedException e){
//            e.printStackTrace();
//        }
        System.out.println("final " + t1.getName() + " and " + t2.getName() + " run over!");

        System.out.println("t1's state: " + t1.getState());
        System.out.println("t2's state: " + t2.getState());

    }
}

结果

main wait Thread-0 and Thread-1 run over!
Thread-0 run over!
Thread-1 run over!
final Thread-0 and Thread-1 run over!
t1's state: TERMINATED
t2's state: TERMINATED

运行到

t1.join();
t2.join();

时,主线程会阻塞,等待t1、t2执行完,再执行

System.out.println("final " + t1.getName() + " and " + t2.getName() + " run over!");

如果注释掉上面的两个join,结果为

main wait Thread-0 and Thread-1 run over!
final Thread-0 and Thread-1 run over!
t1's state: TIMED_WAITING
t2's state: TIMED_WAITING
Thread-0 run over!
Thread-1 run over!

t1、t2不会阻塞主线程,由于休眠,线程里的内容最后才执行完,TIMED_WAITING带有时间的等待,即在执行sleep

 

sleep方法解析

Thread类中的一个静态方法,暂时让出执行权,不参与CPU调度,但是不释放锁。时间到了就进入就绪状态,一旦获取到CPU时间片,则继续执行。

 public static native void sleep(long millis) throws InterruptedException;

    /**
     * Causes the currently executing thread to sleep (temporarily cease
     * execution) for the specified number of milliseconds plus the specified
     * number of nanoseconds, subject to the precision and accuracy of system
     * timers and schedulers. The thread does not lose ownership of any
     * monitors.
     *
     * @param  millis
     *         the length of time to sleep in milliseconds
     *
     * @param  nanos
     *         {@code 0-999999} additional nanoseconds to sleep
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative, or the value of
     *          {@code nanos} is not in the range {@code 0-999999}
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public static void sleep(long millis, int nanos)
    throws InterruptedException {
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        sleep(millis);
    }

 

什么是Native方法

一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数。 "A native method is a Java method whose implementation is provided by non-java code." 在定义一个native method时,并不提供实现体(有些像定义一个java interface),因为其实现体是由非java语言在外面实现的。

用异常打断sleep

package com.aidata.concurrency;

public class SleepDemo {

    public static void main(String[] args) {
        final Object lock = new Object();

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                synchronized (lock){
                    System.out.println(Thread.currentThread().getName() + " get Lock, sleeping");
                }
                try{
                    Thread.sleep(2000);
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " sleep over and run over!");
            }
        });

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                synchronized (lock){
                    System.out.println(Thread.currentThread().getName() + " get Lock, sleeping");
                }
                try{
                    Thread.sleep(2000);
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " sleep over and run over!");
            }
        });

        t1.start();
        t2.start();
        t1.interrupt();
    }
}

结果

Thread-0 get Lock, sleeping
Thread-1 get Lock, sleeping
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.aidata.concurrency.SleepDemo$1.run(SleepDemo.java:14)
    at java.lang.Thread.run(Thread.java:748)
Thread-0 sleep over and run over!
Thread-1 sleep over and run over!

Thread-0没有sleep,而是抛出异常后直接打印了 Thread-0 sleep over and run over!

没有interrupt结果

Thread-0 get Lock, sleeping
Thread-1 get Lock, sleeping
Thread-0 sleep over and run over!
Thread-1 sleep over and run over!

 

 

yield方法

不建议使用

Thread类中的静态native方法,让出剩余的时间片,本身进入就绪状态,CPU再次调度还可能调度到本线程。

易混淆知识点:sleep是在一段时间内进入阻塞状态,CPU不会调度它。而yield是让出执行权,本身处于就绪状态,CPU还可能立即调度它。

    /**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     */
    public static native void yield();

例子

package com.aidata.concurrency;

public class YieldDmo0 extends Thread{

    @Override
    public void run() {
        System.out.println(this.getName() + " yield");
        this.yield();
        System.out.println(this.getName() + " run over");
    }

    public static void main(String[] args) {
        for (int i=0; i<1000; i++){
            YieldDmo0 demo = new YieldDmo0();
            demo.start();
        }
    }
}

会出现这种连城一片的形式

...
Thread-671 yield
Thread-673 yield
Thread-672 yield
Thread-673 run over
Thread-671 run over
Thread-672 run over
...

加锁后

package com.aidata.concurrency;

// yield 让出执行权,但是不释放锁
public class YieldDmo1 extends Thread{

    // 共享锁
    public static Object lock = new Object();

    @Override
    public void run() {
        synchronized (lock){
            System.out.println(this.getName() + " yield");
            this.yield();
            System.out.println(this.getName() + " run over");
        }
    }

    public static void main(String[] args) {
        for (int i=0; i<1000; i++){
            YieldDmo1 demo = new YieldDmo1();
            demo.start();
        }

    }
}

成对出现

Thread-0 yield
Thread-0 run over
Thread-5 yield
Thread-5 run over
...

yield 不释放锁,每个线程都执行完才能执行另外的线程

wait会使线程进入阻塞状态,且是释放锁的。wait方法结合synchronized关键字、notify方法使用。

package com.aidata.concurrency;

// yield 让出执行权,但是不释放锁
public class YieldDmo1 extends Thread{

    // 共享锁
    public static Object lock = new Object();

    @Override
    public void run() {
        synchronized (lock){
            System.out.println(this.getName() + " yield");
//            this.yield();
            // 使用wait方法来做对比,查看释放锁与不释放锁的区别
            try{
                lock.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(this.getName() + " run over");
        }
    }

    public static void main(String[] args) {
        for (int i=0; i<1000; i++){
            YieldDmo1 demo = new YieldDmo1();
            demo.start();
        }
        // 配合wait使用看效果
        synchronized (lock){
            lock.notifyAll();
        }
    }
}

上面全是yield,执行notifyAll()后下面全是run over

...
Thread-994 yield
Thread-995 yield
Thread-996 yield
Thread-999 yield
Thread-996 run over
Thread-995 run over
Thread-994 run over
Thread-993 run over
Thread-992 run over
Thread-991 run over
...

每次线程执行,执行第一个打印后,执行wait()方法都会阻塞,并释放锁给另一个线程,另一个线程也执行第一句打印,执行wait()释放锁给另一个线程...

直到执行notifyAll()方法,所有线程结束阻塞,开始执行剩下的语句。

 

 

线程中断方法

Interrupt相关方法

线程中断是线程间的一种协作模式,通过设置线程中断标志来实现,线程根据这个标志来自行处理。

Thread中相关方法

    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();
    }

    /**
     * Tests whether the current thread has been interrupted.  The
     * <i>interrupted status</i> of the thread is cleared by this method.  In
     * other words, if this method were to be called twice in succession, the
     * second call would return false (unless the current thread were
     * interrupted again, after the first call had cleared its interrupted
     * status and before the second call had examined it).
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if the current thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see #isInterrupted()
     * @revised 6.0
     */
    public static boolean interrupted() {
        return currentThread().isInterrupted(true); // true,改变中断标记
    }

    public boolean isInterrupted() {
        return isInterrupted(false); // false,返回原有的中断标记,不改变,true就是true,false就是false
    }

    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

    private native void interrupt0();

例子

package com.aidata.concurrency;

// isInterrupted和interrupt的使用
public class TnterruptDemo0 {
    public static void main(String[] args) {

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                // 2.开始执行循环
                for (int i=0; i<99999; i++){
                    // 3.判断是否为中断状态,如果是中断则退出循环
                    if (Thread.currentThread().isInterrupted()){
                        System.out.println(Thread.currentThread().getName() + " interrupted");
                        break;
                    }
                    System.out.println(Thread.currentThread().getName() + i + " is running");
                }
            }
        });
        // 1.启动
        t1.start();

        // 4.调用中断,是否会中断死循环?
        t1.interrupt(); // 把t1线程的中断标记设置成了true
        try {
            t1.join();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(t1.getState());
    }
}

结果

Thread-0 interrupted
TERMINATED

也就是说线程里执行了

 if (Thread.currentThread().isInterrupted()){
     System.out.println(Thread.currentThread().getName() + " interrupted");
     break;
 }

如果去掉上面的判断,则

...
Thread-099996 is running
Thread-099997 is running
Thread-099998 is running
TERMINATED

也就是说,会不会被打断是取决于上面的判断的,而非 

 // 4.调用中断,是否会中断死循环?
 t1.interrupt(); // 把t1线程的中断标记设置成了true

它只是为t1打了一个中断标签,并没有真的去打断,你可以捕获并实现相关逻辑

    public static boolean interrupted() {
        return currentThread().isInterrupted(true); // true,改变中断标记
    }

    public boolean isInterrupted() {
        return isInterrupted(false); // false,返回原有的中断标记,不改变,true就是true,false就是false
    }

isInterrupted() 会返回中断标记,不会进行修改

interrupted() 会修改中断标记

package com.aidata.concurrency;

public class InterruptDemo1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                // 3.条件为!true=false退出循环
                while (!Thread.currentThread().interrupted()){

                }
                // 4.这里输出的是什么true还是false
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().isInterrupted());
            }
        });
        // 1.开始
        t1.start();

        // 2.中断标记设置为true
        t1.interrupt();

        try{
            t1.join();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("main is run over");
    }
}

结果

Thread-0:false
main is run over

没有进入while循环,则 Thread.currentThread().interrupted() 为true

而下面打印中 Thread.currentThread().isInterrupted() 结果为false,说明interrupted() 会修改当前线程的中断标记

while循环的写法应该是

while (!Thread.interrupted()){

}

因为interrupted是静态方法,只对当前的线程生效,所以结果是一样的

while判断更换为 !Thread.currentThread().isInterrupted()

package com.aidata.concurrency;

public class InterruptDemo1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                // 3.条件为!true=false退出循环
                // 5.如果这里更换为Thread.currentThread().isInterrupted()
                while (!Thread.currentThread().isInterrupted()){

                }
                // 4.这里输出的是什么true还是false
                // 6.这里输出的是什么true还是false
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().isInterrupted());
            }
        });
        // 1.开始
        t1.start();

        // 2.中断标记设置为true
        t1.interrupt();

        try{
            t1.join();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("main is run over");
    }
}

结果为

Thread-0:true
main is run over

isInterrupted() 会返回中断标记,不会进行修改

 

中断异常

join方法和sleep一样会抛出中断异常

Thread.interrupt()方法中断线程时,join方法的异常只能在自身线程才能被捕获,在其它线程调用时无法被捕获:

package com.aidata.concurrency;

public class JoninDemo2 {
    public static void main(String[] args) {

        final Thread t0 = new Thread(new Runnable() {
            public void run() {
                System.out.println("t0 is running");

                System.out.println(Thread.currentThread().getName() + " run over!");
            }
        });

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                System.out.println("t1 is runnng");
                try {
                    t0.start();
                    Thread.currentThread().interrupt();
                    t0.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " run over!");
            }
        });
        t1.start();
    }
}

t0线程运行在t1线程中,t1线程interrupt后,遇到t0.join()会抛出异常,也就是这个t0.join()没有运行,即没有阻塞等待t0完成

结果:

t1 is runnng
t0 is running
Thread-0 run over!
java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)
    at java.lang.Thread.join(Thread.java:1252)
    at java.lang.Thread.join(Thread.java:1326)
    at com.aidata.concurrency.JoninDemo2$2.run(JoninDemo2.java:20)
    at java.lang.Thread.run(Thread.java:748)
Thread-1 run over!

 

package com.aidata.concurrency;

public class JoinDemo1 {

    public static void main(String[] args) {

        // 创建t0线程
        final Thread t0 = new Thread(new Runnable() {
            public void run() {
                System.out.println("t0 will sleep 00000000000000000000000");
                try {
                    Thread.sleep(2000*20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("sleep的异常");
                }
                System.out.println(Thread.currentThread().getName() + " run over!");
            }
        });

        // 创建main线程
        final Thread mainThread = Thread.currentThread();

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                System.out.println("in t1 now 1111111111111111111");
                // 调用主线程的interrupt方法,开启中断标记,会影响主线中的join方法抛出异常,但是并不会阻碍t0线程的运行
                mainThread.interrupt();
                // 修改为t0.interrupt();观察效果,会影响t0线程的sleep方法抛出异常,让t0线程结束
//                 t0.interrupt();
                System.out.println(mainThread.getName() + " interrupt!");
                System.out.println(Thread.currentThread().getName() + " run over!");
            }
        });
        t0.start();
        t1.start();
        System.out.println(Thread.currentThread().getName() + " wait " + t0.getName() + " and " + t1.getName() + " run over!");

        try {
            System.out.println("will run t0.join()-----------");
            t0.join();
            System.out.println("running t0.join()===========");
        }catch (InterruptedException e){
            e.printStackTrace();
            System.out.println("t0.join的异常");
        }
        System.out.println("final: " + t0.getName() + " and " + t1.getName() + " run over!");

        System.out.println("t0's state: " + t0.getState());
        System.out.println("t1's state: " + t1.getState());
        System.out.println("main's state: " + mainThread.getState());
    }
}

结果

main wait Thread-0 and Thread-1 run over!
will run t0.join()-----------
in t1 now 1111111111111111111
t0 will sleep 00000000000000000000000
main interrupt!
Thread-1 run over!
java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)
    at java.lang.Thread.join(Thread.java:1252)
    at java.lang.Thread.join(Thread.java:1326)
    at com.aidata.concurrency.JoinDemo1.main(JoinDemo1.java:42)
t0.join的异常
final: Thread-0 and Thread-1 run over!
t0's state: TIMED_WAITING
t1's state: TERMINATED
main's state: RUNNABLE

可知

先打印了

 System.out.println(Thread.currentThread().getName() + " wait " + t0.getName() + " and " + t1.getName() + " run over!");

System.out.println("will run t0.join()-----------");

然后,线程t0和t1开始执行,to进行sleep,t1让主线程interrupt,t1执行完后,主线程的t0.join()开始执行,因为主线程被interrupt了,主线程中的子线程调用join会导致异常,但是该异常不会停止t0线程,to线程仍然执行,只是不join()了,也就是不阻塞执行,因此主线程中下面的打印语句都执行了,一直等到t0休眠结束,整个程序结束。

还可以t0.interrupt(),这会导致sleep抛出异常,导致t0结束

package com.aidata.concurrency;

public class JoinDemo1 {

    public static void main(String[] args) {

        // 创建t0线程
        final Thread t0 = new Thread(new Runnable() {
            public void run() {
                System.out.println("t0 will sleep 00000000000000000000000");
                try {
                    Thread.sleep(2000*20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("sleep的异常");
                }
                System.out.println(Thread.currentThread().getName() + " run over!");
            }
        });

        // 创建main线程
        final Thread mainThread = Thread.currentThread();

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                System.out.println("in t1 now 1111111111111111111");// 修改为t0.interrupt();观察效果,会影响t0线程的sleep方法抛出异常,让t0线程结束
                 t0.interrupt();
                System.out.println(mainThread.getName() + " interrupt!");
                System.out.println(Thread.currentThread().getName() + " run over!");
            }
        });
        t0.start();
        t1.start();

        System.out.println(Thread.currentThread().getName() + " wait " + t0.getName() + " and " + t1.getName() + " run over!");

        System.out.println("final: " + t0.getName() + " and " + t1.getName() + " run over!");

        System.out.println("t0's state: " + t0.getState());
        System.out.println("t1's state: " + t1.getState());
        System.out.println("main's state: " + mainThread.getState());
    }
}

结果

main wait Thread-0 and Thread-1 run over!
t0 will sleep 00000000000000000000000
in t1 now 1111111111111111111
final: Thread-0 and Thread-1 run over!
main interrupt!
Thread-1 run over!
t0's state: TIMED_WAITING
t1's state: TERMINATED
main's state: RUNNABLE
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.aidata.concurrency.JoinDemo1$1.run(JoinDemo1.java:12)
    at java.lang.Thread.run(Thread.java:748)
sleep的异常
Thread-0 run over!

Java内部类与final关键字详解

 

线程安全

可见性

安全性

package com.aidata.concurrency;

class User{
    private String name;
    private String pass;

    public User(String name, String pass){
        this.name = name;
        this.pass = pass;
    }

    public void set(String name, String pass){
        this.name = name;
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.pass = pass;
        System.out.println(Thread.currentThread().getName() + " -name=" + this.name + " pass=" + this.pass);
    }
}

/**
 * servlet是单利多线程的,Servlet本身并不是一个单例实现,只是容器加载的时候只实例化一次,所造成的单例现象
 */

class UserServlet{
    private User user;

    public UserServlet(){
        user = new User("w", "123");
    }

    public void setPass(String name, String pass){
        user.set(name, pass);
    }
}

public class DemoThread00 {
    public static void main(String[] args) {
        final UserServlet us = new UserServlet();

        new Thread(new Runnable() {
            public void run() {
                us.setPass("李四", "777");
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                us.setPass("王五", "888");
            }
        }).start();
    }
}

结果

Thread-0 -name=王五 pass=777
Thread-1 -name=王五 pass=888

两个都是王五,原因:

两个线程,操作同一个对象us

第一个线程

us.setPass("李四", "777");

实际调用了User的

    public void set(String name, String pass){
        this.name = name;
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.pass = pass;
        System.out.println(Thread.currentThread().getName() + " -name=" + this.name + " pass=" + this.pass);
    }

首先设定名字为李四,然后就休眠了5秒,此时第二个线程也操作us,设定名字为王五,也就是说进行了覆盖。5秒后,第一个线程继续工作,密码改为777,进行打印。

第二个线程睡眠5秒后,把密码改为888,进行打印。

 

 

线程安全与Synchronized

当多个线程访问某一个类、对象或方法时,这个类、对象或方法都能表现出与单线程执行时一致的行为,那么这个类、对象或方法是线程安全的。

线程安全问题是由全局变量和静态变量引起的。

若每个线程中全局变量、静态变量只有读操作,而无些操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都是需要考虑线程同步,否则的话就可能影响线程安全。

Synchronized的作用是加锁,所有的Synchronized方法都会顺序执行,这里指占用CPU的顺序。

Synchronized 方法执行方式:

  • 首先尝试获得锁
  • 如果获得锁,则执行Synchronized的方法体内容
  • 如果无法获得锁则等待,并且不断的尝试去获得锁,一旦锁释放,则多个线程会同时去尝试获得锁,造成锁竞争问题

锁竞争问题,在高并发、线程数量高时会引起CPU占用居高不下,或直接宕机。

package com.aidata.concurrency;

class User{
    private String name;
    private String pass;

    public User(String name, String pass){
        this.name = name;
        this.pass = pass;
    }

    public synchronized void set(String name, String pass){
        this.name = name;
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.pass = pass;
        System.out.println(Thread.currentThread().getName() + " -name=" + this.name + " pass=" + this.pass);
    }
}

/**
 * servlet是单利多线程的,Servlet本身并不是一个单例实现,只是容器加载的时候只实例化一次,所造成的单例现象
 */

class UserServlet{
    private User user;

    public UserServlet(){
        user = new User("w", "123");
    }

    public void setPass(String name, String pass){
        user.set(name, pass);
    }
}

public class DemoThread00 {
    public static void main(String[] args) {
        final UserServlet us = new UserServlet();

        new Thread(new Runnable() {
            public void run() {
                us.setPass("李四", "777");
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                us.setPass("王五", "888");
            }
        }).start();
    }
}

加锁后,线程安全

Thread-0 -name=李四 pass=777
Thread-1 -name=王五 pass=888

 

 

 

对象锁和类锁

没有锁

package com.aidata.concurrency;

public class DemoThread02 {

    private /*static*/ int count = 0;

    // 如果是static变量会怎么样?
    public /*synchronized*/ /*static*/ void add(){
        count++;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ">count=" + count);
    }

    public static void main(String[] args) {
        final DemoThread02 thread0 = new DemoThread02();
        final DemoThread02 thread1 = new DemoThread02();
        Thread t0 = new Thread(new Runnable() {
            public void run() {
                thread0.add(); // 1.同一个对象,同一把锁
            }
        }, "thread0");
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                thread0.add();
            }
        }, "thread1");
        t0.start();
        t1.start();
    }
}

结果:

thread0>count=2
thread1>count=2

两个线程调用同一个对象的add方法

实现第一个线程,加1,count变为1,休眠1秒,第二个线程加1,count变为2,第一线程休眠完打印,第二个线程休眠完打印。

 

Synchronized作用在非静态方法上代表的对象锁,一个对象一个锁

package com.aidata.concurrency;

public class DemoThread02 {

    private /*static*/ int count = 0;

    // 如果是static变量会怎么样?
    public synchronized /*static*/ void add(){
        count++;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ">count=" + count);
    }

    public static void main(String[] args) {
        final DemoThread02 thread0 = new DemoThread02();
        final DemoThread02 thread1 = new DemoThread02();
        Thread t0 = new Thread(new Runnable() {
            public void run() {
                thread0.add(); // 1.同一个对象,同一把锁
            }
        }, "thread0");
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                thread0.add();
//                 thread1.add(); // 1.同一个对象,同一把锁
            }
        }, "thread1");
        t0.start();
        t1.start();
    }
}

同一个对象,对象锁,一个线程运行完,锁才给另一个线程,同一把锁互斥

结果

thread0>count=1
thread1>count=2

多个对象之间不会发送锁竞争

package com.aidata.concurrency;

public class DemoThread02 {

    private /*static*/ int count = 0;

    // 如果是static变量会怎么样?
    public synchronized /*static*/ void add(){
        count++;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ">count=" + count);
    }

    public static void main(String[] args) {
        final DemoThread02 thread0 = new DemoThread02();
        final DemoThread02 thread1 = new DemoThread02();
        Thread t0 = new Thread(new Runnable() {
            public void run() {
                thread0.add(); // 1.同一个对象,同一把锁
            }
        }, "thread0");
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                thread1.add(); // 1.同一个对象,同一把锁
            }
        }, "thread1");
        t0.start();
        t1.start();
    }
}

两个对象,分别加锁,不是同一把锁,并不会产生锁的互斥,没有关系

结果

thread1>count=1
thread0>count=1

 

Synchronized作用在静态方法上则升级为类锁,所有对象共享一把锁,存在锁竞争

package com.aidata.concurrency;

public class DemoThread02 {

    private static int count = 0;

    // 如果是static变量会怎么样?
    public synchronized static void add(){
        count++;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ">count=" + count);
    }

    public static void main(String[] args) {
        final DemoThread02 thread0 = new DemoThread02();
        final DemoThread02 thread1 = new DemoThread02();
        Thread t0 = new Thread(new Runnable() {
            public void run() {
                thread0.add(); // 1.同一个对象,同一把锁
            }
        }, "thread0");
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                thread1.add(); // 1.同一个对象,同一把锁
            }
        }, "thread1");
        t0.start();
        t1.start();
    }
}

类锁,所有对象共享一把锁,互斥,静态变量两个线程共享

结果

thread0>count=1
thread1>count=2

 

对象锁的同步和异步

同步:必须等待方法执行完毕,才能向下执行,共享资源访问的时候,为了保证线程安全,必须同步。

异步:不用等待其他方法执行完毕,即可立即执行,例如Ajax异步。

package com.aidata.concurrency;

public class DemoThread03 {
    // 同步执行
    public synchronized void print1(){
        System.out.println(Thread.currentThread().getName() + ">hello!");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 异步执行
    public void print2(){
        System.out.println(Thread.currentThread().getName()+">hello!");
    }

    public static void main(String[] args) {
        final DemoThread03 thread = new DemoThread03();
        Thread t0 = new Thread(new Runnable() {
            public void run() {
                thread.print1();
            }
        }, "Thread0");
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                thread.print2();
            }
        }, "Thread1");
        t0.start();
        t1.start();
    }
}

对象锁只针对synchronized修饰的方法生效、对象中所有synchronized方法都会同步执行 ,非synchronized方法异步执行

因此上面,没有等待三秒,t1线程直接运行了线程里的打印语句

 

脏读

由于同步和异步方法的执行个性,如果不从全局上进行并发设计很可能引起数据的不一致,也就是所谓脏读。

多个线程访问同一个资源,在一个线程修改数据的过程中,有另外的线程来读取数据,就好引起脏读:

    private String name = "张三";
    private String address = "大兴";

    public synchronized void setVal(String name, String address){
        this.name = name;
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.address = address;
        System.out.println("setVal最终结果:username = " + name + " ,address = " + address);
    }

    public void getVal(){
        System.out.println("getVal方法得到:username = " + name + " ,address = " + address);
    }

    public static void main(String[] args) throws InterruptedException {
        final DemoThread04 dr = new DemoThread04();
        Thread t0 = new Thread(new Runnable() {
            public void run() {
                dr.setVal("李四", "昌平");
            }
        });
        t0.start();
        Thread.sleep(1000);
        dr.getVal();
    }
}

setVal加锁了,但是getVal没有加锁,t0线程执行,修改name后会休眠两秒,而主线程休眠一秒后就调用了getVal()方法,此时只改了name,address还没改,产生了脏读。

结果

getVal方法得到:username = 李四 ,address = 大兴
setVal最终结果:username = 李四 ,address = 昌平

为了避免脏读,我们一定要保证数据修改操作的原子性,并对读取操作也要进行同步控制。

即不能让其他线程写,也不能让其他线程读,将getVal()也加上synchronized关键字即可。

结果为

setVal最终结果:username = 李四 ,address = 昌平
getVal方法得到:username = 李四 ,address = 昌平

 

 

synchronized锁重入

同一个线程得到了一个对象的锁之后,再次请求此对象时可以再次获得该对象的锁。同一个对象内的多个synchronized方法可以锁重入。

package com.aidata.concurrency;

public class DemoThread05 {

    public synchronized void run1(){
        System.out.println(Thread.currentThread().getName() + ">run1...");
        // 调用同类中的synchronized方法不会引起死锁
        run2();
    }

    public synchronized void run2(){
        System.out.println(Thread.currentThread().getName() + ">run2...");
    }

    public static void main(String[] args) {
        final DemoThread05 demoThread05 = new DemoThread05();
        Thread thread = new Thread(new Runnable() {
            public void run() {
                demoThread05.run1();
            }
        });
        thread.start();
    }
}

结果

Thread-0>run1...
Thread-0>run2...

父子锁可以重入

package com.aidata.concurrency;

public class DemoThread06 {
    public static void main(String[] args) {
        Thread t0 = new Thread(new Runnable() {
            public void run() {
                Child sub = new Child();
                sub.runChild();
            }
        });
        t0.start();
    }
}

class Parent{
    public int i = 10;
    public synchronized void runParent(){
        try {
            i--;
            System.out.println("Parent>>>i=" + i);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Child extends Parent{
    public synchronized void runChild(){
        try{
            while (i > 0){
                i--;
                System.out.println("Child>>>i=" + i);
                Thread.sleep(100);
                // 调用父类的synchronized方法不会引起死锁
                this.runParent();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

结果

Child>>>i=9
Parent>>>i=8
Child>>>i=7
Parent>>>i=6
Child>>>i=5
Parent>>>i=4
Child>>>i=3
Parent>>>i=2
Child>>>i=1
Parent>>>i=0

 

 

 

抛出异常释放锁

一个线程在获得锁之后执行操作,发送错误抛出异常,则自动释放锁。

while死循环,我们想在第十次释放锁:

package com.aidata.concurrency;

public class DemoThread07 {

    private int i = 0;

    public synchronized void run(){
        while (true){
            i++;
            System.out.println(Thread.currentThread().getName() + "-run>i=" +i);

            try {
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            if (i == 10){
                throw new RuntimeException();
            }
        }
    }

    public synchronized void get(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "-get>i=" +i);
    }

    public static void main(String[] args) throws InterruptedException {
        final DemoThread07 demoThread07 = new DemoThread07();
        new Thread(new Runnable() {
            public void run() {
                demoThread07.run();
            }
        }, "t0").start();

        // 保证t1线程先执行
        Thread.sleep(1000);

        new Thread(new Runnable() {
            public void run() {
                demoThread07.get();
            }
        }, "t1").start();
    }
}

结果

t0-run>i=1
t0-run>i=2
t0-run>i=3
t0-run>i=4
t0-run>i=5
t0-run>i=6
t0-run>i=7
t0-run>i=8
t0-run>i=9
t0-run>i=10
Exception in thread "t0" java.lang.RuntimeException
    at com.aidata.concurrency.DemoThread07.run(DemoThread07.java:18)
    at com.aidata.concurrency.DemoThread07$1.run(DemoThread07.java:36)
    at java.lang.Thread.run(Thread.java:748)
t1-get>i=10

总结:

1.可以利用抛出异常,主动释放锁

2.程序异常时防止资源被死锁、无法释放

3.异常释放锁可能导致数据不一致

 

 

Synchronized代码块和锁失效问题

synchronized代码块

可以达到更细粒度的控制

  • 当前对象锁
  • 类锁
  • 任意锁
package com.aidata.concurrency;

public class DemoThread08 {

    public void run1(){
        synchronized (this){
            try {
                System.out.println(Thread.currentThread().getName() + ">当前对象锁...");
                Thread.sleep(2000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public void run2(){
        synchronized (DemoThread08.class){
            try {
                System.out.println(Thread.currentThread().getName() + ">类锁...");
                Thread.sleep(2000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    private Object objectLock = new Object();
    public void run3(){
        synchronized (objectLock){
            try {
                System.out.println(Thread.currentThread().getName() + ">任意锁...");
                Thread.sleep(2000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    // 测试方法
    public static void test(final int type){
        if (type == 1){
            System.out.println("当前对象锁测试...");
        }else if (type == 2){
            System.out.println("类锁测试...");
        }else {
            System.out.println("任意对象锁测试...");
        }
        final DemoThread08 demo1 = new DemoThread08();
        final DemoThread08 demo2 = new DemoThread08();
        Thread t0 = new Thread(new Runnable() {
            public void run() {
                if (type == 1){
                    demo1.run1();
                }else if (type == 2){
                    demo1.run2();
                }else {
                    demo1.run3();
                }
            }
        }, "t0");
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                if (type == 1){
                    demo2.run1();
                }else if (type == 2){
                    demo2.run2();
                }else {
                    demo2.run3();
                }
            }
        }, "t1");
        t0.start();
        t1.start();
    }

    public static void main(String[] args) {
       test(1);
//        test(2);
//        test(3);

    }
}

同类型锁之间互斥,不同类型的锁之间互不干扰

package com.aidata.concurrency;

public class DemoThread08 {

    public void run1(){
        synchronized (this){
            try {
                System.out.println(Thread.currentThread().getName() + ">当前对象锁...");
                Thread.sleep(2000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public void run2(){
        synchronized (DemoThread08.class){
            try {
                System.out.println(Thread.currentThread().getName() + ">类锁...");
                Thread.sleep(2000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    private Object objectLock = new Object();
    public void run3(){
        synchronized (objectLock){
            try {
                System.out.println(Thread.currentThread().getName() + ">任意锁...");
                Thread.sleep(2000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }public static void main(String[] args) {

        final DemoThread08 demo1 = new DemoThread08();
        final DemoThread08 demo2 = new DemoThread08();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                demo1.run2();
            }
        }, "t1");
        t1.start();

        // 保证t1先运行
        try {
            Thread.sleep(100);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                demo2.run1();
            }
        }, "t2");
        t2.start();

    }
}

没有等待,瞬间完成,也就是不同类型的锁之间互不干扰

t1>类锁...
t2>当前对象锁...

不要在线程内修改对象锁的引用

引用被改变会导致锁失效

package com.aidata.concurrency;

public class DemoThread09 {

    private String lock = "lock handler";

    private void method(){
        synchronized (lock){
            try {
                System.out.println("当前线程:" + Thread.currentThread().getName() + "开始");
                // 锁的引用被改变,则其他线程可获得锁,导致并发问题
                lock = "change lock handler";
                Thread.sleep(2000);
                System.out.println("当前线程:" + Thread.currentThread().getName() + "结束");
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        final DemoThread09 changeLock = new DemoThread09();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                changeLock.method();
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                changeLock.method();
            }
        }, "t2");
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 由于锁的引用被改变,所有t2线程也进入到method方法内执行
        t2.start();
    }
}

结果线程不安全:

当前线程:t1开始
当前线程:t2开始
当前线程:t1结束
当前线程:t2结束

线程A修改了对象锁的引用,则线程B实际得到了新的对象锁,而不是锁被释放了,不是同一个锁了,因此引发了线程安全问题

在线程中修改了锁对象的属性,而不修改引用则不会引起锁失效,不会产生线程安全问题

 

 

并发与死锁

指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在相关等待的进程称为死锁进程。

package com.aidata.concurrency;

import org.omg.Messaging.SYNC_WITH_TRANSPORT;

public class DemoThread10 {
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void execute1(){
        // 尝试获得lock1的锁
        synchronized (lock1){
            System.out.println("线程" + Thread.currentThread().getName() + " 获得lock1执行execute1开始");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 尝试或得lock2锁
            synchronized (lock2){
                System.out.println("线程" + Thread.currentThread().getName() + " 获得lock2执行execute1开始");
            }
        }
    }

    public void execute2(){
        synchronized (lock2){
            System.out.println("线程" + Thread.currentThread().getName() + " 获得lock2执行execute2开始");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock1){
                System.out.println("线程" + Thread.currentThread().getName() + " 获得lock1执行execute2开始");
            }
        }
    }

    public static void main(String[] args) {

        final DemoThread10 demo = new DemoThread10();
        new Thread(new Runnable() {
            public void run() {
                demo.execute1();
            }
        }, "t1").start();

        new Thread(new Runnable() {
            public void run() {
                demo.execute2();
            }
        }, "t2").start();
    }
}

死锁,结果

线程t1 获得lock1执行execute1开始
线程t2 获得lock2执行execute2开始

线程t1在等lock2锁,线程2在等lock1锁

 

 

线程之间通讯

每个线程都是独立运行的个体,线程通讯能让多个线程之间协同工作

package com.aidata.concurrency;

import java.util.ArrayList;
import java.util.List;

// while方式
public class DemoThread11 {

    private volatile List<String> list = new ArrayList<String>();
    private volatile boolean canGet = false;

    public void put(){
        for (int i=0; i<10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            list.add("A");
            System.out.println("线程" + Thread.currentThread().getName() + "添加第" + i + "个元素");
            if (i==5){
                // 循环到第i次则通知其他线程开始获取数据进行处理
                canGet = true;
                System.out.println("线程" + Thread.currentThread().getName() + " 发出通知");
            }
        }
    }

    public void get(){
        while (true){
            if (canGet){
                for (String s: list){
                    System.out.println("线程" + Thread.currentThread().getName() + "获取元素:" + s);
                }
                break;
            }
        }
    }

    public static void main(String[] args) {
        final DemoThread11 demo = new DemoThread11();

        new Thread(new Runnable() {
            public void run() {
                demo.put();
            }
        }, "t1").start();

        new Thread(new Runnable() {
            public void run() {
                demo.get();
            }
        }, "t2").start();
    }

}

结果

线程t1添加第0个元素
线程t1添加第1个元素
线程t1添加第2个元素
线程t1添加第3个元素
线程t1添加第4个元素
线程t1添加第5个元素
线程t1 发出通知
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t1添加第6个元素
线程t1添加第7个元素
线程t1添加第8个元素
线程t1添加第9个元素

 

Object类中的wait/notify方法可以实现线程间通讯

wait/notify必须与synchronized一通使用

wait释放锁,notify不释放锁

 锁池和等待池

锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中
Reference:java中的锁池和等待池
然后再来说notify和notifyAll的区别

如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

综上,所谓唤醒线程,另一种解释可以说是将线程由等待池移动到锁池,notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify只会唤醒一个线程。

有了这些理论基础,后面的notify可能会导致死锁,而notifyAll则不会的例子也就好解释了

package com.aidata.concurrency;

import java.util.ArrayList;
import java.util.List;

// wait/notify方式
public class DemoThread12 {

    private volatile List<String> list = new ArrayList<String>();
    private Object lock = new Object();

    public void put(){
        synchronized (lock){
            for (int i=0; i<10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("A");
                System.out.println("线程" + Thread.currentThread().getName() + "添加第" + i + "个元素");
                if (list.size()==5){
                    // 数据准备好了,发出唤醒通知,但是不释放锁
                    lock.notify();
                    System.out.println("线程" + Thread.currentThread().getName() + " 发出通知");
                }
            }
        }
    }

    public void get(){
        synchronized (lock){
            try {
                System.out.println("线程" + Thread.currentThread().getName() + "业务处理,发现需要的数据没准备好,则发起等待");
                System.out.println("线程" + Thread.currentThread().getName() + " wait");
                // wait操作释放锁,否则其他线程无法进入put方法
                lock.wait();
                System.out.println("线程" + Thread.currentThread().getName() + "被唤醒");
                for (String s: list){
                    System.out.println("线程" + Thread.currentThread().getName() + "获取元素:" + s);
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {
        final DemoThread12 demo = new DemoThread12();

        new Thread(new Runnable() {
            public void run() {
                demo.get();
            }
        }, "t1").start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Runnable() {
            public void run() {
                demo.put();
            }
        }, "t2").start();

    }
}

结果:

线程t1业务处理,发现需要的数据没准备好,则发起等待
线程t1 wait
线程t2添加第0个元素
线程t2添加第1个元素
线程t2添加第2个元素
线程t2添加第3个元素
线程t2添加第4个元素
线程t2 发出通知
线程t2添加第5个元素
线程t2添加第6个元素
线程t2添加第7个元素
线程t2添加第8个元素
线程t2添加第9个元素
线程t1被唤醒
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A

上面的高亮可知,wait释放锁给t2,notify不释放锁,t2继续添加元素

notify只会通知一个wait中的线程,并把锁给他,不会产生锁竞争问题,但是该线程处理完毕后必须再次notify或notifyAll,完成类似链式操作。

notifyAll会通知所有wait中的线程,会产生锁竞争问题。

package com.aidata.concurrency;

public class DemoThread13 {

    public synchronized void run1(){
        System.out.println("进入run1方法...");
        // this.notifyAll();
        this.notify();
        System.out.println("run1执行完毕,通知完毕...");
    }

    public synchronized void run2(){
        try {
            System.out.println("进入run2方法...");
            this.wait();
            System.out.println("run2执行完毕,通知完毕...");
            this.notify();
            System.out.println("run2发出通知...");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public synchronized void run3(){
        try {
            System.out.println("进入run3方法...");
            this.wait();
            System.out.println("run3执行完毕,通知完毕...");
            this.notify();
            System.out.println("run3发出通知...");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final DemoThread13 demo = new DemoThread13();

        new Thread(new Runnable() {
            public void run() {
                demo.run2();
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                demo.run3();
            }
        }).start();

        Thread.sleep(1000);

        new Thread(new Runnable() {
            public void run() {
                demo.run1();
            }
        }).start();
    }
}

结果

进入run2方法...
进入run3方法...
进入run1方法...
run1执行完毕,通知完毕...
run2执行完毕,通知完毕...
run2发出通知...
run3执行完毕,通知完毕...
run3发出通知...

notify只能通知一个,必须链式的顺序通知,t2通知t3,t3通知t1

打开t1的notifyAll,关闭t2、t3的notify

package com.aidata.concurrency;

public class DemoThread13 {

    public synchronized void run1(){
        System.out.println("进入run1方法...");
         this.notifyAll();
        System.out.println("run1执行完毕,通知完毕...");
    }

    public synchronized void run2(){
        try {
            System.out.println("进入run2方法...");
            this.wait();
            System.out.println("run2执行完毕,通知完毕...");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public synchronized void run3(){
        try {
            System.out.println("进入run3方法...");
            this.wait();
            System.out.println("run3执行完毕,通知完毕...");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final DemoThread13 demo = new DemoThread13();

        new Thread(new Runnable() {
            public void run() {
                demo.run2();
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                demo.run3();
            }
        }).start();

        Thread.sleep(1000);

        new Thread(new Runnable() {
            public void run() {
                demo.run1();
            }
        }).start();
    }
}

notifyAll可以通知所有线程,会有锁竞争

进入run2方法...
进入run3方法...
进入run1方法...
run1执行完毕,通知完毕...
run3执行完毕,通知完毕...
run2执行完毕,通知完毕...

Object中的wait

/**
     * Causes the current thread to wait until either another thread invokes the
     * {@link java.lang.Object#notify()} method or the
     * {@link java.lang.Object#notifyAll()} method for this object, or a
     * specified amount of time has elapsed.
     * <p>
     * The current thread must own this object's monitor.
     * <p>
     * This method causes the current thread (call it <var>T</var>) to
     * place itself in the wait set for this object and then to relinquish
     * any and all synchronization claims on this object. Thread <var>T</var>
     * becomes disabled for thread scheduling purposes and lies dormant
     * until one of four things happens:
     * <ul>
     * <li>Some other thread invokes the {@code notify} method for this
     * object and thread <var>T</var> happens to be arbitrarily chosen as
     * the thread to be awakened.
     * <li>Some other thread invokes the {@code notifyAll} method for this
     * object.
     * <li>Some other thread {@linkplain Thread#interrupt() interrupts}
     * thread <var>T</var>.
     * <li>The specified amount of real time has elapsed, more or less.  If
     * {@code timeout} is zero, however, then real time is not taken into
     * consideration and the thread simply waits until notified.
     * </ul>
     * The thread <var>T</var> is then removed from the wait set for this
     * object and re-enabled for thread scheduling. It then competes in the
     * usual manner with other threads for the right to synchronize on the
     * object; once it has gained control of the object, all its
     * synchronization claims on the object are restored to the status quo
     * ante - that is, to the situation as of the time that the {@code wait}
     * method was invoked. Thread <var>T</var> then returns from the
     * invocation of the {@code wait} method. Thus, on return from the
     * {@code wait} method, the synchronization state of the object and of
     * thread {@code T} is exactly as it was when the {@code wait} method
     * was invoked.
     * <p>
     * A thread can also wake up without being notified, interrupted, or
     * timing out, a so-called <i>spurious wakeup</i>.  While this will rarely
     * occur in practice, applications must guard against it by testing for
     * the condition that should have caused the thread to be awakened, and
     * continuing to wait if the condition is not satisfied.  In other words,
     * waits should always occur in loops, like this one:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait(timeout);
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     * (For more information on this topic, see Section 3.2.3 in Doug Lea's
     * "Concurrent Programming in Java (Second Edition)" (Addison-Wesley,
     * 2000), or Item 50 in Joshua Bloch's "Effective Java Programming
     * Language Guide" (Addison-Wesley, 2001).
     *
     * <p>If the current thread is {@linkplain java.lang.Thread#interrupt()
     * interrupted} by any thread before or while it is waiting, then an
     * {@code InterruptedException} is thrown.  This exception is not
     * thrown until the lock status of this object has been restored as
     * described above.
     *
     * <p>
     * Note that the {@code wait} method, as it places the current thread
     * into the wait set for this object, unlocks only this object; any
     * other objects on which the current thread may be synchronized remain
     * locked while the thread waits.
     * <p>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor.
     *
     * @param      timeout   the maximum time to wait in milliseconds.
     * @throws  IllegalArgumentException      if the value of timeout is
     *               negative.
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of the object's monitor.
     * @throws  InterruptedException if any thread interrupted the
     *             current thread before or while the current thread
     *             was waiting for a notification.  The <i>interrupted
     *             status</i> of the current thread is cleared when
     *             this exception is thrown.
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     */
    public final native void wait(long timeout) throws InterruptedException;

    /**
     * Causes the current thread to wait until another thread invokes the
     * {@link java.lang.Object#notify()} method or the
     * {@link java.lang.Object#notifyAll()} method for this object, or
     * some other thread interrupts the current thread, or a certain
     * amount of real time has elapsed.
     * <p>
     * This method is similar to the {@code wait} method of one
     * argument, but it allows finer control over the amount of time to
     * wait for a notification before giving up. The amount of real time,
     * measured in nanoseconds, is given by:
     * <blockquote>
     * <pre>
     * 1000000*timeout+nanos</pre></blockquote>
     * <p>
     * In all other respects, this method does the same thing as the
     * method {@link #wait(long)} of one argument. In particular,
     * {@code wait(0, 0)} means the same thing as {@code wait(0)}.
     * <p>
     * The current thread must own this object's monitor. The thread
     * releases ownership of this monitor and waits until either of the
     * following two conditions has occurred:
     * <ul>
     * <li>Another thread notifies threads waiting on this object's monitor
     *     to wake up either through a call to the {@code notify} method
     *     or the {@code notifyAll} method.
     * <li>The timeout period, specified by {@code timeout}
     *     milliseconds plus {@code nanos} nanoseconds arguments, has
     *     elapsed.
     * </ul>
     * <p>
     * The thread then waits until it can re-obtain ownership of the
     * monitor and resumes execution.
     * <p>
     * As in the one argument version, interrupts and spurious wakeups are
     * possible, and this method should always be used in a loop:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait(timeout, nanos);
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor.
     *
     * @param      timeout   the maximum time to wait in milliseconds.
     * @param      nanos      additional time, in nanoseconds range
     *                       0-999999.
     * @throws  IllegalArgumentException      if the value of timeout is
     *                      negative or the value of nanos is
     *                      not in the range 0-999999.
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of this object's monitor.
     * @throws  InterruptedException if any thread interrupted the
     *             current thread before or while the current thread
     *             was waiting for a notification.  The <i>interrupted
     *             status</i> of the current thread is cleared when
     *             this exception is thrown.
     */
    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }

    /**
     * Causes the current thread to wait until another thread invokes the
     * {@link java.lang.Object#notify()} method or the
     * {@link java.lang.Object#notifyAll()} method for this object.
     * In other words, this method behaves exactly as if it simply
     * performs the call {@code wait(0)}.
     * <p>
     * The current thread must own this object's monitor. The thread
     * releases ownership of this monitor and waits until another thread
     * notifies threads waiting on this object's monitor to wake up
     * either through a call to the {@code notify} method or the
     * {@code notifyAll} method. The thread then waits until it can
     * re-obtain ownership of the monitor and resumes execution.
     * <p>
     * As in the one argument version, interrupts and spurious wakeups are
     * possible, and this method should always be used in a loop:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait();
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor.
     *
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of the object's monitor.
     * @throws  InterruptedException if any thread interrupted the
     *             current thread before or while the current thread
     *             was waiting for a notification.  The <i>interrupted
     *             status</i> of the current thread is cleared when
     *             this exception is thrown.
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     */
    public final void wait() throws InterruptedException {
        wait(0);
    }

 

 

阻塞式线程安全队列

package com.aidata.concurrency;

import java.util.ArrayList;
import java.util.List;

class MQueue {

    private List<String> list = new ArrayList<String>();

    private  int maxSize;

    private  Object lock = new Object();

    public MQueue(int maxSize){
        this.maxSize = maxSize;
        System.out.println("线程" + Thread.currentThread().getName() + "已初始化长度为" + this.maxSize + "队列");
    }

    public void put(String element){
        synchronized (lock){
            if (this.list.size() == this.maxSize){
                try {
                    System.out.println("线程" + Thread.currentThread().getName() + "当前队列已满put等待...");
                    lock.wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            this.list.add(element);
            System.out.println("线程"+Thread.currentThread().getName()+"向队列中加入元素:"+element);
            lock.notify(); // 通知可以取数据
        }
    }

    public String take(){
        synchronized (lock){
            if (this.list.size()==0){
                try {
                    System.out.println("线程"+Thread.currentThread().getName()+"队列为空take等待...");
                    // 空,需要wait
                    lock.wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            String result = list.get(0);
            list.remove(0);
            System.out.println("线程"+Thread.currentThread().getName()+"获取数据:"+result);
            lock.notify(); // 通知可以加入线程
            return result;
        }
    }
}

public class ThreadDemo13 {
    public static void main(String[] args) {
        final MQueue q = new MQueue(5);

        new Thread(new Runnable() {
            public void run() {
                q.put("1");
                q.put("2");
                q.put("3");
                q.put("4");
                q.put("5");
                q.put("6");
            }
        }, "t1").start();

        new Thread(new Runnable() {
            public void run() {
                q.put("11");
                q.put("21");
                q.put("31");
                q.put("41");
                q.put("51");
                q.put("61");
            }
        }, "t2").start();

        new Thread(new Runnable() {
            public void run() {
                q.take();
                q.take();
                q.take();
                q.take();
                q.take();
            }
        }, "t3").start();

        new Thread(new Runnable() {
            public void run() {
                q.take();
                q.take();
                q.take();
                q.take();
                q.take();
            }
        }, "t4").start();
    }
}

结果:

线程main已初始化长度为5队列
线程t1向队列中加入元素:1
线程t1向队列中加入元素:2
线程t1向队列中加入元素:3
线程t1向队列中加入元素:4
线程t1向队列中加入元素:5
线程t1当前队列已满put等待...
线程t2当前队列已满put等待...
线程t3获取数据:1
线程t3获取数据:2
线程t3获取数据:3
线程t3获取数据:4
线程t3获取数据:5
线程t1向队列中加入元素:6
线程t2向队列中加入元素:11
线程t2向队列中加入元素:21
线程t2向队列中加入元素:31
线程t2向队列中加入元素:41
线程t2当前队列已满put等待...
线程t4获取数据:6
线程t4获取数据:11
线程t4获取数据:21
线程t4获取数据:31
线程t4获取数据:41
线程t2向队列中加入元素:51
线程t2向队列中加入元素:61

 

守护线程和用户线程

线程分类:daemon线程和user线程

main函数所在线程就是一个用户线程

只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。

  • 最后一个user线程结束时,JVM会正常退出,不管是否有守护线程正在运行。反过来说,只要有一个用户线程还没结束,JVM进程就不会结束。
  • 父线程结束后,子线程还可以继续存活,子线程的生命周期不受父线程影响。
package com.aidata.concurrency;

public class DaemonAndUserThreadDemo {

    public static void main(String[] args) {

        Thread thread = new Thread(new Runnable() {
            public void run() {
                while (true){}
            }
        });

        thread.start();
        // 输出线程是否为守护线程
        System.out.println(thread.getName() + " is daemon" + thread.isDaemon());
        System.out.println(Thread.currentThread().getName() + " is daemon? " + Thread.currentThread().isDaemon());
        System.out.println("main is over");
    }
}

即使父线程结束,不会运行结束,子线程继续存活:

 

 

设为守护线程

package com.aidata.concurrency;

public class DaemonAndUserThreadDemo {

    public static void main(String[] args) {

        Thread thread = new Thread(new Runnable() {
            public void run() {
                while (true){}
            }
        });

        thread.setDaemon(true);
        thread.start();
        // 输出线程是否为守护线程
        System.out.println(thread.getName() + " is daemon" + thread.isDaemon());
        System.out.println(Thread.currentThread().getName() + " is daemon? " + Thread.currentThread().isDaemon());
        System.out.println("main is over");
    }
}

结果:

Thread-0 is daemontrue
main is daemon? false
main is over

 

线程上下文切换

当前线程使用完时间片后会进入就绪状态,让出CPU执行权给其他线程,此时就是从当前线程的上下文切换到了其他线程。

当发生上下文切换的时候需要保存执行现场,等待下次执行时进行恢复。所以,频繁大量上下文切换会造成一定资源开销。

 

 

二、进阶篇

  •  volatile
  • Actomic
  • ThreadLocal
  • 同步类容器
  • 并发类容器
  • 并发无阻塞式队列
  • 并发阻塞式队列

 

volatile关键字

作用:强制线程到共享内存中读取数据,而不从线程工作内存中读取数据,从而使变量在多个线程间可见。

 

public class Test1 {

    private List<String> list = new ArrayList<String>();
    private boolean canGet = false;

    public void put(){
        for (int i=0;i<10;i++){
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            list.add("A");
            System.out.println("线程"+Thread.currentThread().getName()+"添加第"+i+"个元素");
            if (i==5){
                canGet = true;
                System.out.println("线程"+Thread.currentThread().getName()+"发出通知");
            }
        }
    }

    public void get(){
        while (true){
            if (canGet){
                for (String s: list){
                    System.out.println("线程"+Thread.currentThread().getName()+"获取元素:"+s);
                }
                break;
            }
        }
    }

    public static void main(String[] args) {
        final Test1 demo = new Test1();

        new Thread(new Runnable() {
            public void run() {
                demo.put();
            }
        }, "t1").start();

        new Thread(new Runnable() {
            public void run() {
                demo.get();
            }
        }, "t2").start();
    }

}

结果

线程t1添加第0个元素
线程t1添加第1个元素
线程t1添加第2个元素
线程t1添加第3个元素
线程t1添加第4个元素
线程t1添加第5个元素
线程t1发出通知
线程t1添加第6个元素
线程t1添加第7个元素
线程t1添加第8个元素
线程t1添加第9个元素

程序会一直在运行

 

使用volatile关键字

public class Test1 {

    private List<String> list = new ArrayList<String>();
    private volatile boolean canGet = false;

    public void put(){
        for (int i=0;i<10;i++){
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            list.add("A");
            System.out.println("线程"+Thread.currentThread().getName()+"添加第"+i+"个元素");
            if (i==5){
                canGet = true;
                System.out.println("线程"+Thread.currentThread().getName()+"发出通知");
            }
        }
    }

    public void get(){
        while (true){
            if (canGet){
                for (String s: list){
                    System.out.println("线程"+Thread.currentThread().getName()+"获取元素:"+s);
                }
                break;
            }
        }
    }

    public static void main(String[] args) {
        final Test1 demo = new Test1();

        new Thread(new Runnable() {
            public void run() {
                demo.put();
            }
        }, "t1").start();

        new Thread(new Runnable() {
            public void run() {
                demo.get();
            }
        }, "t2").start();
    }

}

结果

线程t1添加第0个元素
线程t1添加第1个元素
线程t1添加第2个元素
线程t1添加第3个元素
线程t1添加第4个元素
线程t1添加第5个元素
线程t1发出通知
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t1添加第6个元素
线程t1添加第7个元素
线程t1添加第8个元素
线程t1添加第9个元素

 

不使用volatile关键字,各个线程都使用的其工作内存 信息,因此get线程中的canGet会一直是false。

使用了volatile关键字,各个线程都从共享内存中获取信息,但put修改了共享内存中的canGet后,put线程开源马上探测到。

 

无法保证原子性,volatile属于轻量级同步,性能比synchronized强很多(不加锁),但是只保证线程见的可见性,并不能替代synchronized的同步功能,netty框架中大量使用了volatile。

public class Test1 implements Runnable{

    public static volatile int sum = 0;

    // 简单的将sum加10000次
    public static void add(){
        System.out.println(Thread.currentThread().getName()+"初始sum="+sum);
        for(int i=0;i<10000;i++){
            sum++;
        }
        System.out.println(Thread.currentThread().getName()+"计算后sum="+sum);
    }

    public void run() {
        add();
    }

    public static void main(String[] args) {
        //如果volatile具有原子性,那么10个线程并发调用,最终结果应该为100000
        ExecutorService es = Executors.newFixedThreadPool(10);
        for(int i=0;i<10;i++){
            es.submit(new Test1());
        }
        es.shutdown();
        while(true){
            if(es.isTerminated()){
                System.out.println("sum最终="+sum);
                if(sum==100000){
                    System.out.println(sum+"=ok");
                }else{
                    System.out.println(sum+"=no");
                }
                break;
            }
        }
    }

}

结果

pool-1-thread-1初始sum=0
pool-1-thread-1计算后sum=10000
pool-1-thread-2初始sum=10000
pool-1-thread-3初始sum=10239
pool-1-thread-2计算后sum=21682
pool-1-thread-3计算后sum=24318
pool-1-thread-4初始sum=24318
pool-1-thread-4计算后sum=34318
pool-1-thread-5初始sum=34318
pool-1-thread-5计算后sum=44318
pool-1-thread-6初始sum=44318
pool-1-thread-6计算后sum=54318
pool-1-thread-7初始sum=54318
pool-1-thread-7计算后sum=64318
pool-1-thread-8初始sum=64318
pool-1-thread-8计算后sum=74318
pool-1-thread-9初始sum=74318
pool-1-thread-9计算后sum=84318
pool-1-thread-10初始sum=84318
pool-1-thread-10计算后sum=94318
sum最终=94318
94318=no

多个线程同时操作了sum,如两个线程当sum是3的时候同时加1,两次相加结果都是4

 

Static保证唯一性, 不保证一致性,多个实例共享一个静态变量。

Volatile保证一致性,不保证唯一性,多个实例有多个volatile变量。

 

 

Atomic类的原子性

使用AtomicIntege等原子类可以保证共享变量的原子性

public class Test1 implements Runnable{

    public static AtomicInteger sum = new AtomicInteger(0);

    // 简单的将sum加10000次
    public static void add(){
        System.out.println(Thread.currentThread().getName()+"初始sum="+sum);
        for(int i=0;i<10000;i++){
            sum.addAndGet(1);
        }
        System.out.println(Thread.currentThread().getName()+"计算后sum="+sum);
    }

    public void run() {
        add();
    }

    public static void main(String[] args) {
        //如果volatile具有原子性,那么10个线程并发调用,最终结果应该为100000
        ExecutorService es = Executors.newFixedThreadPool(10);
        for(int i=0;i<10;i++){
            es.submit(new Test1());
        }
        es.shutdown();
        while(true){
            if(es.isTerminated()){
                System.out.println("sum最终="+sum);
                if(sum.get()==100000){
                    System.out.println(sum+"=ok");
                }else{
                    System.out.println(sum+"=no");
                }
                break;
            }
        }
    }

}

结果

pool-1-thread-1初始sum=0
pool-1-thread-1计算后sum=10000
pool-1-thread-2初始sum=10000
pool-1-thread-2计算后sum=20000
pool-1-thread-3初始sum=20000
pool-1-thread-3计算后sum=30000
pool-1-thread-4初始sum=30000
pool-1-thread-4计算后sum=40000
pool-1-thread-5初始sum=40000
pool-1-thread-5计算后sum=50000
pool-1-thread-6初始sum=50000
pool-1-thread-6计算后sum=60000
pool-1-thread-7初始sum=60000
pool-1-thread-7计算后sum=70000
pool-1-thread-8初始sum=70000
pool-1-thread-8计算后sum=80000
pool-1-thread-9初始sum=80000
pool-1-thread-9计算后sum=90000
pool-1-thread-10初始sum=90000
pool-1-thread-10计算后sum=100000
sum最终=100000
100000=ok

 

只能保证成员变量,Atomic类不能保证成员方法的原子性

public class Test1 implements Runnable{

    //原子类
    private static AtomicInteger sum = new AtomicInteger(0);

    //如果add方法是原子性的,那么每次的结果都是10的整数倍
    /*synchronized*/
    public static void add(){
        sum.addAndGet(1);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        sum.addAndGet(9);
        System.out.println(sum);
    }

    public void run() {
        add();
    }

    public static void main(String[] args) {
        //10个线程调用,每个线程得到10的倍数, 最终结果应该为100,才是正确的
        ExecutorService es = Executors.newFixedThreadPool(10);
        for(int i=0;i<10;i++){
            es.submit(new Test1());
        }
        es.shutdown();
    }

}

结果

19
28
46
37
55
64
73
82
91
100

最终结果虽然是100,但是方法执行不是10的倍数,不是原子性的

public class Test1 implements Runnable{

    //原子类
    private static AtomicInteger sum = new AtomicInteger(0);

    //如果add方法是原子性的,那么每次的结果都是10的整数倍
    /*synchronized*/
    public synchronized static void add(){
        sum.addAndGet(1);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        sum.addAndGet(9);
        System.out.println(sum);
    }

    public void run() {
        add();
    }

    public static void main(String[] args) {
        //10个线程调用,每个线程得到10的倍数, 最终结果应该为100,才是正确的
        ExecutorService es = Executors.newFixedThreadPool(10);
        for(int i=0;i<10;i++){
            es.submit(new Test1());
        }
        es.shutdown();
    }

}

结果

10
20
30
40
50
60
70
80
90
100

 

Actomic采用了CAS这种非锁机制

CAS原理

JDK提供的非阻塞原子操作,通过硬件保证了比较、更新操作的原子性

JDK的Unsafe类提供了一系列的compareAndSwap*方法来支持CAS操作

本质是乐观锁

 

 上述方法貌似解决了问题,但实际上还存在问题。

 

ABA问题

引入时间戳(版本号)

1.如果程序按照1-5的顺序执行,依然是成功的,然后线程1修改x的值时,其实已经从x=A->B->A。

2.由于变量的值产生了环形转换,从A变B又变回了A。如果不存在环形转换也就不存在ABA问题。

怎么办?

1.给变量分配时间戳、版本来解决ABA问题。

2.JDK中使用java.util.concurrent.atomic.AtomicStampedReference类给每个变量的状态都分配一个时间戳,避免ABA问题产生。 

例子

public class CaseDemo0 {
    private static AtomicStampedReference<Integer> atomic = new AtomicStampedReference<Integer>(100, 0);

    public static void main(String[] args) throws InterruptedException {
        Thread t0 = new Thread(new Runnable() {
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                    boolean success = atomic.compareAndSet(100, 101, atomic.getStamp(), atomic.getStamp() + 1);
                    System.out.println(Thread.currentThread().getName() + "set 100>101: " + success);
                    // 101改回100
                    success = atomic.compareAndSet(101, 100, atomic.getStamp(), atomic.getStamp() + 1);
                    System.out.println(Thread.currentThread().getName() + "set 101>100: " + success);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        t0.start();

        // t0会先sleep,先执行的是t1
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                try {
                    int stamp = atomic.getStamp();
                    System.out.println(Thread.currentThread().getName() + "修改之前:" + stamp);
                    // 等待两秒后,t0已经修改了时间戳
                    TimeUnit.SECONDS.sleep(2);
                    int stamp1 = atomic.getStamp();
                    System.out.println(Thread.currentThread().getName() + "等待两秒之后,版本被t0线程修改为:" + stamp1);

                    // 以下两次修改都不会成功,因为版本不符,虽然期待值是相同的,因此解决了ABA问题
                    boolean success = atomic.compareAndSet(100, 101, stamp, stamp + 1);
                    System.out.println(Thread.currentThread().getName() + "set 100>101 使用错误的时间戳:" + success);
                    success = atomic.compareAndSet(101, 100, stamp, stamp+1);
                    System.out.println(Thread.currentThread().getName() + "set 101>100 使用错误的时间戳:" + success);

                    // 以下修改是成功的,因为使用了正确的时间戳
                    success = atomic.compareAndSet(100, 101, stamp1, stamp1+1);
                    System.out.println(Thread.currentThread().getName() + "set 100>101 使用正确的时间戳:" + success);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t0.join();
        t1.join();
        System.out.println("main is over.");
    }
}

结果

Thread-1修改之前:0
Thread-0set 100>101: true
Thread-0set 101>100: true
Thread-1等待两秒之后,版本被t0线程修改为:2
Thread-1set 100>101 使用错误的时间戳:false
Thread-1set 101>100 使用错误的时间戳:false
Thread-1set 100>101 使用正确的时间戳:true
main is over.

源码

/**
 * An {@code AtomicStampedReference} maintains an object reference
 * along with an integer "stamp", that can be updated atomically.
 *
 * <p>Implementation note: This implementation maintains stamped
 * references by creating internal objects representing "boxed"
 * [reference, integer] pairs.
 *
 * @since 1.5
 * @author Doug Lea
 * @param <V> The type of object referred to by this reference
 */
public class AtomicStampedReference<V> {

    private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

    private volatile Pair<V> pair;

    /**
     * Creates a new {@code AtomicStampedReference} with the given
     * initial values.
     *
     * @param initialRef the initial reference
     * @param initialStamp the initial stamp
     */
    public AtomicStampedReference(V initialRef, int initialStamp) {
        pair = Pair.of(initialRef, initialStamp);
    }

    /**
     * Returns the current value of the reference.
     *
     * @return the current value of the reference
     */
    public V getReference() {
        return pair.reference;
    }

    /**
     * Returns the current value of the stamp.
     *
     * @return the current value of the stamp
     */
    public int getStamp() {
        return pair.stamp;
    }

    /**
     * Returns the current values of both the reference and the stamp.
     * Typical usage is {@code int[1] holder; ref = v.get(holder); }.
     *
     * @param stampHolder an array of size of at least one.  On return,
     * {@code stampholder[0]} will hold the value of the stamp.
     * @return the current value of the reference
     */
    public V get(int[] stampHolder) {
        Pair<V> pair = this.pair;
        stampHolder[0] = pair.stamp;
        return pair.reference;
    }

    /**
     * Atomically sets the value of both the reference and stamp
     * to the given update values if the
     * current reference is {@code ==} to the expected reference
     * and the current stamp is equal to the expected stamp.
     *
     * <p><a href="package-summary.html#weakCompareAndSet">May fail
     * spuriously and does not provide ordering guarantees</a>, so is
     * only rarely an appropriate alternative to {@code compareAndSet}.
     *
     * @param expectedReference the expected value of the reference
     * @param newReference the new value for the reference
     * @param expectedStamp the expected value of the stamp
     * @param newStamp the new value for the stamp
     * @return {@code true} if successful
     */
    public boolean weakCompareAndSet(V   expectedReference,
                                     V   newReference,
                                     int expectedStamp,
                                     int newStamp) {
        return compareAndSet(expectedReference, newReference,
                             expectedStamp, newStamp);
    }

    /**
     * Atomically sets the value of both the reference and stamp
     * to the given update values if the
     * current reference is {@code ==} to the expected reference
     * and the current stamp is equal to the expected stamp.
     *
     * @param expectedReference the expected value of the reference
     * @param newReference the new value for the reference
     * @param expectedStamp the expected value of the stamp
     * @param newStamp the new value for the stamp
     * @return {@code true} if successful
     */
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

    /**
     * Unconditionally sets the value of both the reference and stamp.
     *
     * @param newReference the new value for the reference
     * @param newStamp the new value for the stamp
     */
    public void set(V newReference, int newStamp) {
        Pair<V> current = pair;
        if (newReference != current.reference || newStamp != current.stamp)
            this.pair = Pair.of(newReference, newStamp);
    }

    /**
     * Atomically sets the value of the stamp to the given update value
     * if the current reference is {@code ==} to the expected
     * reference.  Any given invocation of this operation may fail
     * (return {@code false}) spuriously, but repeated invocation
     * when the current value holds the expected value and no other
     * thread is also attempting to set the value will eventually
     * succeed.
     *
     * @param expectedReference the expected value of the reference
     * @param newStamp the new value for the stamp
     * @return {@code true} if successful
     */
    public boolean attemptStamp(V expectedReference, int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            (newStamp == current.stamp ||
             casPair(current, Pair.of(expectedReference, newStamp)));
    }

    // Unsafe mechanics

    private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
    private static final long pairOffset =
        objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);

    private boolean casPair(Pair<V> cmp, Pair<V> val) {
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }

    static long objectFieldOffset(sun.misc.Unsafe UNSAFE,
                                  String field, Class<?> klazz) {
        try {
            return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
        } catch (NoSuchFieldException e) {
            // Convert Exception to corresponding Error
            NoSuchFieldError error = new NoSuchFieldError(field);
            error.initCause(e);
            throw error;
        }
    }
}
View Code

 

ThreadLocal类

使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。

 

public class DemoThread21 {
    public static void main(String[] args) throws InterruptedException {
        
        final ThreadLocal<Integer> th = new ThreadLocal<Integer>();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    th.set(100);
                    System.out.println("t1 set th="+th.get());
                    Thread.sleep(2000);
                    System.out.println("t1 get th="+th.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        Thread.sleep(1000);
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                Integer ele = th.get();
                System.out.println("t2 get th="+ele);
                th.set(200);
                System.out.println("t2 get th="+th.get());
            }
        }).start();
        
    }
}

来一个线程,创建一个副本

结果

t1 set th=100
t2 get th=null
t2 get th=200
t1 get th=100

开始t1线程先执行,把th改为100,睡眠

t2线程执行,获取th,打印,打印结果为null,说明和t1的th不一样

t2修改为200

t1输出还是100

两个线程各自有一个副本

 

源码

/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package java.lang;
import java.lang.ref.*;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 *
 * <p>For example, the class below generates unique identifiers local to each
 * thread.
 * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
 * and remains unchanged on subsequent calls.
 * <pre>
 * import java.util.concurrent.atomic.AtomicInteger;
 *
 * public class ThreadId {
 *     // Atomic integer containing the next thread ID to be assigned
 *     private static final AtomicInteger nextId = new AtomicInteger(0);
 *
 *     // Thread local variable containing each thread's ID
 *     private static final ThreadLocal&lt;Integer&gt; threadId =
 *         new ThreadLocal&lt;Integer&gt;() {
 *             &#64;Override protected Integer initialValue() {
 *                 return nextId.getAndIncrement();
 *         }
 *     };
 *
 *     // Returns the current thread's unique ID, assigning it if necessary
 *     public static int get() {
 *         return threadId.get();
 *     }
 * }
 * </pre>
 * <p>Each thread holds an implicit reference to its copy of a thread-local
 * variable as long as the thread is alive and the {@code ThreadLocal}
 * instance is accessible; after a thread goes away, all of its copies of
 * thread-local instances are subject to garbage collection (unless other
 * references to these copies exist).
 *
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */
public class ThreadLocal<T> {
    /**
     * ThreadLocals rely on per-thread linear-probe hash maps attached
     * to each thread (Thread.threadLocals and
     * inheritableThreadLocals).  The ThreadLocal objects act as keys,
     * searched via threadLocalHashCode.  This is a custom hash code
     * (useful only within ThreadLocalMaps) that eliminates collisions
     * in the common case where consecutively constructed ThreadLocals
     * are used by the same threads, while remaining well-behaved in
     * less common cases.
     */
    private final int threadLocalHashCode = nextHashCode();

    /**
     * The next hash code to be given out. Updated atomically. Starts at
     * zero.
     */
    private static AtomicInteger nextHashCode =
        new AtomicInteger();

    /**
     * The difference between successively generated hash codes - turns
     * implicit sequential thread-local IDs into near-optimally spread
     * multiplicative hash values for power-of-two-sized tables.
     */
    private static final int HASH_INCREMENT = 0x61c88647;

    /**
     * Returns the next hash code.
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    protected T initialValue() {
        return null;
    }

    /**
     * Creates a thread local variable. The initial value of the variable is
     * determined by invoking the {@code get} method on the {@code Supplier}.
     *
     * @param <S> the type of the thread local's value
     * @param supplier the supplier to be used to determine the initial value
     * @return a new thread local variable
     * @throws NullPointerException if the specified supplier is null
     * @since 1.8
     */
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

    /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    /**
     * Factory method to create map of inherited thread locals.
     * Designed to be called only from Thread constructor.
     *
     * @param  parentMap the map associated with parent thread
     * @return a map containing the parent's inheritable bindings
     */
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

    /**
     * Method childValue is visibly defined in subclass
     * InheritableThreadLocal, but is internally defined here for the
     * sake of providing createInheritedMap factory method without
     * needing to subclass the map class in InheritableThreadLocal.
     * This technique is preferable to the alternative of embedding
     * instanceof tests in methods.
     */
    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

    /**
     * An extension of ThreadLocal that obtains its initial value from
     * the specified {@code Supplier}.
     */
    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

        private final Supplier<? extends T> supplier;

        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }

        @Override
        protected T initialValue() {
            return supplier.get();
        }
    }

    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        /**
         * Construct a new map including all Inheritable ThreadLocals
         * from given parent map. Called only by createInheritedMap.
         *
         * @param parentMap the map associated with parent thread.
         */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

        /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

        /**
         * Replace a stale entry encountered during a set operation
         * with an entry for the specified key.  The value passed in
         * the value parameter is stored in the entry, whether or not
         * an entry already exists for the specified key.
         *
         * As a side effect, this method expunges all stale entries in the
         * "run" containing the stale entry.  (A run is a sequence of entries
         * between two null slots.)
         *
         * @param  key the key
         * @param  value the value to be associated with key
         * @param  staleSlot index of the first stale entry encountered while
         *         searching for key.
         */
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // Back up to check for prior stale entry in current run.
            // We clean out whole runs at a time to avoid continual
            // incremental rehashing due to garbage collector freeing
            // up refs in bunches (i.e., whenever the collector runs).
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // Find either the key or trailing null slot of run, whichever
            // occurs first
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

                // If we find key, then we need to swap it
                // with the stale entry to maintain hash table order.
                // The newly stale slot, or any other stale slot
                // encountered above it, can then be sent to expungeStaleEntry
                // to remove or rehash all of the other entries in run.
                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // Start expunge at preceding stale entry if it exists
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                // If we didn't find stale entry on backward scan, the
                // first stale entry seen while scanning for key is the
                // first still present in the run.
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // If there are any other stale entries in run, expunge them
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

        /**
         * Expunge a stale entry by rehashing any possibly colliding entries
         * lying between staleSlot and the next null slot.  This also expunges
         * any other stale entries encountered before the trailing null.  See
         * Knuth, Section 6.4
         *
         * @param staleSlot index of slot known to have null key
         * @return the index of the next null slot after staleSlot
         * (all between staleSlot and this slot will have been checked
         * for expunging).
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

        /**
         * Heuristically scan some cells looking for stale entries.
         * This is invoked when either a new element is added, or
         * another stale one has been expunged. It performs a
         * logarithmic number of scans, as a balance between no
         * scanning (fast but retains garbage) and a number of scans
         * proportional to number of elements, that would find all
         * garbage but would cause some insertions to take O(n) time.
         *
         * @param i a position known NOT to hold a stale entry. The
         * scan starts at the element after i.
         *
         * @param n scan control: {@code log2(n)} cells are scanned,
         * unless a stale entry is found, in which case
         * {@code log2(table.length)-1} additional cells are scanned.
         * When called from insertions, this parameter is the number
         * of elements, but when from replaceStaleEntry, it is the
         * table length. (Note: all this could be changed to be either
         * more or less aggressive by weighting n instead of just
         * using straight log n. But this version is simple, fast, and
         * seems to work well.)
         *
         * @return true if any stale entries have been removed.
         */
        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

        /**
         * Re-pack and/or re-size the table. First scan the entire
         * table removing stale entries. If this doesn't sufficiently
         * shrink the size of the table, double the table size.
         */
        private void rehash() {
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            if (size >= threshold - threshold / 4)
                resize();
        }

        /**
         * Double the capacity of the table.
         */
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

        /**
         * Expunge all stale entries in the table.
         */
        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }
    }
}
View Code

 

三、精通篇

 

 

 

四、Disruption高并发框架

 

 

五、RateLimiter高并发访问限流

 

posted on 2020-02-16 12:59  AI数据  阅读(303)  评论(0)    收藏  举报

导航