JUC学习

295 阅读17分钟

JUC学习

volatile关键字

用于解决内存可见性,因为不同线程存在不同的数据拷贝,也就是共享变量不可见

如果使用synchronized()加锁,会降低效率

所以使用volatile可以视作操作在主存中完成,相较于synchronized更加轻量的同步策略,但是不能保证原子性,也不具备“互斥性”

//即使flag变为true,main函数也不会停止
//flag存在于主存中,然后两个线程获得两份拷贝,当线程一修改flag以后再还回去修改主存的flag,由于
//main的while效率太高(过于底层)所以会不断执行导致无法读取flag新值,如果暂停一会就可以的得到应
//有结果
public class juc {
   @Test
    public  void fun()
   {
       ThreadDemo threadDemo = new ThreadDemo();
       new Thread(threadDemo).start();
       while (true){
            try {
               Thread.sleep(100);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           if (threadDemo.flag==true) {
               System.out.println(threadDemo.flag);
               break;
           }
       }

   }
}
class ThreadDemo implements Runnable{
    public boolean flag=false;
    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag=true;
        System.out.println("flag="+flag);
    }
}

原子变量和CAS算法

i++不是一个原子操作,也就是说操作可以分割

//Thread-2:4
//Thread-5:3
//Thread-7:6
//Thread-3:9
//Thread-6:5
//Thread-8:1
//Thread-9:2
//Thread-0:8
//Thread-1:1
//Thread-4:7
//会出现某些线程打印的number值相同的问题
public static void main(String[] args) {
        AtomicDemo atomicDemo = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(atomicDemo).start();
        }
       
    }

}
class AtomicDemo implements Runnable{
private volatile int number=0;
public int getNumber()
{
    number++;
    return number;
}
@Override
    public void run() {
    try {
        Thread.sleep(200);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+":"+getNumber());
}
}

为了解决这个问题就需要原子变量(jdk1.5之后的java.util.concurrent.atomic提供了常用的原子变量,包括基本类型和引用)

原子变量特性

1,volatile保证内存的可见性

2,cas(compare-and-swap)算法保证原子性,本质上是硬件对并发操作的支持,包含了三个操作数

内存值v,预估值a(当前值),更新值b,当且仅当v==a时,v=b,否则不做操作,只有一个线程能修改,不会阻塞线程,其余线程可以再去尝试修改

就是指当两者进行比较时,如果相等,则证明共享数据没有被修改,替换成新值,然后继续往下运行;如果不相等,说明共享数据已经被修改,放弃已经所做的操作,然后重新执行刚才的操作。容易看出 CAS 操作是基于共享数据不会被修改的假设。当同步冲突出现的机会很少时,这种假设能带来较大的性能提升。

private AtomicInteger number=new AtomicInteger(0);  
number.getAndIncrement();
number.incrementAndGet();
//区别就是按字面顺序执行先获得还是先自增
//解决了i++不是原子性的问题
    public static void main(String[] args) {
        AtomicDemo atomicDemo = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(atomicDemo).start();
        }

    }
class AtomicDemo implements Runnable{

private AtomicInteger number=new AtomicInteger(0);
public int  getNumber()
{
    int i = number.getAndIncrement();
    return i;
}
@Override
    public void run() {
    try {
        Thread.sleep(200);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+":"+getNumber());
}

简单模拟cas算法

public class TestCompareAndSwap {
    public static void main(String[] args) {
        final CompareAndSwap cas = new CompareAndSwap();

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                int expectedValue = cas.get();
                boolean boo = cas.compareAndSet(expectedValue, (int)(Math.random() * 100));
                System.out.println(boo+":预估值--"+expectedValue);
            }).start();
        }
    }
}

class CompareAndSwap{
    //模拟内存值
    private int value;

    //获取内存值
    public synchronized int get(){
        return this.value;
    }

    //比较和交换
    public synchronized int compareAndSwap(int expectedValue,int newValue){
        //获取内存值
        int oldValue = value;
        //内存值等于预估值
        if(expectedValue == oldValue){
            //更新内存值
            value = newValue;
        }
        return oldValue;
    }

    //判断是否修改成功
    public synchronized boolean compareAndSet(int expectedValue,int newValue){
        return expectedValue == compareAndSwap(expectedValue, newValue);
    }
}

