N 段代码掌握Java N 种多线程开发姿势

N 段代码掌握Java N 种多线程开发姿势

synchronized相关

  • 锁对对象独立创建 ==> 应该弄成 static类对象
class T {
    private int count =10;
    private Object o=new Object();

    public void m(){
    		//任何线程要执行下面的代码,必须先拿到o的锁	
        synchronized (o){   
            count--;
            System.out.println(Thread.currentThread().getName()+" count = "+count);
        }
    }
}
  • 锁this ==> 类实例对象
class T {
    private int count=10;
    public void m(){
        synchronized (this){
            count--;
            System.out.println(Thread.currentThread().getName()+" count = "+count);
        }
    }
}
  • 锁实例方法 == 锁实例对象
class T {
    private int count=10;
    public synchronized void m(){
        count--;
        System.out.println(Thread.currentThread().getName()+" count = "+count);
    }
}

  • 锁静态方法 ==> 锁静态类对象
class T {
    private static int count=10;

    /*修饰静态方法-*/

    public synchronized static void m(){    //这里等同于synchronized(yxxy.c_001.T.class)
        count--;
        System.out.println(Thread.currentThread().getName()+" count = "+count);
    }

    public static void mm(){
        synchronized (T.class){ //这里写synchronized(this)是否可以? ==> 不可以 类对象和实例对象不一样
            count--;
        }
    }
}
  • 多线程启动去运行一个方法 ==> 严格意义上输出结果不一定按启动顺序
class T implements Runnable {

    private int count=10;
    @Override
    public synchronized void run() {
        count--;
        System.out.println(Thread.currentThread().getName()+" count = "+count);
    }

    public static void main(String[] args) {
        T t=new T();
        for (int i = 0; i < 5; i++) {
            new Thread(t,"Thread"+i).start();
        }
    }
}
  • 验证同步方法和非同步方法是否可以同步调用 ==> 验证异步性
class T {

    /*同步方法*/
    public synchronized void m1(){
        System.out.println(Thread.currentThread().getName()+"m1 start...");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" m1 end");
    }

    /*非同步方法*/
    public void m2(){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" m2");
    }

    public static void main(String[] args) {
        T t=new T();
        new Thread(()->t.m1(),"t1").start();
        new Thread(()->t.m2(),"t2").start();

        /*
        * 等效写法
        new Thread(t::m1,"t1").start();
        new Thread(t::m2,"t2").start();*/
    }
}

t1m1 start…
t2 m2
t1 m1 end

  • 银行账户脏读场景
package c_008;

import java.util.concurrent.TimeUnit;

/**   DirtyRead
 * 对业务写方法加锁
 * 对业务读方法不加锁
 * 容易产生脏读问题(dirtyRead)
 * 写还没写完  先读过去了  就是脏读
 */
class Account {
    private String name;
    private double balance; //余额

  	//写
    public synchronized void set(String name,double balance){
        this.name=name;
      //模拟睡眠器间,读线程执行完毕,这时张三的余额初始为0
        try {
            Thread.sleep(2000);   
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance=balance;
    }
		
    //读
    public /*synchronized*/ double getBalance(String name) {
        return this.balance;
    }

    public static void main(String[] args) {
        Account a=new Account();
        //写100 进余额  需要 2s 完成
        new Thread(()->a.set("张三",100.0)).start(); 
				//睡1s
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
				//读 ==> 读到 0
        System.out.println(a.getBalance("张三"));

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
				//读到 100
        System.out.println(a.getBalance("张三")); /*读线程*/

    }
}

0.0
100.0

  • 演示 synchronized 可重入支持继承机制
import java.util.concurrent.TimeUnit;

/**
 * 一个同步方法可以调用另外一个同步方法,当然是两个同步方法锁对象一致
 * 一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该锁
 * 也就是说synchronized获得的锁是可重入的
 * 可重入锁保证继承的机制 就是即使两个锁对象 拥有继承关系也可以
 */
class T {
    //锁 t 实例对象
    synchronized void m(){
        System.out.println("m start");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("m end");
    }
    
    //主方法 调用子类的同步方法
    public static void main(String[] args) {
        new TT().m();
    }
}

//子类
class TT extends T{
    @Override
    //锁tt 实例对象
    synchronized void m(){
        System.out.println("child m start");
        //调用父类同步方法  此时会申请父类的锁对象
        super.m();  
        System.out.println("chile m end");
    }
}

