Java 多线程JUC



多线程

创建线程的方式

1.继承Thread类
public class test extends Thread {
	@Override 
	public void run(){
		System.out.println("Thread")
	}
	public static void main(String[] args){
		Test test = new Test();
		test.start();
	}
}
2.实现Runnable接口
public class test implments Runnable{
	@Override 
	public void run(){
		System.out.println("Thread")
	}
	public static void main(String[] args){
		Test test = new Test();
		Thread thread = new Thread(test);
		thread.start();
	}
}
3.实现callable接口
public class CallableFutureTaskDemo implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int t = 1;
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + (t++));
        }
        return t;
    }

    public static void main(String[] args) {
        Callable<Integer> cftd1 = new CallableFutureTaskDemo();
        Callable<Integer> cftd2 = new CallableFutureTaskDemo();
        FutureTask<Integer> ft1 = new FutureTask<>(cftd1);
        FutureTask<Integer> ft2 = new FutureTask<>(cftd2);
        Thread t1 = new Thread(ft1);
        Thread t2 = new Thread(ft2);
        t1.setName("Thread1");
        t2.setName("Thread2");
        t1.start();
        t2.start();
        try {
            System.out.println(ft1.get());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

线程的状态

  • 创建状态:新建了一个线程对象。
  • 就绪状态:线程创建了对象后,其他线程调用了该对象的start方法。该线程变得可运行,除了CPU的使用权其他运行时所需要的资源都已经全部获得。
  • 运行状态:就绪状态的线程得到CPU的资源,执行程序代码。
  • 阻塞状态:由于某些原因放弃CPU使用权,暂时停止运行,知道线程进入就绪状态,才有机会转到运行状态。
    1. 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池,直至执行notify()或者notifyAll()方法才能唤醒该线程。
    2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
    3. 运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时, 或者I/O处理完毕时,线程重新转入就绪状态
  • 死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

sleep() 和 yield()和join()

  • sleep(时间):每一个对象都会有一个锁,sleep不会释放锁,可以模拟网络延时、倒计时。
  • yield():礼让线程让当前执行的线程暂停,但不阻塞,将线程从运行在状态转为就绪状态,礼让不一定会成功。
  • join():合并线程,待此线程执行完毕,在执行其他的线程其他线程阻塞。

线程同步

并发:同一个对象被多个线程操作。

synchronized同步方法: 同步方法必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行就独占该锁。直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁继续执行。

synchronized同步代码块:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。

死锁:指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

产生死锁的必要条件:
  • 互斥条件:一个资源每次只能被一个进程使用
  • 请求保持条件:一个进程因请求资源而阻塞时,对已获得资源保持不放。
  • 不剥削条件:进程以获得的资源,在未使用之前,不能强行剥夺
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
synchronized 和lock的区别
  1. synchronized 是java 内置的关键字,在jvm 层面Lock是一个类
  2. synchronized 是无法获取锁的状态,Lock可以判断是否获取到锁的状态。
  3. synchronized 可以自动释放锁,Lock需要在finally中手动释放锁,否则容易造成线程死锁。
  4. 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1
    阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以
    不用一直等待就结束了。
  5. synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
  6. Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

读写锁(ReadWriteLock)

独占锁(写锁):指该锁一次只能被一个线程锁持有。ReentrantLock和 Synchronized 都是独占锁。

共享锁(读锁):该锁可被多个线程所持有。

public class ReadWriteLock {
    //读-读 不能共存    读-写可以共存   写-写 不可以共存
    public static void main(String[] args) {
        Mycache mycache = new Mycache();

        for (int i = 1; i <= 5; i++) {
            int t = i;
            new Thread(()->{
                mycache.put(t+"",t+"");
            }).start();
        }

        for (int i = 1; i <= 5; i++) {
            int t = i;
            new Thread(()->{
                mycache.get(t+"");
            }).start();
        }
    }
}

/*
* 自定义缓存
* */
class Mycache{
    private volatile Map<String,Object> map = new HashMap<>();
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //写
    public void put(String key,String value){
        try {
            readWriteLock.writeLock().lock();//加写锁
            System.out.println(Thread.currentThread().getName()+"写入"+key);
            map.put(key,value);

            System.out.println(Thread.currentThread().getName()+"写入成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }

    }
    //存
    public void get(String key){
        try {
            readWriteLock.readLock().lock();//加读锁
            System.out.println(Thread.currentThread().getName()+"读"+key);

            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+"读成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }

    }
}

阻塞队列

阻塞队列是一个队列,在数据结构中起的作用如下图:

在这里插入图片描述

当队列是空的,从队列中获取元素的操作将会被阻塞。
当队列是满的,从队列中添加元素的操作将会被阻塞。
试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素。

试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完
全清空,使队列变得空闲起来并后续新增。

ArrayBlockingQueue:由数组结构组成的有界阻塞队列。

LinkedBlockingQueue:由链表结构组成的有界阻塞队列。
PriorityBlockingQueue:支持优先级排序的无界阻塞队列
DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。
LinkedTransferQueue:由链表组成的无界阻塞队列。
LinkedBlockingDeque:由链表组成的双向阻塞队列。

处理方式抛出异常返回特殊值阻塞等待超时等待
添加add(e)offer(e)put(e)一直等待offer(e,超时时间,超时单位)超时结束TimeUnit.Seconds
移除remove()poll()take()poll(time,unit)
检查队首元素element()peek()不可用不可用

SynchronousQueue(同步队列)

**SynchronousQueue:**与其他的 BlockingQueue 不同,SynchronousQueue是一个不存储元素的 BlockingQueue 。每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。

public class SynchronousQueueDemo {
    
  public static void main(String[] args) {
    //put 一个值必须take取出来,否则不能put进去值
    BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
    new Thread(()->{
      try {
        System.out.println(Thread.currentThread().getName()+" put1");
        blockingQueue.put("1");
        System.out.println(Thread.currentThread().getName()+" put2");
        blockingQueue.put("2");
        System.out.println(Thread.currentThread().getName()+" put3");
        blockingQueue.put("3");
     } catch (InterruptedException e) {
        e.printStackTrace();
     }
   },"T1").start();
    new Thread(()->{
      try {
        TimeUnit.SECONDS.sleep(3);
      
 		System.out.println(Thread.currentThread().getName()+blockingQueue.take());
        TimeUnit.SECONDS.sleep(3);
      
 		System.out.println(Thread.currentThread().getName()+blockingQueue.take());
        TimeUnit.SECONDS.sleep(3);
      
 		System.out.println(Thread.currentThread().getName()+blockingQueue.take());
     } catch (InterruptedException e) {
        e.printStackTrace();
     }
   },"T1").start();
 }
}

线程池

线程池的三大方法

Executors.newFixedThreadPool(int):执行长期任务性能好,创建一个线程池,一池有N个固定的线程,有固定线程数的线程

Executors.newSingleThreadExecutor():只有一个线程

**Executors.newCachedThreadPool();**执行很多短期异步任务,线程池根据需要创建新线程,需要多少创建多

public class Demo {
    public static void main(String[] args) {
        
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        ExecutorService threadPool = Executors.newCachedThreadPool();
        try {

            for (int i = 0; i < 100; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName());
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}
ThreadPoolExecutor 七大参数

**corePoolSize:**核心线程数

**maximumPoolSize:**最大线程数

**keepAliveTime:**空闲的线程保留的时间

TimeUnit:空闲线程的保留时间单位

TimeUnit.DAYS;        //天
TimeUnit.HOURS;       //小时
TimeUnit.MINUTES;      //分钟
TimeUnit.SECONDS;      //秒
TimeUnit.MILLISECONDS;    //毫秒
TimeUnit.MICROSECONDS;    //微妙
TimeUnit.NANOSECONDS;    //纳秒

**BlockingQueue:**阻塞队列

**ThreadFactory:**线程工厂,用来创建线程,一般默认即可

RejectedExecutionHandler :拒绝策略

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

使用ThreadPoolExecutor创建线程池

public class Demo01 {
    public static void main(String[] args) {
        //最大线程如何去设置?
        //CPU密集型 Runtime.getRuntime().availableProcessors()  获取CPU的在最大线程数
        //IO密集型  比业务中的大型线程多就可以了
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                5,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        try {

            for (int i = 0; i < 100; i++) {
                poolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName());
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            poolExecutor.shutdown();
        }
    }

}

四大函数式接口

**Function()**函数型接口,有一个输入,有一个输出
public static void main(String[] args) {
  // 函数式接口,可以改为 lambda 表达式
  //Function<String,Integer> function = new Function<String, Integer>() {
  //  @Override
  //  public Integer apply(String s) {
  //    return 1024;
  //  }
  //};
  // 简写
  Function<String,Integer> function = s->{return s.length();};
  System.out.println(function.apply("abc"));
 
}
**Predicate()**断定型接口,有一个输入参数,返回只有布尔值。
public static void main(String[] args) {
  //Predicate<String> predicate = new Predicate<String>() {
  //  @Override
  //  public boolean test(String s) {
  //    return false;
  //  }
  //};
  // 简写
  Predicate<String> predicate = s -> {return s.isEmpty();};
  System.out.println(predicate.test("abc"));
}
Comsumer消费型接口,有一个输入参数,没有返回值
public static void main(String[] args) {
  //    Consumer<String> consumer = new Consumer<String>() {
  //      @Override
  //      public void accept(String s) {
  //
  //      }
  //    };
  // 简写
  Consumer<String> consumer = s -> { System.out.println(s);};
  consumer.accept("abc");
}
Supplier供给型接口,没有输入参数,只有返回参数
public static void main(String[] args) {
  //  Supplier<String> supplier = new Supplier<String>() {
  //    @Override
  //    public String get() {
  //      return null;
  //    }
  //  };
  Supplier<String> supplier = ()->{return "abc";};
  System.out.println(supplier.get());
}

ForkJoin 合并分支

ForkJoin:讲一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果

工作窃取

一个工作线程下会维护一个包含多个子任务的双端队列。而对于每个工作线程来说,会从头部到尾部依次执行任务。这时,总会有一些线程执行的速度较快,很快就把所有任务消耗完了。

package com.example.department.config;

import java.io.FileOutputStream;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.function.LongBinaryOperator;
import java.util.stream.LongStream;

public class ForkJoin extends RecursiveTask<Long> {
    private Long start;
    private Long end;
    private Long temp = 10000L;

    public ForkJoin(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
       /* long start = System.currentTimeMillis();

        Long sum = 0L;
        for (Long i = 1L; i < 10_0000_0000; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println
        (sum +"时间"+(end-start));*/


        //forkJoin 执行
       /* long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();//实现ForkJoin就必须要有ForkJoinPool
        ForkJoinTask<Long> forkJoin = new ForkJoin(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoin);
        Long aLong = submit.get();//提交任务
        long end = System.currentTimeMillis();
        System.out.println(aLong +"时间"+(end-start));*/

        //Stream 并行流
        System.out.println(LongStream.rangeClosed(0L,
		10_0000_0000L).parallel().reduce(0,Long::sum));
    }

    @Override
    protected Long compute() {
        if(end-start>temp){
            //执行分之合并计算
            /*
             *
             * */
            Long mid = (start+end)/2;
            ForkJoin forkJoin1 = new ForkJoin(start,mid);
            forkJoin1.fork();//拆分任务,把任务压入线程队列
            ForkJoin forkJoin2 = new ForkJoin(mid,end);
            forkJoin2.fork();
            return forkJoin1.join() + forkJoin2.join();
        }else{
            Long sum = 0L;
            for (Long i = start; i < end; i++) {
                sum += i;
            }
            System.out.println(sum);
            return sum;
        }
    }
}

异步回调(Future)

为了让程序更加高效,让CPU最大效率的工作,我们会采用异步编程。首先想到的是开启一个新的线程
去做某项工作。再进一步,为了让新线程可以返回一个值,告诉主线程事情做完了,于是乎Future粉墨
登场。然而Future提供的方式是主线程主动问询新线程,要是有个回调函数就爽了。所以,为了满足
Future的某些遗憾,强大的CompletableFuture随着Java8一起来了。

public class Future {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //没有返回值
        /*CompletableFuture<Void> completableFuture = 
        CompletableFuture.runAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "没有返回, update mysql ok");
        });
        System.out.println(11111);
        completableFuture.get();*/
        //有返回值的异步回调
        CompletableFuture<Integer> completableFuture1 = 
        CompletableFuture.supplyAsync(()->{

            System.out.println(Thread.currentThread().getName() + "有返回, update mysql ok");
            return 11;
        });
        completableFuture1.whenComplete((t,u)->{
            //成功回调
            System.out.println(t);
            System.out.println(u);//错误信息
        }).exceptionally((e)->{
            //失败回调
            System.out.println(e.getMessage());
            return 233; //错误返回的信息
        });
    }
}

JMM

volitile 是 Java 虚拟机提供的轻量级的同步机制,三大特性:保证可见性、不保证原子性、禁止指令重排。

JMM的同步约定:
  • 线程解锁前,必须把共享变量的值刷新回主内存
  • 线程加锁前,必须读取主内存的最新值到自己的工作内存
  • 加锁解锁是同一把锁。
JMM的八种操作:
  • 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 Test11 {
    // volatile 不加volatile没有可见性
    // 不加 volatile 就会死循环,这里给大家将主要是为了面试,可以避免指令重排
    private  volatile static int  num = 0;
    public static void main(String[] args) {
        new Thread(()->{
            while(num==0){

            }
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num = 1;
        System.out.println(num);
    }
}

Volatile不保证原子性

不可分割,完整性,也就是某个线程正在做某个具体的业务的时候,中间不可以被加塞或者被分割,需
要整体完整,要么同时成功,要么同时失败。

public class Test13 {
    // volatile 不能保证原子性
    //使用原子操作类,来保证原子性
    private volatile static AtomicInteger num =new AtomicInteger() ;

    public static void add(){
        //a++ 多线程情况下不安全
        num.getAndIncrement();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        //Thread.activeCount 线程的数量
        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(num);
    }
}

单例模式

  • 饿汉式
public class Hungry {
  private Hungry() {
 }
  private final static Hungry hungry = new Hungry();
  public static Hungry getInstance() {
    return hungry;
 }
}
  • 懒汉式
public class LazyMan {
  private LazyMan() {
    System.out.println(Thread.currentThread().getName()+"Start");
 }
  private static LazyMan lazyMan;
  public static LazyMan getInstance() {
    if (lazyMan == null) {
      lazyMan = new LazyMan();
   }
    return lazyMan;
 }
  // 测试并发环境,发现单例失效
  public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
      new Thread(()->{
        LazyMan.getInstance();}).start();
   }
 }
}

改进版

public class Lazy {
    //双重检测锁模式  懒汉式单例
    public Lazy(){
    }
    private volatile static Lazy lazy; //保证不会出现指令重排
    public static Lazy getInstance(){
        if(lazy==null){
            synchronized (Lazy.class){
                if(lazy==null){
                    lazy = new Lazy(); //不是原子操作,可能会出现指令重排
                    /*
                    * 1.分配内存空间
                    * 2.执行构造方法,初始化对象
                    * 3.对象指向空间
                    * */
                }
            }
        }
        return lazy;
    }
    }
  • 静态内部类
public class Test {
  private Holder() {
 }
  public static Test getInstance() {
    return InnerClass.test;
 }
  private static class InnerClass {
    private static final Test test = new Test();
 }
}

CAS

CAS:比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作
内存中的值一致为止。

CAS 应用

CAS 有3个操作数,内存值V,旧的预期值A,要修改的更新值B。且仅当预期值A 和 内存值 V 相同时,
将内存值 V 修改为B,否则什么都不做。

CAS 的缺点

1、循环时间长开销很大。可以看到源码中存在 一个 do…while 操作,如果CAS失败就会一直进行尝试。
2、只能保证一个共享变量的原子操作。

  • 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作。但是:
  • 对多个共享变量操作时,循环CAS就无法保证操作的原子性,这时候就可以用锁来保证原子性。
  • 会出现ABA问题
public class CAS {

    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //如果期望值达到了就更新,否则不更新
        atomicInteger.compareAndSet(2020,2021);
        System.out.println(atomicInteger.get());
        atomicInteger.getAndIncrement();
        atomicInteger.compareAndSet(2020,2021);
        System.out.println(atomicInteger.get());
    }
}

ABA问题用原子引用 AtomicReference解决
public class CAS {

    public static void main(String[] args) {
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
        new Thread(
                ()->{

                    int stamp = atomicStampedReference.getStamp();
                    System.out.println("a===>"+stamp);
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    atomicStampedReference.compareAndSet(1,2,
                            atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
                    System.out.println("a2===>"+atomicStampedReference.getStamp());

                    System.out.println(atomicStampedReference.compareAndSet(2, 1,
                            atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
                    System.out.println("a3===>"+atomicStampedReference.getStamp());

                },"a").start();

        new Thread(
                ()->{
                    int stamp = atomicStampedReference.getStamp();
                    System.out.println("b===>"+stamp);
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1));
                    System.out.println("b==>"+atomicStampedReference.getStamp());
                },"b").start();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。在编写C程序时,需要注意变量的声明和定义、指针的使用、内存的分配与释放等问题。C语言中常用的数据结构包括: 1. 数组:一种存储同类型数据的结构,可以进行索引访问和修改。 2. 链表:一种存储不同类型数据的结构,每个节点包含数据和指向下一个节点的指针。 3. 栈:一种后进先出(LIFO)的数据结构,可以通过压入(push)和弹出(pop)操作进行数据的存储和取出。 4. 队列:一种先进先出(FIFO)的数据结构,可以通过入队(enqueue)和出队(dequeue)操作进行数据的存储和取出。 5. 树:一种存储具有父子关系的数据结构,可以通过中序遍历、前序遍历和后序遍历等方式进行数据的访问和修改。 6. 图:一种存储具有节点和边关系的数据结构,可以通过广度优先搜索、深度优先搜索等方式进行数据的访问和修改。 这些数据结构在C语言中都有相应的实现方式,可以应用于各种不同的场景。C语言中的各种数据结构都有其优缺点,下面列举一些常见的数据结构的优缺点: 数组: 优点:访问和修改元素的速度非常快,适用于需要频繁读取和修改数据的场合。 缺点:数组的长度是固定的,不适合存储大小不固定的动态数据,另外数组在内存中是连续分配的,当数组较大时可能会导致内存碎片化。 链表: 优点:可以方便地插入和删除元素,适用于需要频繁插入和删除数据的场合。 缺点:访问和修改元素的速度相对较慢,因为需要遍历链表找到指定的节点。 栈: 优点:后进先出(LIFO)的特性使得栈在处理递归和括号匹配等问题时非常方便。 缺点:栈的空间有限,当数据量较大时可能会导致栈溢出。 队列: 优点:先进先出(FIFO)的特性使得
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言中,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言中常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言中常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言中用于封装代码的单元,可以实现代码的复用和模块化。C语言中定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言中用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言中定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言中用于存储同类型数据的结构,可以通过索引访问和修改数组中的元素。字符串是C语言中用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言中用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言中通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习和实践打下坚实的基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值