CAS缺点

CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作

1,ABA问题

​ 因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

举个例子,我月入1W5,又因为彩票获奖2K,顺便买了2K的东西,前后仍是1W5,如果使用CAS就会导致这2K交不上税,我就算逃税了,就很不好

关于ABA问题参考文档: blog.hesey.net/2011/09/res…

2. 循环时间长开销大

​ 自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

3. 只能保证一个共享变量的原子操作

​ 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

ABA问题实例

   AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
        new Thread(()->{
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);
        }).start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicReference.compareAndSet(100, 105);
            System.out.println(atomicReference.get());
        }).start();
//输出为105,实际上不应更改,因为初值变动了一次

利用戳解决问题

AtomicStampedReference内部维护了对象值和版本号,在创建AtomicStampedReference对象时,需要传入初始值和初始版本号, 当AtomicStampedReference设置对象值时,对象值以及状态戳都必须满足期望值,写入才会成功

    AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 0);
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);
        }).start();
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 1005, stamp, atomicStampedReference.getStamp()+1);
            System.out.println("value: "+atomicStampedReference.getReference()+"stamp:"+atomicStampedReference.getStamp());
        }).start();
    }
//输出value: 100stamp:2

利用标记解决ABA问题

AtomicStampedReference可以给引用加上版本号,追踪引用的整个变化过程,如:A -> B -> C -> D - > A,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了3次 但是,有时候,我们并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference AtomicMarkableReference的唯一区别就是不再用int标识引用,而是使用boolean变量——表示引用变量是否被更改过

其中get()方法,参数为Boolean数组,其实只用下标为0的一个,将最后mark写入,并返回最终成功修改后的值

 AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<Integer>(100,false);
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            markableReference.compareAndSet(100, 101, markableReference.isMarked(), true);
            markableReference.compareAndSet(101, 100, markableReference.isMarked(),true );
        }).start();
        new Thread(()->{
            boolean ismarked = markableReference.isMarked();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            markableReference.compareAndSet(100, 1000, ismarked, true);
            System.out.println(markableReference.getReference());
        }).start();

外拓

对于数据库的幂等操作也可以设置戳来实现

同步容器类(例子:ConcurrentHashMap)

juc集合类

CopyOnWriteArrayList:相当于线程安全的ArrayList,实现了List接口,支持高并发,当期望的读数和遍历远远大于列表的更新数时,优于同步的ArrayList

CopyOnWriteArraySet:相当于线程安全的HashSet,它继承于AbstractSet类,其内部是通过CopyOnWriteArrayList来实现的

ConcurrentSkipListSet:相当于线程安全的TreeSet,它继承于AbstractSet类,并实现了NavigableSet接口,其内部是通过ConcurrentSkipHashMap实现的,支持高并发

ConcurrentHashMap:相当于线程安全的HashMap,它继承于AbstractMap类,并且实现ConcurrentMap接口。ConcurrentHashMap是通过“分段锁”来实现并发的

ConcurrentSkipListMap:相当于线程安全的TreeMap,它继承于AbstractMap,并实现了ConcurrentNavigableMap接口。ConcurrentSkipListMap是通过“跳表”来实现并发的

ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列

LinkedBlockingQueue是单向链表实现的(指定大小)阻塞队列,该队列按 FIFO(先进先出)排序元素。

LinkedBlockingDeque是双向链表实现的(指定大小)双向并发阻塞队列,该阻塞队列同时支持FIFO和FILO两种操作方式。

ConcurrentLinkedQueue是单向链表实现的无界队列,该队列按 FIFO(先进先出)排序元素。

ConcurrentLinkedDeque是双向链表实现的无界队列,该队列同时支持FIFO和FILO两种操作方式。

ConcurrentHashmap

ConcurrentHashMap线程安全的HashMap,比Hashtable性能强,因为Hashtable是锁整个表,而且复合操作(不存在就添加,存在就删除)会导致线程不安全,因为两个操作间隙多线程锁的竞争问题。

1.8之前而ConcurrentHashmap采用锁分段机制,内部分为十六个段,每个段独立的表。每个段都是独立的锁,支持多个线程同时访问同一个表,也提供了复合操作的方法

1.8之后变为CAS算法支持