  • 演示同步出现异常 ==> 证明出异常会释放锁 ==> 合理的catch
package c_011;

import java.util.concurrent.TimeUnit;

/**  同步业务异常需谨慎!
 * 程序在执行过程中,如果出现异常,默认情况锁会被释放
 * 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况
 * 比如,在一个web app处理过程中,多个servlet线程共同访问一个资源,这时如果异常处理不合适,
 * 在第一个线程抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生的数据,
 * 因此要非常小心的处理同步业务逻辑中的异常
 */
class T {
    int count=0;
    //循环对 count 做自增
    synchronized void m(){
        System.out.println(Thread.currentThread().getName()+" start");
        while (true){
            count++;
            System.out.println(Thread.currentThread().getName()+" count =  "+count);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //在 count == 5 时候 模拟出异常 程序会释放锁
            if (count == 5){
                int i=1 / 0;
            }
        }
    }

    //两个线程先后去执行 m 方法
    public static void main(String[] args) {
        T t=new T();
        Runnable r=new Runnable() {
            @Override
            public void run() {
                t.m();
            }
        };

        new Thread(r,"t1").start();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(r,"t2").start();
    }

}

t1 start
t1 count = 1
t1 count = 2
t1 count = 3
t1 count = 4
t1 count = 5
t2 start
t2 count = 6
Exception in thread “t1” java.lang.ArithmeticException: / by zero
at c_011.T.m(T.java:27)
at c_011.T$1.run(T.java:46)
at java.base/java.lang.Thread.run(Thread.java:831)
t2 count = 7
t2 count = 8

