Java并发面试

Java并发:

线程状态?

新建 new: 创建后尚未启动;

可运行 Runnable:可能正在运行,也可能正在等待CPU时间片;
	(包含了操作系统线程状态中Running和Ready)
	
阻塞 Blocking:等待获取一个排它锁,如果其线程释放了锁,就会结束此状态。

无限期等待 Waiting:等待其他线程显式唤醒,否则不会被分配CPU时间片。
	
限期等待 Timed Waiting:无需等待其他线程显式地唤醒,在一定时间之后会被系统自动唤醒。

死亡 Terminated:可以是线程结束任务之后自己结束,或者产生了异常而结束。

实现线程的方式?

1. 继承Thread类
2. 实现Runnable接口
3. 实现Callable接口:Callable 可以有返回值,返回值通过 FutureTask 进行封装
4. 创建线程池
// 1. 继承Thread类
public class MyThread extends Thread {
    public void run() {
        // ...
    }
}

public static void main(String[] args) {
    MyThread mt = new MyThread();
    mt.start();
}
// 2. 实现Runnable接口
public class MyRunnable implements Runnable {
    public void run() {
        // ...
    }
}

public static void main(String[] args) {
    MyRunnable instance = new MyRunnable();
    Thread thread = new Thread(instance);
    thread.start();
}
// 3. 实现Callable接口
public class MyCallable implements Callable<Integer> {
    public Integer call() {
        return 123;
    }
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyCallable mc = new MyCallable();
    FutureTask<Integer> ft = new FutureTask<>(mc);
    Thread thread = new Thread(ft);
    thread.start();
    System.out.println(ft.get());
}

线程池:

线程池 ThreadPoolExecutor:

三种定义好的线程池,但不推荐使用:
1、newCachedThreadPool:有多少任务,创建多少线程。CPU干100%。

2、newFixedThreadPool(10):超过第10个任务,任务要排队等待(10个10个一组)。
	内存溢出。

3、newSingleThreadPool:和Fixed同理,也会内存溢出。
推荐使用自定线程池:

常用7个参数:

1. 核心线程数:int corePoolSize
	线程池创建好后,就准备就绪的线程数量,等待来接受异步任务执行。
	
2. 池中允许的最大线程数:int maxinumPoolSize
	
3. 存活时间:long keepAliveTime
	当线程数大于核心线程数时,这是多余的空间线程在终止前,等待新任务的最长时间。
	
4. 时间单位 TimeUnit unit

5. 阻塞队列 BlockingQueue<Runnable> workQueue
	如果任务有很多,就会将目前多的任务,放在队列里;
	只要有线程空闲了,就会去队列里面取出新的任务继续执行。(默认)
	
6. 线程的创建工厂 ThreadFactory threadFactory

7. 拒绝策略 RejectedExecutionHandler handler
	如果队列满了,按照指定的拒绝策略执行任务。(默认使用丢弃策略AbortPolicy)
举例:一个线程池:core 7,max 20,queue:50;

100个并发进来怎么执行?

7个会立刻执行,50个会进入队列,再开13个进入执行,剩下30个就被拒绝执行。
阻塞队列:BlockingQueue

1. ArrayBlockingQueue
2. LinkedBlockingQueue
拒绝策略:RejectedExecutionHandler

1. AbortPolicy:丢弃任务并抛出异常:
	RejectedExcutionException异常;
	
2. DiscardPolicy:丢弃任务,但是不抛出异常;

3. DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。

4. CallerRunsPolicy:重试添加当前的任务,自动重复调用execute()方法,直到成功。
配置线程池考虑因素:
	任务的优先级,
	任务的执行时间长短,
	任务的性质(CPU密集/ IO密集),
	任务的依赖关系。
	
CPU密集型: 尽可能少的线程,Ncpu+1;

IO密集型: 尽可能多的线程, Ncpu*2,比如数据库连接池;

混合型: CPU密集型的任务与IO密集型任务的执行时间差别较小,拆分为两个线程池;否则没有必要拆分。
public class ThreadPoolTest {
    public static void main(String[] args) {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 20L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10), new ThreadPoolExecutor.DiscardOldestPolicy());

        for (int i = 0; i < 40; i++) {
            poolExecutor.execute(new MyTask(i));
        }
        poolExecutor.shutdown();
    }
}

class MyTask implements Runnable {
    int i = 0;