CopyOnWriteArrayList

每次写入都会复制然后再添加,所以添加性能差,适合并发迭代

//仅仅是一个线程就会抛出并发修改异常
//Exception in thread "Thread-0" java.util.ConcurrentModificationException
//	at //java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
//	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
//	at MyThread.run(juc.java:22)
//	at java.base/java.lang.Thread.run(Thread.java:835)
//static CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>();只要改为这个类就好
public static void main(String[] args) {
        MyThread myThread = new MyThread();
            new Thread(myThread).start();
    }
class MyThread implements Runnable
{    static List<String> list = Collections.synchronizedList(new ArrayList<String>());
    static{
        list.add("AA");
        list.add("BB");
    }
    @Override
    public void run() {
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
            list.add("aa");
        }
    }
}

CountDownLatch闭锁

这是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待

在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行(比如说计算多线程总共执行时间)

CountDownLatch()构造函数的参数为线程数,每结束一个线程减一直至为0

计算多线程执行时间的例子

public class juc {
    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(5);
        LatchDemon latchDemon = new LatchDemon(latch);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5; i++) {
            new Thread(latchDemon).start();
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(System.currentTimeMillis()-start);
    }
}
class LatchDemon implements Runnable{
    private CountDownLatch latch;
    public LatchDemon(CountDownLatch latch) {
        this.latch = latch;
    }
    @Override
    public void run() {
        synchronized (this)
        {
            try {
                for (int i = 0; i < 50000; i++) {
                    if (i%2==0) {
                       // System.out.println(i);
                    }
                }
            }finally {
                latch.countDown();
            }
        }
    }
}

创建一个线程的新方式(实现callable接口)

可以看出来是一个带有泛型的接口,且call()方法带有返回值,且接口泛型为返回值类型,和可以抛出异常

//执行callable方式需要FutureTask实现类的支持,用于接收返回值
//FutureTask是 RunnableFuture接口(这个接口继承runnable)的实现类
//如果调换获得结果和启动的顺序的话,程序无法结束
public class juc {
    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        FutureTask<Integer> result = new FutureTask<>(threadDemo);
        new Thread(result).start();
        //接受结果,只有对应线程结束后才能执行get()方法
        //类似于闭锁
        try {
            System.out.println(result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
class ThreadDemo implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        int sum=0;
        for (int i = 0; i <=100; i++) {
            sum+=i;
        }
        return sum;
    }
}

同步锁lock

如何解决线程安全问题

synchronized:隐式锁

1同步代码块

2同步方法

jdk1.5之后

3同步锁Lock:显式锁,需要lock()方法上锁,必须通过unlock()方法释放锁

只允许一个线程持有锁

//只有拿到锁的线程能运行
public class juc{
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(ticket,"1").start();
        new Thread(ticket,"2").start();
        new Thread(ticket,"3").start();

    }
}
class Ticket implements Runnable{
    private int ticket=10;
    Lock lock=new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (ticket>0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + --ticket);
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

生产者和消费者案例-虚假唤醒

基础的等待唤醒机制

何为虚假唤醒?

虚假唤醒就是一些obj.wait()会在除了obj.notify()和obj.notifyAll()的其他情况被唤醒,而此时是不应该唤醒的。

解决的办法是基于while来反复判断进入正常操作的临界条件是否满足,自旋锁:

如果把库存上限改小(1),由于生产者现象睡眠了200毫秒,因而可能产生的情况是最后消费者线程循环走完了然后就真的结束了,而生产者线程由于wait没有线程来唤醒,所以最终导致一直等待,因而程序不会结束,控制台就不终止。所以去掉else关键字,确保notifyAll执行。

但是多个消费者生产者线程也会导致货物值为负,通过api文档可以得知将if换为while就好

所以为了避免虚假唤醒问题,应该总是使用在循环中

public class juc{
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producror producror = new Producror(clerk);
        Consumer consumer = new Consumer(clerk);
        new Thread(producror,"pro").start();
        new Thread(consumer,"con").start();
    }
}
class Clerk {
    private int product = 0;

    public synchronized void addProduct() {
        if (product >= 10) {
            System.out.println("满了");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            this.notifyAll();
            System.out.println(Thread.currentThread().getName() + ":" + ++product);
        }
    }

