JUC学习日记

JUC

什么是JUC

java.util.concurrent

java.util.concurrent.atomic

java.util.concurrent.locks

java.util.function

进程和线程

进程是线程的集合,一个进程至少有一个线程,进程是操作系统调度的最小单位

线程是CPU调度的最小单位

java默认有两个线程–>main和GC

开启线程的方法: Thread,Runnable,Callable

但其实,JAVA本身是没有权限开启新线程的,它是调用了本地方法库(native关键字修饰的,其实是C++库函数)start0();来开启新线程的

并发和并行

并行(多个人一起行走)多个人当然可同时行走(多核)

并发(多个线程操作同一个资源),其实在一个时间点只有一个线程获取了这个资源(单核)

并发编程的本质: 充分利用CPU的资源

线程的状态

Thread.State//枚举类型,一共有6种状态,具体看注释
    public enum State {
        /**新生
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**运行
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**阻塞
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**等待,死死地等
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called {@code Object.wait()}
         * on an object is waiting for another thread to call
         * {@code Object.notify()} or {@code Object.notifyAll()} on
         * that object. A thread that has called {@code Thread.join()}
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**超时等待
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**终止
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

wait和sleep的区别

1.来自不同的类

wait->Object

sleep->java.util.concurrent.TimeUnit

2.关于锁的释放

wait 会释放锁,sleep 睡觉了,抱着锁睡,不释放

3.使用的范围不同

wait 必须在同步代码段中使用!

sleep 可以在任何地方睡

Lock锁(重点)

首先我们要知道锁是怎么用的,面对同一个对象的同一把锁,多个线程只能有一个线程获取改锁,而只有获取这把锁的线程才能不被阻塞的执行下去.当获得锁的对象释放锁的时候,根据线程的先后等待顺序来进行使获得锁的锁叫公平锁,根据CPU随机分配锁的叫公平锁

传统synchronized

本质上就是一个队列

Lock接口

他有三个实现类 ReentrantLock ,ReentrantReadWriteLock.ReadLock ,ReentrantReadWriteLock.WriteLock

公平锁就是不能插队

非公平锁就是可以插队(默认选这个)

所以在不知道线程那个先到的情况下,没必要搞公平锁呗

Lock的使用方法

  1. new ReentrantLock();
  2. Lock.lock();//加锁
  3. 解锁

实例:

public class test1 {
    public static void main(String[] args) {
        Tickets tickets=new Tickets(30);
        new Thread(()->{for(int i=0;i<30;i++)tickets.seal();},"A").start();
        new Thread(()->{for(int i=0;i<30;i++)tickets.seal();},"B").start();
        new Thread(()->{for(int i=0;i<30;i++)tickets.seal();},"C").start();
    }
}

class Tickets{

    private int num;
    public Tickets(int num){
        this.num=num;
    }
    Lock lock=new ReentrantLock();
    public void seal(){
        //不要犯傻吧Lock定义在这里不然每次运行都是生成不一样的锁,和没加没啥区别的...
        lock.lock();
        try {
            if(num<=0) return;
            System.out.println(Thread.currentThread().getName()+"已经吧第"+num+"张票卖出去了!");
            num--;
            System.out.println("剩余"+num+"张票");
        }catch (Exception e){
            System.out.println(e);
        }finally {
            lock.unlock();
        }
    }

}

Lock和Synchronized

  1. synchronized 内置的JAVA关键字,LOCK是一个JAVA类
  2. Synchronized 无法判断获取锁的状态,Lock可以判断是否获得了奖
  3. Synchronized 会自动释放锁,lock必须要手动释放锁,不释放就会产生死锁
  4. Synchronized 线程1(获得锁,阻塞),线程2(等待,傻傻的等);Lock锁就不一定会等待不会
  5. Synchronized 可重入锁,不可以中断的,非公平的;lock,可重入锁,可以判断锁,非公平(可以自己设置)
  6. Synchronized 适合锁少量的代码同步问题,LOCK适合锁大量的同步代码

Lock的线程通信(condition)

可以通过JUC的condition来精确控制某个线程的状态(休眠或唤醒)

condition.signal()唤醒

condition.await()休眠

condition的使用方法:一个condition只对应一个锁和线程,所以通过lock.newcondition()方法来获取对象,通过condition的方法来实现线程的休眠和唤醒

按理说这种唤醒线程的方式应该是不会触发虚假唤醒的,因为他是对特定线程的唤醒操作,感觉很安全很可靠的样子,事实上也确实是这个样子,但为了保险还是用while循环吧

实例:

public class test {

    public static void main(String[] args) {
        work w=new work();
        new Thread(()->{for (int i=0;i<10;i++) w.doitA();}).start();
        new Thread(()->{for (int i=0;i<10;i++) w.doitB();}).start();
        new Thread(()->{for (int i=0;i<10;i++) w.doitC();}).start();

    }
}

class work{
    Lock lock=new ReentrantLock();
    private int num=1;
    Condition condition1=lock.newCondition();
    Condition condition2=lock.newCondition();
    Condition condition3=lock.newCondition();

    public void doitA() {
        lock.lock();
        while(num!=1) {
            try {
                condition1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("A~~~~~~~~~~~");
        num=2;

        condition2.signal();
        lock.unlock();
    }
    public void doitB() {
        lock.lock();
        while(num!=2) {
            try {
                condition2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("B~~~~~~~~~~~");
        num=3;

        condition3.signal();
        lock.unlock();
    }
    public void doitC() {
        lock.lock();
        while(num!=3) {
            try {
                condition3.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("C~~~~~~~~~~~");
        num=1;

        condition1.signal();
        lock.unlock();
    }
}

注意,只有获取锁的线程才可以调用signal()方法!!!

锁的都是什么东西

锁只会锁两样东西:Class 对象和普通对象

我们只需要知道什么时候锁什么对象就行,当锁静态方法的时候其实是锁的Class 对象(似乎只有这一种锁Class对象的方法?),和锁普通方法是没有关联的,因为锁的不是同一个对象,所以他们之间是不会存在阻塞的情况的

普通集合/列表的线程不安全

arraylist在多线程的情况下调用add可能会出现java.util.ConcurrentModificationException

问题代码:

public class Tesst {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        for(int i=0;i<100;i++)
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);//要读出来,不然不会报错....
            }).start();
    }
}

可代替的安全的列表类

List<String> list=new ArrayList<>();//不安全
List<String> list=new Vector<>();//不推荐,太老了
List<String> list=new CopyOnWriteArrayList<>();//推荐
List<String> list= Collections.synchronizedList(new ArrayList<>());//不是很推荐

callable接口的实现

通过callable接口来实现多线程有一个好处就是可以得到线程的返回值线程也可以抛出异常(而不是遇见异常就崩溃…)话说别的方法不能跑异常嘛?

他本质上是Runnable的一个实现类,正因如此,可以做到下面代码中第五行的样子

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test t = new test();
        FutureTask futureTask = new FutureTask(t);//适配器模式,FutureTask作为适配器(中转站),他本质上是Runnable的一个实现类,起到了一个中转的作用而已
        new Thread(futureTask,"A").start();//启动线程
        Integer ans=(Integer) futureTask.get();//获取返回值
        System.out.println(ans);
    }
}

class test implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("Yes,I know it!");
        return 10;
    }
}

要注意一点,只有线程启动了,FutureTask.get()才能得到值,否则会进入阻塞状态?这意味着5行和第6行代码是不能更换次序的

对比以下几份代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PRY7l1bY-1620326927236)(C:\Users\张博文\AppData\Roaming\Typora\typora-user-images\image-20210406012432426.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3a7Ls6nU-1620326927238)(C:\Users\张博文\AppData\Roaming\Typora\typora-user-images\image-20210406012527532.png)]

可以观察到,一个FutreTask对象只能被执行一次的样子,老师说,这是因为结果被缓存了,代码是不会执行多次的结果(其实不是很懂)不过他确实有个isDone的方法,至少能反映出它确实只能执行一次…

CountDownLatch

public static void main(String[] args) throws InterruptedException {
    CountDownLatch countDownLatch=new CountDownLatch(5);
    for(int i=0;i<5;i++)
    new Thread(()->{
        System.out.println(Thread.currentThread().getName());
        countDownLatch.countDown();
    },String.valueOf(i)).start();
    countDownLatch.await();//等待计数降为0后关闭阻塞
    System.out.println("job has done");
}

这个类用于辅助调控同步没有LOCK+condition灵活

主要方法有两个:

  1. countDownLatch.countDown();减少计数
  2. countDownLatch.await();阻塞,直到计数为0

信号量(semaphore)

字面意思的信号量

public class SemaphoreTest {
    public static void main(String[] args) {
        Past p=new Past();
        Carwork carwork=new Carwork(p);
        for (int i = 1; i <= 6; i++) {
            new Thread(new FutureTask(carwork),String.valueOf(i)).start();
        }
    }
}

class Past{
    Semaphore semaphore=new Semaphore(3);//初始化车位
    public void goIn() throws InterruptedException {
        semaphore.acquire();
        System.out.println(Thread.currentThread().getName()+"has come!");
    }
    public void goAway() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"has gone!");
        semaphore.release();
    }
}

class Carwork implements Callable {

    Past past;
    public Carwork(Past p){
        past=p;
    }
    @Override
    public Object call() throws Exception {
        past.goIn();
        TimeUnit.SECONDS.sleep(2);
        past.goAway();
        return null;
    }
}

读写锁

读者写者问题的完美解(方便的一批),当然通过计算机操作系统我们知道这个问题可以通过两个信号量来解决(忘完了,百度了才想起来这个东西怎么写…)

下面是信号量的解决方法:

public class ReadAndWrite {
    public static void main(String[] args) throws InterruptedException {
        Text text = new Text();
        for(int i=0;i<10;i++){
            new Thread(()->{
                try {
                    text.push(Thread.currentThread().getName()+"writen"+'\n');
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"writer"+i).start();
        }
        for(int i=0;i<20;i++){
            new Thread(()->{
                try {
                    text.get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"reader"+i).start();
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
        text.show();
    }

}

class Text{
    Semaphore countable=new Semaphore(1);
    Semaphore writeable=new Semaphore(1);
    int count=0;
    String content="";
    public void push(String s) throws InterruptedException {
        while(true){
            countable.acquire();
            if(count==0){
                writeable.acquire();
                System.out.println(Thread.currentThread().getName() + " is working");
                content=content+s;
                System.out.println(Thread.currentThread().getName() + " has worken");
                writeable.release();
                countable.release();
                break;
            }
            countable.release();
        }
    }
    public void get() throws InterruptedException {
        countable.acquire();
        if(count==0) writeable.acquire();
        count++;
        countable.release();
        System.out.println(Thread.currentThread().getName()+" is reading");
        System.out.println(Thread.currentThread().getName()+" has gone");
        countable.acquire();
        count--;
        if(count==0) writeable.release();
        countable.release();
    }
    public void show(){
        System.out.println(content);
    }
}

这个是读写锁的解决方法,要注意的是ReadWriteLock是一个接口!!!官方只实现了一个实现类就是ReentrantReadWriteLock;

ReadWriteLock里内置了两个锁,分别是writeLock(排它锁),readLock(共用锁)

public class ReadWriteLockDemo {

    public static void main(String[] args) throws InterruptedException {
        Text01 text01 = new Text01();
        for(int i=0;i<10;i++)
        {
            new Thread(()->{text01.push(Thread.currentThread().getName()+" say:balabala");},"writer"+i).start();
        }
        for(int i=0;i<10;i++)
        {
            new Thread(()->{text01.get();},"reader"+i).start();
        }
        TimeUnit.SECONDS.sleep(1);
        text01.show();
    }
}


class Text01{
    ReadWriteLock lock=new ReentrantReadWriteLock();
    String content="";

    public void push(String s){
        lock.writeLock().lock();
        System.out.println(Thread.currentThread().getName()+" is working!");
        content=content+s+'\n';
        System.out.println(Thread.currentThread().getName()+" has gone!");
        lock.writeLock().unlock();
    }
    public void get(){
        lock.readLock().lock();
        System.out.println(Thread.currentThread().getName()+" is reading!");
        System.out.println(Thread.currentThread().getName()+" has gone!");
        lock.readLock().unlock();
    }

    public void show(){
        System.out.println(content);
    }

}

阻塞队列(BlockingQueue)

要知道阻塞是指当前线程停止运行等待…(所以如果问到队列长度不够时,可以考虑阻塞队列的!)

public class BlockingQueueDemo {
    /**
     *  BlockingQueue 有四组入队出队的方式分别代表着有异常,无异常,阻塞(一直等待),阻塞(超时等待)
     *  add和remove代表着有异常的入队和出队
     */
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue queue = new ArrayBlockingQueue<String>(3);
        //方法一:抛出异常
//        System.out.println(queue.add("A"));
//        System.out.println(queue.add("B"));
//        System.out.println(queue.add("C"));
        queue.add("D");//爆出异常 IllegalStateException
//        System.out.println(queue.element());//返回队首元素,如果队列为空则抛出异常
//        queue.remove();
//        queue.remove();
//        queue.remove();
//        queue.remove();//爆出异常 NoSuchElementException
        //方法二:有返回值,没有异常
//        System.out.println(queue.offer("A"));
//        System.out.println(queue.offer("B"));
//        System.out.println(queue.offer("C"));
//        System.out.println(queue.offer("D"));//不会报错但会返回false
//        //推出队列
//        System.out.println(queue.peek());//返回队首元素,为空返回null
//        System.out.println(queue.poll());
//        System.out.println(queue.poll());
//        System.out.println(queue.poll());
//        System.out.println(queue.poll());//返回null
        //方法三:一直阻塞来等待队列有空位,没有返回值
//        queue.put("A");
//        queue.put("B");
//        queue.put("C");
        queue.put("D");//会一直阻塞,以至于程序假死
//        System.out.println(queue.take());
//        System.out.println(queue.take());
//        System.out.println(queue.take());
//        System.out.println(queue.take());//会一直阻塞等待队列队列填入数据
        //方法四:超时等待,到某个时间后取消等待
        queue.offer("A");
        queue.offer("B");
        queue.offer("C");
        queue.offer("D",2, TimeUnit.SECONDS);//只有这个是等待2s之后如果没有空位就返回null
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll(2,TimeUnit.SECONDS));//同上
    }
}