  • 锁失效
package c_017;

import java.util.concurrent.TimeUnit;

/**
 * 锁定某个对象o,如果o的属性发生改变,不影响锁的使用
 * 但如果o变成另外一个对象,则锁失效
 * 应该避免将锁定对象的引用变成另外的对象
 */
class T {
   Object o=new Object();
   void m(){
       synchronized (o){
           while (true){
               System.out.println(Thread.currentThread().getName());
               try {
                   TimeUnit.SECONDS.sleep(1);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }
   }

    public static void main(String[] args) {
        T t=new T();
        //启动第一个线程
        new Thread(t::m,"t1").start();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //创建第二个线程
        Thread t2=new Thread(t::m,"t2");
        t.o = new Object();   //锁发生改变,所以t2线程得意执行,如果注释掉这句话,线程2永远得不到执行机会
        t2.start();
    }
}

t1
t1
t1
t2 //锁失效后t2得以进入同步区代码
t2
t1
t2
t1

valiate 相关

  • valiate 使用场景
package c_012;

import java.util.concurrent.TimeUnit;

/**
 * volatile关键字,使一个变量在多个线程间可见
 * A B线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道
 * 使用volatile关键字,会让所有线程都读到该变量的修改值
 *
 * 在下面的代码中,running是存在于堆内存的t对象中
 * 当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去读取
 * 堆内存,这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行
 *
 * 使用volatile,将会强制所有线程都去堆内存读取running的值
 *
 * 可以阅读这篇文章进行更深入的理解
 * https://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html
 *
 * volatile 并不能保证多个线程共同修改runing变量时所带来的不一致问题,也就是说volatile不能替代synchronized
 */
class T {
    //对比一下有无volatile的情况下,整个程序运行结果的区别
    volatile boolean running = true;
    void m(){
        System.out.println("m start");
        while (running){
            System.out.println("m is running");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // running == false
        System.out.println("m end");
    }

    //2s 后循环判断为 false
    public static void main(String[] args) {
        T t=new T();
        new Thread(t::m,"t1").start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.running=false;
    }
}

  • valiate 无法保证最终一致性

    
    /**
     * volatile并不能保证多个线程共同修改running变量时所带来的不一致问题
     * 也就是说volatile不能替代synchronized
     * 看到了又如何 两个线程指令一交错 我看到了但我也已经改动了 除非配合 cas
     */
    class T {
        volatile int count = 0;
    
        //volatile代替 synchronized还是会有不同步问题
        void m(){
            //疯狂对count自增
            for (int i = 0; i < 1000000; i++) {
                count++;
            }
        }
    
        public static void main(String[] args) {
            T t=new T();
            //模拟线程池
            List<Thread> threads=new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                threads.add(new Thread(t::m,"thread-"+i));
            }
            threads.forEach((o)->o.start());
            //join 是一种用于主线程与子线程的同步方式
            // 子线程调用 join 替代主线程再次休眠
            //在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行
            //这里就是等所有子线程执行完主线程再执行
            threads.forEach((o)->{
                try {
                    o.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            System.out.println(t.count);
        }
    }
    

4116738 < 100000

AtomXXX类相关

  • 解决上一个 valiate 的最终一致性问题
package c_015;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 解决同样的问题的更高效的方法,使用AtomXXX类
 * AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性的
 */
class T {
    //使用原子类
    AtomicInteger count = new AtomicInteger(0);
    void m(){
        for (int i = 0; i < 10000000; i++) {
            /*if (count.get()<1000000)*/
            count.incrementAndGet();    //替代count++的
            /*如果这里出现其他方法,那么m方法不能保证原子性*/
        }
    }

    public static void main(String[] args) {
        T t=new T();
        List<Thread> threads=new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(t::m,"thread-"+i));
        }
        threads.forEach((o)->o.start());
        threads.forEach((o)->{
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(t.count);
    }
}

100000000

底层是乐观锁 https://blog.csdn.net/JunSIrhl/article/details/106858462

面试题1 观察者

/**
 * 曾经的面试题:
 * 实现一个容器,提供两个方法:add,size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
 * 分析下面这个程序,能完成这个功能吗?
 */
public class MyContainer1 {
    static List<Object> list = new ArrayList();
    public void add(Object o){
        list.add(o);
    }
    public int size(){
        return list.size();
    }

    public static void main(String[] args) {
        MyContainer1 c = new MyContainer1();
        //启动十个线程去搞
        new Thread(()->{
            /*添加10个对象*/
            for (int i = 0; i < 10; i++) {
                c.add(new Object());
                System.out.println("add " + i);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1").start();


        //观察者
        new Thread(()->{
            while (true){
                if (c.size() == 5){
                    break;
                }
            }
            System.out.println("t2结束");
        },"t2").start();
    }
}

add 1
add 2
add 3
add 4
add 5
add 6

==> t2 线程中的 size没有可见性

  • 对 list添加 valiate 后

/**
 * 曾经的面试题:
 * 实现一个容器,提供两个方法:add,size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
 * 给lists添加volatile之后,t2能够接到通知,但结果好像不准确,但是t2线程的死循环很浪费cpu,如果不用死循环怎么做呢?
 *
 * 分析下面这个程序,能完成这个功能吗?
 */
public class MyContainer2 {
    //添加volatile,使t2能够得到通知
    volatile List<Object> list = new ArrayList();

    public void add(Object o){
        list.add(o);
    }

    public int size(){
        return list.size();
    }

    public static void main(String[] args) {

        MyContainer2 c=new MyContainer2();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                c.add(new Object());
                System.out.println("add "+ i);

                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1").start();

        new Thread(()->{
            while (true){
                if (c.size() == 5){
                    break;
                }
            }
            System.out.println("t2结束");
        },"t2").start();
    }
}

add 0
add 1
add 2
add 3
add 4
t2结束
add 5
add 6

  • 不要死循环 改用通知机制

/**
 * 曾经的面试题:
 * 实现一个容器,提供两个方法:add,size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
 *
 * 给lists添加volatile之后,t2能够接到通知,但是t2线程的死循环很浪费cpu,如果不用死循环怎么做呢?
 *
 * 这里使用wait和notify可以做到,wait会释放锁,而notify不会释放锁
 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
 *
 * 阅读下面程序,并分析出结果
 * 可以读到输出结果但并不是sieze=5时退出,而是t1结束时t2才接收到通知而退出
 *
 *  * notify之后,t1必须释放锁,t2退出后也必须notify,通知t1继续执行
 *  * 整个通信过程比较繁琐
 *
 *
 */
public class MyContainer3 {
    //添加volatile,使t2能够得到通知
    volatile List<Object> list=new ArrayList();

    public void add(Object o){
        list.add(o);
    }
    public int size(){
        return list.size();
    }

    public static void main(String[] args) {

        MyContainer3 c=new MyContainer3();
        final Object lock = new Object();
        //观察者
        new Thread(()->{
            synchronized (lock){
                System.out.println("t2启动");
                if (c.size() != 5){
                    try {
                        //进入等待 释放锁 -> t1取得锁*/
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("t2结束");
            }
        },"t2").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //对容器做 add 操作
        new Thread(()->{
            //等待lock锁
            synchronized (lock){
                System.out.println("t1启动");
                for (int i = 0; i < 10; i++) {
                    c.add(new Object());
                    System.out.println("add " + i);
                    //通知观察者
                    if (c.size() == 5){
                        //对持有本锁的其他线程进行通知, notify不会当前锁释放锁 t2无法获得锁  ->*/
                        lock.notify();
                    }
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("t1结束");
            }
        },"t1").start();


    }
}

t2启动
t1启动
add 0
add 1
add 2
add 3
add 4
add 5
add 6
add 7
add 8

无效


/**
 * notify之后,t1必须释放锁,t2退出后也必须notify,通知t1继续执行
 * 整个通信过程比较繁琐
 */
public class MyContainer4 {
    //添加volatile,使t2能够得到通知
    volatile List<Object> list = new ArrayList();
    public void add(Object o){
        list.add(o);
    }
    public int size(){
        return list.size();
    }

    public static void main(String[] args) {

        MyContainer4 c = new MyContainer4();
        final Object lock=new Object();
        new Thread(()->{
            synchronized (lock){
                System.out.println("t2启动");
                if (c.size() != 5){
                    try {
                        //释放锁  进入阻塞队列
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("t2结束");
                //让t1继续执行
                lock.notify();
            }
        },"t2").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            synchronized (lock){
                System.out.println("t1启动");
                for (int i = 0; i < 10; i++) {
                    c.add(new Object());
                    System.out.println("add "+i);

                    if (c.size() == 5){
                        //通知阻塞队列
                        lock.notify();
                        //释放锁,让t2得以执行
                        try {
                            //释放锁
                            lock.wait(); 
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("t1结束");
            }
        },"t1").start();
    }
}

基于 AQS 的CountDownLatch

解决上一段代码频繁释放通知的繁琐

/**
 * 使用Latch(门闩/门栓)替代wait notify来进行通知
 * 好处是通信方式简单,同时也可以指定等待时间
 * 使用await和countdown方法替代wait和notify
 * CountDownLatch不涉及锁定,当count的值为零是当前线程继续运行
 * 当不涉及同步,只是涉及线程通信的时候,用synchronized+wait/notify就显得太重了
 * 这时应该考虑CountDownLacth/cyclicbarrier/semaphore
 *
 *
 */
public class MyContainer5 {
    //添加volatile,使t2能够得到通知
    volatile List<Object> list = new ArrayList();

    public void add(Object o){
        list.add(o);
    }
    public int size(){
        return list.size();
    }

    public static void main(String[] args) {

        MyContainer5 c=new MyContainer5();

        CountDownLatch latch = new CountDownLatch(1);

        /*监视线程*/
        new Thread(()->{
            System.out.println("t2启动");
            if (c.size() != 5){
                try {
                    //Decrements the count of the latch
                    //releasing all waiting threads if the count reaches zero.
                    latch.await();
                    //也可以指定等待时间
                    //latch.await(5000,TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t2结束");

        },"t2").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        new Thread(()->{
            System.out.println("t1启动");
            for (int i = 0; i < 10; i++) {
                c.add(new Object());
                System.out.println("add "+i);
                if (c.size() == 5){
                    //Decrements the count of the latch
                    //releasing all waiting threads if the count reaches zero.
                    latch.countDown();
                }

                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t1结束");

        },"t1").start();


    }
}

ReentrantLock相关

  • 基本演示
/**
 * reentrantlock用于替代synchronized
 * 需要注意的是,必须要手动释放锁
 * 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放
 */
class ReentrantLock2 {
    
    //ReentrantLock
    Lock lock = new ReentrantLock();

    void m1(){
        /**
         * 在 try-finally 外加锁的话,如果因为发生异常导致加锁失败
         * try-finally 块中的代码不会执行
         * 相反,如果在 try{ } 代码块中加锁失败,finally 中的代码无论如何都会执行
         * 但是由于当前线程加锁失败并没有持有 lock 对象锁,程序会抛出异常
         *
         * 详情参考 https://blog.csdn.net/u013568373/article/details/98480603
         */
        lock.lock();    //相当于synchronized(this)
        try {
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    void m2(){
        lock.lock();
        try {
            System.out.println("m2....");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLock2 r1=new ReentrantLock2();
        new Thread(r1::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(r1::m2).start();
    }
}

  • trylock
/**
 * reentrantlock用于替代synchronized
 *
 * 需要注意的是,必须要手动释放锁
 * 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放
 *
 * 使用reentrantlock可以进行“尝试锁定”trylock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待
 */
class ReentrantLock3 {
    
    Lock lock=new ReentrantLock();

    void m1(){
        lock.lock();    //相当于synchronized(this)
        try {
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    /**
     * 使用trylock进行尝试锁定,不管锁定与否,方法都将继续执行
     * 可以根据trylock的返回值来判断是否锁定
     * 也可以指定trylock的时间,由于trylock(time)抛出异常,所以要注意unlock的处理,必须方法finally中
     */
    void m2(){
        boolean locked = false;
        try {
            //try lock 等待 5s
            locked= lock.tryLock(5, TimeUnit.SECONDS);
            System.out.println("m2...."+ locked );
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (locked) lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLock3 r1 = new ReentrantLock3();
        new Thread(r1::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(r1::m2).start();
    }
}

0
1
2
3
4
m2…false
5
6
7

  • 演示打断

lockInterruptibly

  • 如果当前线程未被中断,则获取锁。

  • 如果该锁没有被另一个线程保持,则获取该锁并立即返回,将锁的保持计数设置为 1。

  • 如果当前线程已经保持此锁,则将保持计数加 1,并且该方法立即返回。 (可重入)

  • 如果锁被另一个线程保持,发生以下两种情况之一以前,该线程将一直处于休眠状态:

    • 锁由当前线程获得
    • 其他某个线程中断当前线程
  • 如果当前线程获得该锁,则将锁保持计数设置为 1。
    如果当前线程:

    • 在进入此方法时已经设置了该线程的中断状态

    • 在等待获取锁的同时被中断。

      则抛出 InterruptedException,并且清除当前线程的已中断状态。

package c_020;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应
 * 在一个线程等待锁的过程中,可以被打断
 */
public class ReentrantLock4 {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Thread t1 = new Thread(()->{
            lock.lock();
            try {
                System.out.println("t1 start");
                //无限睡眠、线程2拿不到锁
                TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
                System.out.println("t1 end");
            } catch (InterruptedException e) {
                System.out.println("InterruptedException");
            } finally {
                lock.unlock();
            }
        });
        t1.start();

        Thread t2 = new Thread(()->{
            try {
                //此时锁被t1持有 在此等待
                lock.lockInterruptibly();
                System.out.println("t2 start");
                TimeUnit.SECONDS.sleep(5);
                System.out.println("t2 end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("打断响应且释放锁");
                lock.unlock();
            }
        });

        t2.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打断线程2的等待 抛出异常
        t2.interrupt();
    }
}
  • 演示公平锁 ==> 乖乖去阻塞队列等待被唤醒 排队
/**
 * ReentrantLock还可以指定公平锁
 */
public class ReentrantLock5 extends Thread {
    //参数为true表示为公平锁,轻对比输出结果
    private static ReentrantLock lock = new ReentrantLock(true);


    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"获得锁");
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        ReentrantLock5 r1 = new ReentrantLock5();
        Thread th1 = new Thread(r1);
        Thread th2 = new Thread(r1);
        th1.start();
        th2.start();
    }
}

输出交替

Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁

非公平锁 ==> 不可重入 ==>

Thread-1获得锁
Thread-1获得锁
Thread-1获得锁
Thread-1获得锁
Thread-1获得锁
Thread-2获得锁
Thread-2获得锁
Thread-2获得锁

面试题 2 生产者与消费者

wait/notify

package c_021;

import java.util.LinkedList;
import java.util.concurrent.TimeUnit;

/**
 * 面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法
 * 能够支持两个生产者线程以及10个消费者线程的阻塞调用
 * 使用wait和nofify、notifyAll来实现
 */
public class MyContainer1<T> {
    final private LinkedList<T> list = new LinkedList<T>();
    final private int MAX = 10;   //最多10个元素
    private int count=0;

    //生产者
    public synchronized void put(T t){
        //想想为什么用while而不是用if ->while 中每条语句都会对while中的判断条件进行判断
        //满了 ==> 等着
        while (list.size() == MAX){
            try {
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        list.add(t);
        ++count;
        this.notifyAll();//通知消费者线程进行消费
    }

    //消费者
    public synchronized T get(){
        T t = null;
        //没得拿 wait 等着
        while (list.size() == 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        t = list.removeFirst();
        count--;
        this.notifyAll();   //通知生产者进行生产
        return t;
    }

    public static void main(String[] args) {
        MyContainer1<String> c =new MyContainer1<>();
        //启动十个消费者线程
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                //一次拿五个
                for (int j = 0; j < 5; j++) {
                    System.out.println("cget:" + c.get());
                }
            },"c" + i).start();
        }

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //启动生产者线程
        for (int i = 0; i < 2; i++) {
            new Thread(()->{
                //一次生产25个
                for (int j = 0; j < 25; j++) {
                    c.put(Thread.currentThread().getName()+" "+j);
                }
            },"p" + i).start();
        }
    }
}

condition条件变量

package c_021;

import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法
 * 能够支持两个生产者线程以及10个消费者线程的阻塞调用
 *
 * 使用Lock和Condition来实现
 * 对比两种方式,condition的方式可以更加精确的指定哪些线程被唤醒
 * notifyall的唤醒是由JVM选择的
 *
 */
public class MyContainer2<T> {
    final private LinkedList<T> list = new LinkedList<T>();
    final private int MAX = 10;   //最多10个元素
    private int count = 0;

    private Lock lock = new ReentrantLock();
    /*绑定到condition中-->更有针对性   */
    private Condition producer = lock.newCondition();
    private Condition consumer = lock.newCondition();

    public void put(T t){
        lock.lock();
        try {
            while (list.size() == MAX){  //想想为什么用while而不是用if
                /*Causes the current thread to wait until it is signalled or interrupted.*/
                producer.await();
            }
            list.add(t);
            ++count;
            //通知消费者线程进行消费
            consumer.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public T get(){
        T t = null;
        lock.lock();
        try {
            while (list.size() == 0){
                consumer.await();
            }
            t = list.removeFirst();
            count--;
            producer.signalAll();   //通知生产者进行生产
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return t;
    }

    public static void main(String[] args) {
        
        MyContainer2<String> c =new MyContainer2<>();
        //启动消费者线程
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 5; j++) {
                    System.out.println(c.get());
                }
            },"c"+i).start();
        }

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //启动生产者线程
        for (int i = 0; i < 2; i++) {
            new Thread(()->{
                for (int j = 0; j < 25; j++) {
                    c.put(Thread.currentThread().getName()+" "+j);
                }
            },"p"+i).start();
        }
    }
}

ThreadLocal相关

  • 基本使用 - 背景
package c_022;

import java.util.concurrent.TimeUnit;

/**
 * ThreadLocal线程局部变量
 */
public class ThreadLocal1 {

    volatile static Person p = new Person();  /*使用volatile-->线程可见
                                                *注意这里不写Volatile可能发生问题,也可能不发生问题*/
    public static void main(String[] args) {
        //2s 前后 pname
        new Thread(()->{
            System.out.println(p.name);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(p.name);
        }).start();

        //修改 name
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            p.name="lisi1";
        }).start();
    }
}

class Person{
    String name="zhangsan";
}

zhangsan

lisi1

  • 使用 threadlocal
package c_022;

import java.util.concurrent.TimeUnit;

/**
 * ThreadLocal线程局部变量
 *
 * ThreadLocal是使用空间换时间,synchronized是使用时间换空间
 * 比如在hibernate中的session就存在ThreadLocal中,避免synchronized的使用
 */
public class ThreadLocal2 {
    /*不使用volatile,各个线程维护各自的"本地副本”*/
    static ThreadLocal<Person> t1 = new ThreadLocal<>();

    public static void main(String[] args) {
        System.out.println(t1.get());
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            /*使用get获取默认值,get不到线程2中的new Person (lisi) 所以结果为null*/
            System.out.println(t1.get()); 
        }).start();


        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t1.set(new Person()); /*set->设置副本为new Person*/
        }).start();
    }

    static class Person{
        String name="zhangsan";
    }
}

null

Null

面试题 3 火车票

情景复现

/**
 * 有n张火车票,每张票都有一个编号
 * 同时有10个窗口对外售票
 * 请写一个模拟程序
 * 分析下面的程序可能会产生哪些问题?
 * 重复销售?超量销售?  --> 不加锁的情况下 都有可能
 */
public class TicketSeller1 {
    static List<String> tickets = new ArrayList<>();
    //初始 1w 张票
    static {
        for (int i = 0; i < 10000; i++) {
            tickets.add("票编号:" + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            //无限抢票
            new Thread(()->{
               while (tickets.size() > 0){
                   System.out.println(Thread.currentThread().getName()+"销售了--" + tickets.remove(0));
               }
            },"窗口"+i).start();
        }
    }
}

  • static Vector< String> tickets = new Vector<>(); 低级线程安全

  • synchronized (tickets){ ==> 十分低效

  • static Queue< String> tickets=new ConcurrentLinkedQueue<>(); 高级并发容器

    package c_024;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Queue;
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 有n张火车票,每张票都有一个编号
     * 同时有10个窗口对外售票
     * 请写一个模拟程序
     *
     * 分析下面的程序可能会产生哪些问题?
     * 重复销售?超量销售?
     *
     */
    public class TicketSeller4 {
        //并发容器
        static Queue<String> tickets = new ConcurrentLinkedQueue<>();
    
        static {
            for (int i = 0; i < 1000; i++) {
                tickets.add("票编号:"+i);
            }
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                   while (true){
                       //先poll再判断,就不用加syn -->但输出结果非顺序(判断操作分离
                       String s = tickets.poll();
                       if (s == null){
                           break;
                       }else{
                           System.out.println(Thread.currentThread().getName()+"销售了--" + s);
                       }
                   }
                },"窗口"+ i).start();
            }
        }
    }
    

并发容器相关【待补充】

线程池相关

/**
 * 认识Executor
 */
public class T01_MyExecutor implements Executor {
    public static void main(String[] args) {
        new T01_MyExecutor().execute(()->{
           	//Runnable
            System.out.println("hello executor");
        });
    }

    @Override
    public void execute(Runnable command){
        new Thread(command).run();
        //command.run();
    }
}

/**
 * 认识ExecutorService,阅读API文档
 */

/**
 * 认识Callable,对Runnable进行了扩展
 */

/**
 * 认识Executors
 */
package c_026;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 线程池的概念
 */
public class T05_ThreadPool {
    public static void main(String[] args) throws InterruptedException {
        //execute submit
        ExecutorService service= Executors.newFixedThreadPool(5);
        for (int i = 0; i < 6; i++) {
            service.execute(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
        }

        //打印线程池信息
        System.out.println(service);
        //等任务执行完成后结束
        service.shutdown();
        System.out.println(service.isTerminated());
        //是否shutdown状态
        System.out.println(service.isShutdown());
        System.out.println(service);
        //给时间让任务都执行完,根据CPU的性能,需要调控好时间才能看出区别
        TimeUnit.SECONDS.sleep(10);
        System.out.println(service.isTerminated());
        System.out.println(service.isShutdown());
        System.out.println(service);
    }
}

java.util.concurrent.ThreadPoolExecutor@a09ee92[Running, pool size = 5, active threads = 5, queued tasks = 1, completed tasks = 0]
false
true
java.util.concurrent.ThreadPoolExecutor@a09ee92[Shutting down, pool size = 5, active threads = 5, queued tasks = 1, completed tasks = 0]
pool-1-thread-1
pool-1-thread-5
pool-1-thread-3
pool-1-thread-2
pool-1-thread-4
pool-1-thread-1
false
true
java.util.concurrent.ThreadPoolExecutor@a09ee92[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 6]

Future Callable

单线程与线程池用法

public class T06_Future {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //定义任务
        FutureTask<Integer> task = new FutureTask<>(()->{
            //Callable
            TimeUnit.MILLISECONDS.sleep(500);
            return 1000;
        });

        //新创建线程来执行这个任务task
        new Thread(task).start();
        //阻塞,等待计算完成,取回结果 --> 1000
        System.out.println(task.get());

        //线程池操作
        ExecutorService service= Executors.newFixedThreadPool(5);
        /*任务定义*/
        Future<Integer> f = service.submit(()->{
            TimeUnit.MILLISECONDS.sleep(500);
            return 1;
        });
        System.out.println(f.get());
        System.out.println(f.isDone());

    }
}

1000
1
true

  • 并行计算质数
/**
 * 应用
 * nasa --> 平行(并行)计算
 */
public class T07_ParallelComputing {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //正常计算 统计时间
        long start = System.currentTimeMillis();
        List<Integer> results = getPrime(1,200000);
        long end = System.currentTimeMillis();
        System.out.println(end - start);



        //线程池方式
        ExecutorService service= Executors.newFixedThreadPool(5);
        MyTask t1=new MyTask(1,80000);
        MyTask t2=new MyTask(80001,130000);
        MyTask t3=new MyTask(130001,170000);
        MyTask t4=new MyTask(170000,200000);

        Future<List<Integer>> f1 = service.submit(t1);
        Future<List<Integer>> f2 = service.submit(t2);
        Future<List<Integer>> f3 = service.submit(t3);
        Future<List<Integer>> f4 = service.submit(t4);

        start=System.currentTimeMillis();
        f1.get();
        f2.get();
        f3.get();
        f4.get();
        end=System.currentTimeMillis();
        System.out.println(end - start);
    }

    //获取 start ==> end 之间的质数
    static class MyTask implements Callable<List<Integer>>{
        int startPos, endPos;

        MyTask(int s,int e){
            this.startPos = s;
            this.endPos = e;
        }
        //重写Callable的cALL方法 --> 大致跟run方法一样
        @Override
        public List<Integer> call() throws Exception {
            List<Integer> r = getPrime(startPos , endPos);
            return r;
        }
    }

    //判断质数
    private static boolean isPrime(int num){
        for (int i = 2; i < num / 2; i++) {
            if (num % i == 0){
                return false;
            }
        }
        return true;
    }

    //获取质数
    private static List<Integer> getPrime(int start, int end) {
        List<Integer> result= new ArrayList<>();
        for (int i = start; i <= end; i++) {
            if (isPrime(i)){
                result.add(i);
            }
        }
        return result;
    }
}

CachedPool

无限救急

public class T08_CachedPool {
    public static void main(String[] args) throws InterruptedException {

        ExecutorService service = Executors.newCachedThreadPool();
        System.out.println(service);

        for (int i = 0; i < 2; i++) {
            service.execute(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
        }

        System.out.println(service);
        TimeUnit.SECONDS.sleep(80);
        System.out.println(service);
    }
}

java.util.concurrent.ThreadPoolExecutor@279f2327[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
java.util.concurrent.ThreadPoolExecutor@279f2327[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
pool-1-thread-1
pool-1-thread-2

SingleThreadExecutor

单线程处理多任务

ExecutorService service= Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
    final int j = i;
    service.execute(()->{
        System.out.println(j + " "+Thread.currentThread().getName());
    });
}

0 pool-1-thread-1
1 pool-1-thread-1
2 pool-1-thread-1
3 pool-1-thread-1
4 pool-1-thread-1

SchedulePool

创建一个周期线程池,支持定时及周期性任务执行

package c_026;

import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


public class T10_SchedulePool {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(4);

        /**
         * Creates and executes a periodic action that becomes enabled first
         * after the given initial delay*/
        service.scheduleAtFixedRate(()->{
            try {
                //1s 内随机睡
//                TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
                TimeUnit.MILLISECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
            //延迟 0 秒  0.5秒执行一次任务
        },0,500, TimeUnit.MILLISECONDS);
    }
}

pool-1-thread-4
pool-1-thread-4
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-4
pool-1-thread-4
pool-1-thread-4
pool-1-thread-4
pool-1-thread-1
pool-1-thread-1

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值