    public MyTask(int i) {
        this.i = i;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "___" + i);
        // 这行注释, 会出现线程复用 (线程在执行完毕后,又会去继续执行其他任务)
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

异步执行:

CompletableFuture 异步执行任务。

Future:用于Runnable和Callable任务执行结果,进行取消、查询是否完成,获取结果;
	必要时使用get方法获取执行结果,该方法会阻塞到任务返回结果。
	
CompletableFuture的 join和get 的区别?
	1. 都是获取CompletableFuture异步之后的返回值的;
	2. join抛出异常是uncheck异常,未经检查的异常,不会强制开发者捕捉
		(CompletionException、CancellationException);
	    get抛出的是经过检查的异常,需要手动try-catch抛出
	    (ExecutionException、InterruptedException)。
1. 
runAsync: 启动一个异步任务,无返回值;
supplyAsync: 启动一个异步任务,有返回值。
    // 自定义核心线程池:
    public static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20,
            60L, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(50), new ThreadPoolExecutor.CallerRunsPolicy());

	public static void main(String[] args) throws ExecutionException, InterruptedException {
        // runAsync 启动一个异步任务,无返回值。
        CompletableFuture.runAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getName());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
        }, executor);
        
        // supplyAsync 启动一个异步任务,有返回值。
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getName());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return i;
        }, executor);
        Integer resultNum = future.get();
        System.out.println("异步任务有返回值的,返回值结果为:" + resultNum);
    }
2. 
whenComplete:能够接受返回值和异常,但没法修改返回值;和上一个调用执行是同一个线程,
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getName());
            int i = 10 / 0;
            System.out.println("运行结果:" + i);
            return i;
        }, executor).whenComplete((res, throwable) -> {
            // 能够接受返回值和异常,但没法修改返回值;和上一个调用执行是同一个线程,

            // 抛异常就不会return,没有返回值结果
            System.out.println(res);
            System.out.println(throwable.getMessage());
        }).exceptionally(throwable -> {
            // 只处理异常,不能接受返回值,可以修改默认返回值。
            System.out.println(throwable.getMessage());
            return 10;
        });
3. 
handle: 无论成功或失败,都可以在回调中,接受异常,接受返回值并修改数据。
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getName());
            int i = 10 / 0;
            System.out.println("运行结果:" + i);
            return i;
        }, executor).handle((res, throwable) -> {
            // 无论成功或失败,都可以在回调中,接受异常,接受返回值并修改数据:
            if (ObjectUtil.isNotNull(res)) {
                if (res % 2 == 0) {
                    return 2;
                }
                return 1;
            }
            System.out.println(throwable.getMessage());
            return -1;
        });

        Integer resNum = future.get();
        System.out.println("最终返回值:" + resNum);

方法start run join wait sleep:

start():
	底层调用start0方法,会创建新的线程;
	start0方法是本地方法jvm调用,是c语音实现的。

run():
	只是简单的方法调用,不会创建新的线程。
	
main线程启动一个子线程Thread时,主线程不会阻塞,而是主线程和子线程交替执行;
(不是主线程结束了,子线程也结束)

join():
	线程插队,插队的线程一旦成功,肯定先执行,插入的线程所有任务。
	
yield():
	线程让步,让出CPU使用权,让其他线程执行。(让了也不一定成功)
	
wait():
	wait是object方法,任何对象实例都能调用;
	wait会释放锁,但调用它的前提是:当前线程占用锁。
	
sleep():
	sleep是Thread的静态方法;
	sleep不会释放锁,也不会占用锁。

锁?

线程要不要锁住同步资源?
	锁住,悲观锁;
	不锁住,乐观锁;
	
锁住同步资源失败,线程要不要阻塞?
	阻塞;
	不阻塞;自旋锁,适应性自旋锁;
	
一个线程中的多个流程能不能获取同一把锁?
	能,可重入锁;
	不能,非可重入锁;
	
多个线程竞争锁时,要不要排队?
	排队,公平锁;
	先尝试插队,插队失败在排队; 非公平锁;
	
多个线程能不能共享一把锁?
	能,共享锁;
	不能,排它锁;
	
多个线程竞争同步资源的流程细节有没有区别?
	无锁;不锁住资源,多个线程中只有一个能修改资源成功,其他线程会重试;
	偏向锁;同一个线程执行同步资源时自动获取资源;
	轻量级锁;多个线程竞争同步资源时,没有获取资源的线程自选等待锁释放;
	重量级锁;多个线程竞争同步资源时,没有获取资源的线程阻塞等待唤醒;