线程池(重点)

3大方法.7打阐述,4种拒绝

池化技术:事先准备好一些资源,有人要用,直接给,用完后还给我

线程池本质上就是优化资源的利用,降低资源的小号,提高相应的速度,方便管理从而达到线程服用,可以控制最大并发数,管理线程

阿里的代码规范不提倡(强制不)使用Executors,而是通过ThreadPoolExcutor来创建线程池,原因是Executors容易OOM(原因是它的请求的最大值实在是太大了),但是线程池的三大方法与Executors有关.所以我们还是要会,推荐使用ThreadPoolExecutor,自定义7打参数

池化思想:

创建线程池的三种方法

    public static void main(String[] args) {
//        ExecutorService executorService = Executors.newSingleThreadExecutor();//容量为1的线程池
//        ExecutorService executorService = Executors.newFixedThreadPool(5);//容量为五的线程池
        ExecutorService executorService = Executors.newCachedThreadPool();//可变容量的线程池
        for(int i=0;i<100;i++)
        executorService.execute(()->{
            System.out.println(Thread.currentThread().getName());
        });
        executorService.shutdown();
    }

线程池的7大参数

public ThreadPoolExecutor(int corePoolSize,//核心线程池的大小
                          int maximumPoolSize,//线程池的最大大小
                          long keepAliveTime,//非核心线程超时多久会被释放
                          TimeUnit unit,//时间单位
                          BlockingQueue<Runnable> workQueue,//等待队列
                          ThreadFactory threadFactory,//线程工厂
                          RejectedExecutionHandler handler) {//拒绝策略
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

在这里插入图片描述

自定义线程池

最大线程的定义方式

  1. CPU密集型 几个处理器就是几,可以使CPU效率最高 //System.out.println(Runtime.getRuntime().availableProcessors())获取计算机处理器数目
  2. IO 密集型 十分消耗IO的线程数的两倍
/**
 * 4种拒绝策略
 * ThreadPoolExecutor.AbortPolicy() 直接抛出异常
 * ThreadPoolExecutor.CallerRunsPolicy() 哪里来的回哪里去,依靠原线程执行
 * ThreadPoolExecutor.DiscardPolicy() 没有异常,直接丢掉任务
 * ThreadPoolExecutor.DiscardOldestPolicy()会推出阻塞队列里最老的未处理请求,然后尝试将这个请求加入队列,也不会抛出异常
 */
public class ThreadPoolExcutorDemo {
    public static void main(String[] args) {

        ThreadPoolExecutor threadPool =new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );
        for(int i=0;i<9;i++)
        {
            threadPool.execute(()->{
                System.out.println(Thread.currentThread().getName());
            });
        }
        threadPool.shutdown();
    }
}

