Java并发编程 Java内部类与final关键字详解

一、基础篇

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

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

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

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

内存与线程

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

使用JDK工具观察线程

jcmd

jstack

jvisualVM

jconsole

线程创建的三种方法

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

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就无法继承其他类了

实现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执行权给其他线程,此时就是从当前线程的上下文切换到了其他线程。

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

二、进阶篇

三、精通篇

四、Disruption高并发框架

五、RateLimiter高并发访问限流

猜你喜欢

转载自www.cnblogs.com/aidata/p/12305840.html
今日推荐