悲观锁:(查询,新增)
	认为线程安全问题,一定会存在;
	因此在操作数据之前,先获取锁,确保线程串行执行。
	如:Synchronized,Lock (添加同步锁,让线程串行执行)
	

乐观锁:(只能用于修改)
	认为线程安全问题不一定会发生,因此不加锁;
	只是在更新数据时,去判断有没有其他线程修了数据。

	如果没有修改,则认为是安全的,自己才更新数据;
	如果已经被其他线程修改,说明发生了安全问题,此时可以重试或抛异常。
	
	1.版本号:version查询要更新版本号,更新时也要更新版本号;
	2.简化:可使用 库存>0 代替版本号 (CAS法 比较并交换);
		(不加锁,在更新时判断是否有其他线程在修改)。
		
// ------------------------- 悲观锁的调用方式 -------------------------
// synchronized
public synchronized void testMethod() {
	// 操作同步资源
}
// ReentrantLock
private ReentrantLock lock = new ReentrantLock(); // 需要保证多个线程使用的是同一个锁
public void modifyPublicResources() {
	lock.lock();
	// 操作同步资源
	lock.unlock();
}

// ------------------------- 乐观锁的调用方式 -------------------------
private AtomicInteger atomicInteger = new AtomicInteger();  
// 需要保证多个线程使用的是同一个AtomicInteger
atomicInteger.incrementAndGet(); //执行自增1

阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成的,这种状态转换需要耗费处理器时间;
	如果同步代码块的内容过于简单,状态转换消耗的时间可能比代码执行的时间还要长。
	
为了让当前线程 等待一下,我们要让当前线程进行自旋,如果在自旋完成后,前面锁定的同步资源的线程就释放了;
	那么当前线程就不必阻塞,而是直接获取同步资源,从而避免切换线程的开销,者就是自旋锁。
	
1. 自旋锁:
	某线程尝试获取同步资源的锁失败,资源被占用;
	不放弃CPU时间片,通过自旋等待释放锁;
		再尝试获取锁,如果失败,就自旋继续等待;
		(通过自旋操作减少CPU切换,以及恢复现场导致的消耗)
		获取锁成功,获取同步资源的锁。
		
自旋锁本身是由缺点的:
	它不能代替阻塞,自旋等待虽然避免了线程切换的开销;
	但它要占用处理器时间,如果锁被占用的时间很短,自旋等待的效果就会非常好;
	反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。
	
自旋的等待时间必须要有一定的限度,如果自旋超过了限定次数;
(默认10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。

自旋锁的实现原理同样也是CAS,
	AtomicInteger中调用unsafe进行自增操作的源码中的do-while循环就是一个自旋操作,如果修改数值失败则通过循环来执行自旋,直至修改成功。
ABA问题:
线程1:拿到的了A的值,把修改为B;
线程2:抢到A,把修改成了C,但最后又修改成了A)。

解决方法:加个version字段,修改一次,版本号加1(AtomicStampedReference实现类)。
		如果是基本数据类型无所谓,但是修改的是对象,引用类型就有问题了。
可重入锁:
synchronized和Lock都是可重入锁,也叫递归锁。

一把锁可以重复调用,直到栈溢出,所以是可重入
    public synchronized void add() {
        add();
    }

    public static void main(String[] args) {
        new SynThread().add();
    }

synchronized:

是互斥锁、悲观锁、重量级锁(满足原子性)

使用synchronized修饰,表示在一个时刻只能有一个线程访问;
	局限性:会导致程序执行效率低。
	
底层实现:
	1. jdk早期是重量级锁,有操作系统OS调用;
	
	2. 后期:锁升级;
	sync(obj):第一个去访问锁的线程,先在object头部记录这个线程id(偏向锁);
	如果线程争用,发现头部的某两位,id不相等;升级位自旋锁(调用者一直循环在哪里,看该锁释放已经释放了);
	自旋10次以后,还没有得到这把锁,才升级位重量级锁。
	
	3. 执行时间短(加锁代码):
	线程数少,用自旋;线程数多,同系统锁。
public static void main(String[] args) {
    int i = 0;
    Object o = new Object();
    // 所谓给对象上锁,就是给object header上添加信息。
    synchronized (o) {
        i++;
    }
}
// 想要执行{ }括号里的,必须持有锁(o),所谓的锁定o 指的是修改o的头部,记录锁的信息。
1. 
非静态同步锁:

sychronzied(object) 不能用String,常量,Integer,Long,基本数据类型;
可以是this,也可以是其他对象,
	new Ojbect(); 
	(要求多个线程共享的同一个对象)。