4个函数式接口

没啥好说的,

Function<T,R>接口 既有参数,也有返回值

Consumer 接口,只有参数没有返回值

Supplier 接口,只有返回值,没有参数

Predicate 接口 返回值是Boolen,有参数

既然是函数式接口,那么他们里面只有一个待实现方法,可以使用lamda表达式快速创建而不用写匿名内部类或者新建实现类来实现接口

public static void main(String[] args) {
    Function<String,String> function=(a)->{return a.toLowerCase(Locale.ROOT);};
    System.out.println(function.apply("ADEWS"));
}

其他的都类似这样,只是执行函数可能不是apply而已

链式编程&Stream流式计算

这部分代码需要上边的函数式接口作为铺垫

public class StreamDemo {


    public static void main(String[] args) {
        Message m1=new Message("A",1,30);
        Message m2=new Message("B",2,3);
        Message m3=new Message("C",3,20);
        Message m4=new Message("D",4,24);
        Message m5=new Message("E",5,10);
        Message m6=new Message("F",6,11);
        Message m7=new Message("G",7,19);
        Message m8=new Message("H",8,25);
        ArrayList<Message> list=new ArrayList<>(10);
        list.add(m1);
        list.add(m2);
        list.add(m3);
        list.add(m4);
        list.add(m5);
        list.add(m6);
        list.add(m7);
        list.add(m8);
        list.stream()
                .filter((a) -> { return a.id % 2 == 0; })
                .filter((a)->{return a.age>=20;})
                .sorted((a,b)->{return b.compareTo(a);})
//                .map((a)->{return a.name.toLowerCase(Locale.ROOT);})
                .forEach(System.out::println);
    }

}