    public synchronized void sale() {
        if (product <= 0) {
            System.out.println("缺货");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            this.notifyAll();
            System.out.println(Thread.currentThread().getName() + ":" + --product);
        }
    }
}
class Producror implements Runnable{
    private Clerk clerk;
    public Producror(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.addProduct();
        }
    }
}
class Consumer implements Runnable{
    private Clerk clerk;
    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.sale();
        }

    }
}

Condition线程通信

Condition接口描述了可能会与锁有关联的条件变量,这些变量在用法上与使用Object.wait访问的隐式监视器类似,但是提供了更强大的功能。单个Lock可能与多个Condition对象关联,为了避免兼容性问题,Condition方法与对应Object版本不同

在Condition对象中,与wait,notify和notifyAll方法对应的分别是await,signal和signalAll

Condition实例实际上被绑定到一个锁上,要为特定Lock实例获得Condition实例要使用用其newCondition()方法

为了防止虚假唤醒的现象,也要使用while确保休眠


public class juc{
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producror producror = new Producror(clerk);
        Consumer consumer = new Consumer(clerk);
        new Thread(producror,"pro").start();
        new Thread(consumer,"con").start();
        new Thread(producror,"pro1").start();
        new Thread(consumer,"con1").start();
    }
}
class Clerk {
    private int product = 0;
    private Lock lock=new ReentrantLock();
    private Condition condition=lock.newCondition();
    public void addProduct() {
        lock.lock();
        try {
            while(product >= 1) {
                System.out.println("满了");
                try {
                   condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            condition.signalAll();
            System.out.println(Thread.currentThread().getName() + ":" + ++product);
        }finally {
            lock.unlock();
        }

    }

    public void sale() {
        lock.lock();
        try {
            while (product <= 0) {
                System.out.println("缺货");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            condition.signalAll();
            System.out.println(Thread.currentThread().getName() + ":" + --product);

        }finally {
            lock.unlock();
        }

    }
}
class Producror implements Runnable{
    private Clerk clerk;
    public Producror(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.addProduct();
        }
    }
}
class Consumer implements Runnable{
    private Clerk clerk;
    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.sale();
        }

    }
}

线程按序交替

按顺序打印ABC五遍

Condition相当于各个线程的监视器,控制各个线程的启动或者休眠

//先获取锁的原因是因为,每一个方法有5个线程在抢夺,所以必须保证同一时刻只有一个线程执行函数或者自旋

为了防止虚假唤醒所以要反复检测临界条件,让线程进入自旋状态,这就是为什么main函数里面for循环启动线程顺序与结果无关