private int num = 10;

public synchronized void m() {
    num--;
    System.out.println(Thread.currentThread().getName() + " num=" + num);
}

public void m1() {
    // 任何线程要执行下面代码,必须先拿到this的锁。
   synchronized (this) {
        num--;
        System.out.println(Thread.currentThread().getName() + " num=" + num);
    }
}
2.
静态同步锁:
public class StaticSynchronized {
    private static int count = 10;

    // 这里等同于 synchronized(StaticSynchronized.class) 锁的是这个。
    public synchronized static void m() {
        count--;
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }
		
 	// 两个方法相等:
    public static void m1() {
        // 这里是否能用 synchronized (this) ?
        synchronized (StaticSynchronized.class) {
            count--;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }
}

Lock接口ReentrantLock:

// 1. tryLock尝试锁:
public class T {
    Lock lock = new ReentrantLock();

    void m1() {
        try {
            lock.lock();
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(1000L);
                    System.out.println(Thread.currentThread().getName() + " Thread: " + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    void m2() {
        boolean locked = false;
        try {
            // 判断有没有拿到锁, tryLock尝试拿锁。 m1等待时间 < m2。
            locked = lock.tryLock(5L, TimeUnit.SECONDS);
            System.out.println(Thread.currentThread().getName() + 
                               "Thread-TryLock " + locked);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (locked) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        T t = new T();

        new Thread(t::m1, "t1").start();

        new Thread(t::m2, "t2").start();
    }
}
// 2. 公平锁:卖票案例
public class SaleTicket {
    public static void main(String[] args) {
        LockTicket lockTicket = new LockTicket();
        new Thread(lockTicket, "窗口1").start();
        new Thread(lockTicket, "窗口2").start();
        new Thread(lockTicket, "窗口3").start();
    }

}

class LockTicket implements Runnable {
    private static int ticketSum = 100;
    private Boolean flag = true;
    // true表示公平锁,默认是false非公平锁
    private final Lock lock = new ReentrantLock(true);

    public void setFlag(Boolean flag) {
        this.flag = flag;
    }

    public Boolean getFlag() {
        return flag;
    }

    private void sale() {
        lock.lock();
        try {
            if (ticketSum <= 0) {
                System.out.println("票已卖完,售票结束");
                setFlag(false);
                return;
            }
            System.out.println(Thread.currentThread().getName() + 
                               "当前窗口卖一张票,剩余票数:" + (ticketSum--));
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void run() {
        while (flag) {
            sale();
        }
    }
}
Lock synchronized区别?
1. 
Lock接口:
	发生异常时,若没有主动通过unLock()去释放锁,会造成死锁,需要在final块中释放锁;
	有公平锁、非公平锁;
	可以让等待锁的线程响应中断:lock interupptibly;
	尝试获取锁。
	
2.
synchronized是关键字,内置语言实现;
	发生异常时,会自动释放占有线程锁,不会导致死锁;
	无公平锁;
	不能中断响应,会让等待的线程一直等待下去。

卖票超卖案例:

多个线程同时对一个数据进行操作时,会出现并发的问题,我们应该对同步操作加锁。
// synchronized完成超卖案例:
public class SynSaleTicket {
    public static void main(String[] args) {
        SynSale synSale = new SynSale();
        new Thread(synSale, "窗口1").start();
        new Thread(synSale, "窗口2").start();
        new Thread(synSale, "窗口3").start();
    }
}

class SynSale implements Runnable {
    private static int ticketSum = 100;
    private Boolean flag = true;

    public void setFlag(Boolean flag) {
        this.flag = flag;
    }

    public Boolean getFlag() {
        return flag;
    }

    private synchronized void sale() {
        if (ticketSum <= 0) {
            System.out.println("票已卖完,售票结束");
            setFlag(false);
            return;
        }
        System.out.println(Thread.currentThread().getName() +
                "当前窗口卖一张票,剩余票数:" + (ticketSum--));
    }

    private void sale1() {
        synchronized (this){
            if (ticketSum <= 0) {
                System.out.println("票已卖完,售票结束");
                setFlag(false);
                return;
            }
            System.out.println(Thread.currentThread().getName() +
                    "当前窗口卖一张票,剩余票数:" + (ticketSum--));
        }
    }

    @Override
    public void run() {
        while (flag) {
            sale1();
        }
    }
}

线程之间交替执行:

需求:
两个线程一个打印ABCD,一个打印1234,结果交替执行1A2B3C4D;
/***
 *  1.
 *  synchronized: A1B2C3D4 两个线程交替执行
 */
public class SynAlternate {
    public static void main(String[] args) {
        new SynAlternate().doRun();
    }

    Object o = new Object();
    char[] a1 = "1234567".toCharArray();
    char[] aA = "ABCDEFG".toCharArray();

    public void doRun() {
        new Thread(() -> {
            synchronized (o) {
                for (char c : a1) {
                    System.out.print(c);
                    try {
                        o.notify();
                        o.wait(); // 让出锁,当前线程进入该锁的等待队列。
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                o.notify(); // 必须,否则无法停止程序
            }
        }, "a1").start();

        new Thread(() -> {
            synchronized (o) {
                for (char c : aA) {
                    System.out.print(c);
                    try {
                        o.notify();
                        o.wait(); // 让出锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                o.notify(); // 必须,否则无法停止程序
            }
        }, "aA").start();
    }
}

/***
 *  2.
 *  Lock实现交替执行:
 */
public class LockAlternate {
    public static void main(String[] args) {
        new LockAlternate().doRun();
    }
    char[] a1 = "1234567".toCharArray();
    char[] aA = "ABCDEFG".toCharArray();

    Lock lock = new ReentrantLock();
    // 一个锁共用 二个队列
    Condition conditionT1 = lock.newCondition(); // 队列1 只存放消费者的
    Condition conditionT2 = lock.newCondition(); // 队列2 只存放生产者的

    public void doRun() {
        new Thread(() -> {
            lock.lock();
            try {
                for (char c : a1) {
                    System.out.print(c);
                    // 唤醒第二个线程
                    conditionT2.signal();
                    conditionT1.await();
                }
                conditionT2.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "a1").start();

        new Thread(() -> {
            lock.lock();
            try {
                for (char c : aA) {
                    System.out.print(c);
                    // 唤醒第二个线程
                    conditionT1.signal();
                    conditionT2.await();
                }
                conditionT1.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "aA").start();
    }
}
// 3.
public class LockSupportTest {
    static Thread t1 = null;
    static Thread t2 = null;

    char[] a1 = "1234567".toCharArray();
    char[] aA = "ABCDEFG".toCharArray();

    public void doRun() {
        t1 = new Thread(() -> {
            for (char c : a1) {
                System.out.print(c);
                // unpark:唤醒 t2
                LockSupport.unpark(t2);
                LockSupport.park();
            }
        });

        t2=new Thread(()->{
            for (char c : aA) {
                LockSupport.park();
                System.out.print(c);
                LockSupport.unpark(t1);
            }
        });

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

    public static void main(String[] args) {
        LockSupportTest lockSupportTest = new LockSupportTest();
        lockSupportTest.doRun();
    }
}

虚假唤醒问题wait,await

1.
使用wait()方法,必须使用while,不能用if;
	在哪等待,就在哪唤醒,之前的if是不会进行判断的,所以不能用if,由于多次同一个线程执行跳过了if。
// synchronized锁:
while (number != 0) {
	this.wait();
}

// lock锁
while (number != 0) {
	condition.await();
}

线程通信:

三步骤:
判断
干活
通知
// 1. syn
public class SynTalk {
    private int number = 0;

    /***
     *  加1方法
     */
    public synchronized void incr() throws InterruptedException {
        // 判断
        while (number != 0) {
            this.wait();
        }
        // 干活
        number++;
        System.out.println(Thread.currentThread().getName()
                + ",当前线程加一,为:" + number);
        // 通知
        this.notify();
    }

    public synchronized void decr() throws InterruptedException {
        while (number != 1) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()
                + ",当前线程减一,为:" + number);
        this.notify();
    }

    public static void main(String[] args) {
        SynTalk synTalk = new SynTalk();
        // +1
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    synTalk.incr();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // -1
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    synTalk.decr();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
// 2. Lock
public class LockTalk {
    private int number = 0;
    private final ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    // +1
    public void incr() {
        try {
            lock.lock();
            // 判断
            while (number != 0) {
                condition.await();
            }
            // 干活
            number++;
            System.out.println(Thread.currentThread().getName()
                    + ",当前线程加一,为:" + number);
            //通知
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    // -1
    public void decr() {
        try {
            lock.lock();
            while (number != 1) {
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()
                    + ",当前线程减一,为:" + number);
            //通知
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        LockTalk lockTalk = new LockTalk();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                lockTalk.incr();
            }
        }).start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                lockTalk.decr();
            }
        }).start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值