class Message implements  Comparable<Message>{
    //这里为了方便就全部声明为public了,正式的编码一定不要这么傻..
    public String name;
    public int id;
    public int age;
    public Message(String _name,int _id,int _age)
    {
        name=_name;
        id=_id;
        age=_age;
    }

    @Override
    public String toString() {
        return "Message{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Message message) {
        return this.name.compareTo(message.name);
    }


}

fork/join模型

就知道是分治的思想,但是感觉知道了分治的思想不用这个东西也能写的样子,下面举个例子

public class BigSum {
    private static int len=10000;

    public static void main(String[] args) {
        long start= System.currentTimeMillis();
//        try {
//            System.out.println(getsum(1, 1_0000_00001));
//        } catch (ExecutionException e) {
//            e.printStackTrace();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        long sum=0;
        for(int i=1;i<1_0000_00001;i++)
            sum+=i;
        long end= System.currentTimeMillis();
        System.out.println(end - start);
    }

    public static long getsum(int s, int e) throws ExecutionException, InterruptedException {
        long sum=0l;
        if(e-s>=len){
            Work work = new Work(s, e);
            FutureTask task=new FutureTask(work);
            new Thread(task).start();
            return (long) task.get();
        }
        else {
            int mid=(s+e)>>1;
            Work work1 = new Work(s, mid);
            Work work2 = new Work(mid, e);
            FutureTask task1=new FutureTask(work1);
            FutureTask task2=new FutureTask(work2);
            return (long) task1.get()+(long) task2.get();
        }
    }

}