public class juc{
    public static void main(String[] args) {
        Alter alter = new Alter();
        for (int i = 0; i < 5; i++) {
            new Thread(()->alter.C()).start();
            new Thread(()->alter.B()).start();
            new Thread(()->alter.A()).start();
        }
    }
}
 class Alter{
    //添加互斥锁
    private ReentrantLock r = new ReentrantLock();
    private Condition c1 = r.newCondition();
    private Condition c2 = r.newCondition();
    private Condition c3 = r.newCondition();
    private int flag = 1;
    //使用Lock显式锁来锁住当前对象,当当前线程运行A方法时,该对象的BC方法堵塞,处于等待状态
    //使用await()和signal()是当前线程等待和唤醒其它线程
    //要注意的是使用显式锁需要自己进行加锁和解锁操作
    public void A()  {
        r.lock();
        try {
           while (flag != 1) {
                c1.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("A");
        flag = 2;
        c2.signal();
        r.unlock();
    }

    public void B() {
        r.lock();
        try {
            while (flag != 2) {
                c2.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("B");
        flag = 3;
        c3.signal();
        r.unlock();
    }

    public void C() {
        r.lock();
        try {
            while (flag != 3) {
                c3.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("C");
        flag = 1;
        c1.signal();
        r.unlock();
    }
}

读写锁(ReadWriteLock)

多个线程写入要加锁,一个读一个写也要加锁,但多个线程读不要加锁

读锁可以多个线程并发的持有,写锁是独占的

//加锁就是确保写入时,写入线程独占共享变量,其他线程无法访问
//如果不加锁,那么每个读线程得到的结果不一致,
public class juc {
    public static void main(String[] args) {
        Demon demon = new Demon();
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                demon.set((int)(Math.random()*101));
            }
        } ,"write").start();
        for (int i = 0; i < 100; i++) {
            new Thread(()->demon.get()).start();

        }
    }
}
class Demon{
    private int number=0;
    private ReadWriteLock lock=new ReentrantReadWriteLock();
    public void get(){
        lock.readLock().lock();
        System.out.println(Thread.currentThread().getName() + ":" + number);
        lock.readLock().unlock();
    }
    public void set(int number){
        lock.writeLock().lock();
        System.out.print(Thread.currentThread().getName());
        this.number=number;
        lock.writeLock().unlock();
    }

}

线程synchronized八种情况

1两个普通同步方法结果BA,实际情况中可看到延迟现象,证明先执行了printB()方法

2新增普通方法printC(),则是CBA

3如果是两个对象,则是ACB

        var numberPrint=new NumberPrint();
        var numberPrint2=new NumberPrint();
        new Thread(()->numberPrint.printB()).start();
        new Thread(()->numberPrint2.printA()).start();
        new Thread(()->numberPrint.printC()).start()

4将printB修改为静态方法则输出为AB

5如果printA和printB均为静态方法,那么会看到延迟,然后打印为BA

6如果两个对象,一个静态方法,一个非静态方法 打印为AB

        var numberPrint=new NumberPrint();
        var numberPrint2=new NumberPrint();
        new Thread(()->numberPrint2.printB()).start();
        new Thread(()->numberPrint.printA()).start();

7两个对象,两个静态方法打印为BA

    public static void main(String[] args) {
        var nunberprint=new NumberPrint();
        
        new Thread(()->numberprint.printB()).start();
        new Thread(()->numberprint.printA()).start();
        new Thread(()->numberprint.printC()).start();
    }
}
class NumberPrint{
   synchronized void printA()
    {
        System.out.println("A");
    }
    synchronized void printB()
    {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("B");
            void printC()
    {
        System.out.println("C");
    }
    }
}


因为非静态方法的锁默认为当前实例(this),静态方法的锁是对应的Class实例(只有一个)

某一时刻内,只有个一个线程能持有锁,无论几个方法

线程池

简单应用

维护了一个线程队列,保存所有等待的线程,避免了创建与销毁带来的性能消耗

结构

java.util.concurrent.Excutor 线程使用和调度的根接口

​ |->**.ExcutorService子接口,线程池的主要接口

​ |->ThreadPoolExecutor 线程池的实现类

​ |->ScheduledExecutorService子接口负责线程的调度

​ |->ScheduledThreadPollExecutor继承ThreadPoolExecutor,实现了

​ ScheduledExecutorService

对应工具Executors

newFixedThreadPool():创造固定大小的线程池

newCachedThreadPool():缓存线程池数量不固定,根据需求自动更改数量

newSingleThreadExecutor():创建只有一个线程的线程池

newScheduledThreadPool():创建一个调度的线程池

newScheduledThreadPool():创建固定大小的线程,可以延迟或定时执行任务

public class juc {
    public static void main(String[] args) {
        //创建
        ExecutorService pool = Executors.newFixedThreadPool(5);
        //提交任务可以cllable也可以runable
        for (int i = 0; i < 10; i++) {
            pool.submit(()-> System.out.println(Thread.currentThread().getName()));
       }

        var future =pool.submit(()->
        {
            int sum=0;
            for (int i = 0; i < 101; i++) {
                sum+=i;
            }
            return sum;
        });
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //关闭,shutdown()会等待所有线程结束以后再关闭,且不接受新任务,shutdownNow()是直接关闭
        pool.shutdown();

    }

}

线程的调度

//对于第二个参数为延迟的的时间个数,第三个为一个枚举类型是第二个参数的单位
//第一个也是接受callable和runable的
public class juc {
    public static void main(String[] args)throws Exception {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 11; i++) {
            ScheduledFuture<Integer> result = pool.schedule(() -> {
                int sum = 0;
                for (int j = 0; j <= 100; j++) {
                    sum += j;
                }
                return sum;
            },
             3, TimeUnit.SECONDS);
            System.out.println(result.get());
        }
        pool.shutdown();
    }
}