class Work implements Callable<Long>{
    int s,e;
    public Work(int _s,int _e) {
        s = _s;
        e = _e;
    }

    @Override
    public Long call() throws Exception {
        long sum=0L;
        for(int i=s;i<e;i++) sum+=i;
        return sum;
    }
}

异步

等我学成归来再写吧…(学的不明不白的…)

JMM

谈谈你对Volatile的理解

他是JAVA虚拟机提供的轻量级的同步机制

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

什么是JMM

JVM:java虚拟机

JMM:java内存模型,不存在的东西,是个概念,约定

关于JMM的一些同步的约定

  1. 线程解锁前,必须吧共享变量立刻刷回主存
  2. 线程加锁前,必须读取主存中的最新值到工作内存中!
  3. 加锁和解锁是同一把锁

img

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
  • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
  • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
  • write  (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

JMM对这八种指令的使用,制定了如下规则:

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

volatile

public class VolatileDemo {
    private static int num=0;
    public static void main(String[] args) {
        new Thread(()->{
            while(num==0);
            System.out.println("DONE");
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num=1;
        System.out.println("11111");
    }

}

由于每个线程都有自己的工作内存,所以这个是会死循环的

private volatile static int num=0;

但是吧num加一个修饰就可以让他们实现同步,这个不是锁也不是sychronized,似乎是原子类的操作,本质上是CAS算法,效率很高,涉及到对操作系统的直接操作

不同的线程对同一个操作数据的值始终是统一的,这个就叫做可见性,很显然Volatile可以实现可见性,但是它没有原子性.

不可打断的操作,不能分割的操作叫原子操作,但是可惜的是Volatile不能保证原子性,我们可以通过多线程的自增操作发现,就不上代码了.

一些观念上的问题

线程就操作资源类,没有任何附属的操作!,OOP降低耦合性方法会报异常

什么是资源类

1.属性,方法

2.能不继承就不继承,能不实现就不实现接口–>降低耦合度

多个线程同时用一个资源类就可能会产生线程不安全

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值