Java 多线程、线程同步、线程池

一. 线程

        1. 线程:线程(Thread)是一个程序内部的一条执行流程。

        2. 程序中如果只有一条执行流程,那这个程序就是单线程的程序。

二. 多线程 

        多线程是指从硬件上实现多条执行流程的技术(多条线程由CPU负责调度)

        Javas是通过java.lang.Thread类的对象来代表线程的

        1. 多线程的创建方式一:继承Thread类

                ① 定义一个子类继承线程类(java.lang.Thread),重写run()方法

                ② 创建子类线程对象

                ③ 调用线程对象的start()方法启动线程(启动后还是执行的run方法)

                优点:编码简单;

                缺点:线程继承了Thread类,无法继承其他类,不利于线程的扩展

                注意事项:启动线程必须调用start()方法,不是调用run()方法;不要把主线程任务放在启动子线程之前


/*
* 1. 让子类继承Thread线程类
* */
public class MyThread extends Thread {

    //2.重写 run方法
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程类:" + i);
        }
    }
}




public class Test1_Thread {
    //main方法是由一条默认的主线程负责执行
    public static void main(String[] args) {
        //创建线程类对象
        Thread t = new MyThread();
        //启动线程
        t.start();//main线程的子线程

        //主线程任务在后
        //不要把主线程任务放在启动子线程之前(会导致执行完主线程任务后才会启动子线程)

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程:" + i);
        }
    }
}

        2. 多线程的创建方式二:实现Runnable接口

                ① 定义一个线程任务类实现Runnable接口,重写run()方法;

                ② 创建任务类对象

                ③ 把任务类对象交给Thread处理(public Thread(Runnable target))

                ④ 调用线程对象的start()方法启动线程

                优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强

                缺点:需要多创建一个Runnable对象

/*
* implements Runnable 实现Runnable接口
* */
public class MyRunnable implements Runnable {

    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程:" + i);
        }
    }
}


public class Test2_Runnable {
    public static void main(String[] args) {
        //创建任务对象
        Runnable r = new MyRunnable();

        //把任务对象交给一个线程对象处理 public Thread(Runnable target)
        new Thread(r).start();


        //主线程
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程" + i);
        }
    }
}



public class Test3_Runnable {
    public static void main(String[] args) {
        //使用匿名内部类创建线程
        //1.直接创建Runnable接口的匿名内部类形式
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程" + i);
                }
            }
        };

        //启动线程
        new Thread(r).start();

        //简化1
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程2" + i);
                }
            }
        }).start();

        //简化2
        new Thread(() ->{
            for (int i = 0; i < 10; i++) {
                System.out.println("子线程3" + i);
            }
        }).start();

        //主线程
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程" + i);
        }
    }
}

        3. 多线程的创建方式三:实现Callable接口

                前面两种线程创建方式都存在一个问题:他们重写的run方法均不能直接返回结果;JDK5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式) :可以直接返回线程执行的结果 

                (1) 创建任务对象:定义一个类实现Callable接口,重写call方法,封装要做的事情和要返回的数据。把Callable类型的对象封装成FutureTask(线程任务对象)。     

                (2) 把任务类对象交给Thread处理(public Thread(Runnable target))

                (3) 调用线程对象的start()方法启动线程

                (4) 线程执行完毕后、通过FutureTask对象的get方法去获取线程任务执行的结果。

FutureTask提供的构造器说明
public FutureTask<>(Callable call)把Callable对象封装成FutureTask对象
FutureTask提供的方法说明
public V get() throws Exception获取线程call方法返回的结果

public class MyCallable implements Callable<String> {
    private int num;
    public MyCallable(int num) {
        this.num = num;
    }
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 0; i < num; i++) {
            sum = sum + i;
        }
        return "sum:" + sum;
    }
}



public class Test4_Callable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //(1) 创建任务对象:定义一个类实现Callable接口,重写call方法,封装要做的事情和要返回的数据。
        Callable<String> callable = new MyCallable(10);
        //把Callable类型的对象封装成FutureTask(线程任务对象)。
        FutureTask<String> futureTask = new FutureTask<>(callable);
        // (2) 把任务类对象交给Thread处理(public Thread(Runnable target))
        //(3) 调用线程对象的start()方法启动线程
        new Thread(futureTask).start();
        //(4) 线程执行完毕后、通过FutureTask对象的get方法去获取线程任务执行的结果。
        // 假如上面的线程还没有执行完成,这里的代码会暂停,等待上面线程执行完毕后才会获取结果
        String s = futureTask.get();
        System.out.println(s);
    }
}

三. Thread的常用方法

Thread提供的常用方法说明
public void run()线程的任务方法
public void start()启动线程
public String getName()获取当前线程的名称,线程名称默认是Thread-索引
public void setName(String name)设置线程名称
public static Thread currentThread()获取当前执行的线程对象
public static void sleep(long time)让当前执行的线程休眠多少毫秒后,再继续执行
public final void join() ...让调用当前这个方法的线程先执行完
Thread提供的常见构造器说明
public Thread(String name)设置当前线程名称
public Thread(Runnable target)封装Runnable对象为线程对象
public Thread(Runnable target, String name)封装Runnable对象为线程对象,并指定线程名称
public class MyThread extends Thread {

    public MyThread(String name) {
        super(name);
    }

    public MyThread(){
        super();
    }

    //2.重写 run方法
    public void run() {
        Thread t = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            System.out.println(t.getName() + "线程:" + i);
        }
    }
}


public static void main(String[] args) throws Exception {
        //Thread的常用方法

        Thread t1 = new MyThread();
        Thread t2 = new MyThread();
        t1.start();
        t2.start();

        //主线程
        for (int i = 0; i < 10; i++) {
            //public static void sleep(long time)	让当前执行的线程休眠多少毫秒后,再继续执行
            if (i == 5 ){
                Thread.sleep(2000);
            }
            System.out.println("主线程" + i);
        }
        //public String getName()	获取当前线程的名称,线程名称默认是Thread-索引
        System.out.println(t1.getName());//Thread-0
        System.out.println(t2.getName());//Thread-1

        //public void setName(String name)	设置线程名称
        t1.setName("线程1");
        t2.setName("线程2");
        System.out.println(t1.getName());//线程1
        System.out.println(t2.getName());//线程2

        //public static Thread currentThread()	获取当前执行的线程对象
        // 哪个线程执行他,就会得到哪个线程对象
        Thread m = Thread.currentThread();
        System.out.println(m.getName());//main
        m.setName("主线程");
        System.out.println(m.getName());//主线程




        //public Thread(String name)	设置当前线程名称
        Thread t3 = new MyThread("线程3");
        System.out.println(t3.getName());//线程3

        //public final void join() ...	让调用当前这个方法的线程先执行完
        Thread t4 = new MyThread("线程4");
        t4.start();
        t4.join();

        Thread t5 = new MyThread("线程5");
        t5.start();
        t5.join();

        Thread t6 = new MyThread("线程6");
        t6.start();
        t6.join();
    }

四. 线程安全

        线程安全问题:多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。

        

//账户类
public class Account {
    private double balance;
    private String accountId;

    public Account(double balance, String accountId) {
        this.balance = balance;
        this.accountId = accountId;
    }

    public Account() {
    }

    //取钱方法
    public void deposit(double amount) {
        String name = Thread.currentThread().getName();
        if (this.balance >= amount ) {
            System.out.println(
                    name + "取钱" + amount + " 成功"
            );
            balance = balance - amount;
            System.out.println(name + "余额" + this.balance);
        }else {
            System.out.println(name + "取钱" + amount + "余额不足");
        }
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public String getAccountId() {
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }
}



//取钱线程
public class depositThread extends Thread{
    private Account account;
    public depositThread(Account account, String name) {
        super(name);
        this.account = account;
    }
    public void run() {
        account.deposit(100000);
    }
}





//main测试方法
public static void main(String[] args) {
        Account account = new Account(100000, "147852963" );
        new depositThread(account, "朵朵").start();
        new depositThread(account, "大宇").start();
    }

五. 线程同步

        线程同步:解决线程安全问题的方案:让多个线程先后依次的访问共享资源

        常见方案:加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能在加锁进来。

        1. 方式一:同步代码块

                作用:把访问共享资源的核心代码给上锁,保证线程安全

                格式:synchronized(同步锁){

                                访问公共资源的核心代码块

                        }

                原理:每次只允许一个线程加锁进去,执行完毕后自动解锁,其他线程才可以进来执行

                注意事项:对于当前执行的线程来说,同步锁必须是同一个对象,否则会出bug

                锁对象的使用规范:

                        ① 建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象

                        ② 对于静态方法建议使用字节码(类名.class)对象作为锁对象

//取钱方法
    public void deposit(double amount) {
        String name = Thread.currentThread().getName();

        synchronized (this) {
            if (this.balance >= amount ) {
                System.out.println(
                        name + "取钱" + amount + " 成功"
                );
                balance = balance - amount;
                System.out.println(name + "余额" + this.balance);
            }else {
                System.out.println(name + "取钱" + amount + "余额不足");
            }
        }
    }

//如果为静态方法
    public static void deposit1(double amount) {
        String name = Thread.currentThread().getName();

        synchronized (Account.class) {
            
        }
    }





public static void main(String[] args) {
        Account account = new Account(100000, "147852963" );
        new depositThread(account, "朵朵").start();//
        new depositThread(account, "大宇").start();

        Account account1 = new Account(150000, "88888888" );
        new depositThread(account1, "欢欢").start();//
        new depositThread(account1, "麦琪").start();
}

        2. 方式二:同步方法

                作用:把访问共享资源的核心方法上锁,保证线程安全

                格式: 修饰符 synchronized 返回值类型 方法名称(形参列表){

                                操作共享资源的代码

                        }

                原理:每次只允许一个线程加锁进去,执行完毕后自动解锁,其他线程才可以进来执行

                同步方法的底层原理:

                        ① 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码

                        ② 如果方法是实例方法:同步方法默认用this作为锁对象

                        ③ 如果方法是静态方法:同步方法默认用类名.class作为锁对象

  public synchronized void deposit(double amount) {
        String name = Thread.currentThread().getName();

      
        if (this.balance >= amount ) {
            System.out.println(
                    name + "取钱" + amount + " 成功"
            );
            balance = balance - amount;
            System.out.println(name + "余额" + this.balance);
        }else {
            System.out.println(name + "取钱" + amount + "余额不足");
        }
        
    }



public static void main(String[] args) {
        Account account = new Account(100000, "147852963" );
        new depositThread(account, "朵朵").start();//
        new depositThread(account, "大宇").start();

        Account account1 = new Account(150000, "88888888" );
        new depositThread(account1, "欢欢").start();//
        new depositThread(account1, "麦琪").start();
    }

        3. 方式三: Lock锁

                Lock锁是jdk5开始提供的一个新的锁定方式,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。

                Lock是接口,不能直接实例化,可以采用他的实现类ReentrantLock来构建Lock对象

构造器说明
public ReentrantLock()获得Lock锁的实现类对象
常用方法说明
void lock()获得锁
void unlock()释放锁
public class Account {
    private double balance;
    private String accountId;
    //创建一个锁对象
    private final Lock lock = new ReentrantLock();

    public Account(double balance, String accountId) {
        this.balance = balance;
        this.accountId = accountId;
    }

    public Account() {
    }

    public void deposit(double amount) {
        String name = Thread.currentThread().getName();

        try {
            lock.lock();//加锁
            if (this.balance >= amount ) {
                System.out.println(
                        name + "取钱" + amount + " 成功"
                );
                balance = balance - amount;
                System.out.println(name + "余额" + this.balance);
            }else {
                System.out.println(name + "取钱" + amount + "余额不足");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();//解锁
        }

    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public String getAccountId() {
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }
}



 public static void main(String[] args) {
        Account account = new Account(100000, "147852963" );
        new depositThread(account, "朵朵").start();//
        new depositThread(account, "大宇").start();

        Account account1 = new Account(150000, "88888888" );
        new depositThread(account1, "欢欢").start();//
        new depositThread(account1, "麦琪").start();
    }

六.  线程通信

        1. 线程通信:当多个线程共同操作共享资源时,线程间通过某种方式互相告知自己的状态,以相互协调,避免无效的资源争夺

Object类的等待和唤醒方法说明
void wait()让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或notify()方法
void notify()唤醒正在等待的单个线程
void notifyAll()唤醒正在等待的所有线程

        上述方法应当使用当前同步的锁对象进行调用。

七. 线程池

        线程池:就是一个可以复用线程的技术。

        不使用线程池的问题:用户每发起一个请求,后台就需要创建一个新的线程来处理,而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,会影响系统的性能。

       

        1. 创建线程池方式一实现类ThreadPoolExecutor

                 JDK5..0起提供了代表线程池的接口:ExecutorService,常用的实现类:ThreadPoolExecutor

ThreadPoolExecutor构造器
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

                参数一:int corePoolSize:线程池的核心线程数量。

                参数二:int maximumPoolSize:线程池的最大线程数量。

                参数三:long keepAliveTime:临时线程的存活时间。

                参数四:TimeUnit unit:临时线程存活的时间单位(秒,分,时,天)

                参数五:BockingQueue<Runnable> workQueue:线程池的任务队列

                参数六:ThreadFactory threadFactory:线程池的线程工厂

                参数七:RejectedExecutionHandler handler:线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了怎么处理)

拒绝策略说明

ThreadPoolExecutor.AbortPolicy

丢弃任务并抛出RejectedExecutionException异常。默认
ThreadPoolExecutor.DiscardPolicy丢弃任务,但不抛出异常;不推荐
ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy由主线程负责调用任务的run()方法从而绕过线程池直接执行

                临时线程的创建:新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

                新任务拒绝:核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务

        2. 使用线程池处理Runnable任务

ExecutorService的常用方法名称说明
void execute(Runnable command)执行Runnable任务
void shutdown()等全部任务执行完毕后,再关闭线程池
List<Runnable> shutdownNow()立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务
public class MyRunnable implements Runnable {

    //重写run方法
    @Override
    public void run() {

        System.out.println(Thread.currentThread().getName() + "--子线程打印:qwer" );

        try {
            //测试:将睡眠时间调至最大 观察临时线程
            Thread.sleep(Integer.MAX_VALUE);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}



public static void main(String[] args) {
        /*
        * public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
        * long keepAliveTime, TimeUnit unit, BockingQueue<Runnable> workQueue,
        *  ThreadFactory threadFactory, RejectedExecutionHandler handler)
        * */
        ExecutorService pool = new ThreadPoolExecutor(3,5,8, TimeUnit.MINUTES,
                new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        //void execute(Runnable command)	执行Runnable任务
        Runnable r = new MyRunnable();
        pool.execute(r);//创建一个新线程
        pool.execute(r);//创建一个新线程
        pool.execute(r);//创建一个新线程

        pool.execute(r);//线程池的任务队列
        pool.execute(r);//线程池的任务队列
        pool.execute(r);//线程池的任务队列
        pool.execute(r);//线程池的任务队列

        pool.execute(r);//临时线程
        pool.execute(r);//临时线程

        //异常 RejectedExecutionException
        pool.execute(r);

        //void shutdown()	等全部任务执行完毕后,再关闭线程池
        pool.shutdown();

        //List<Runnable> shutdownNow()	立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务
        pool.shutdownNow();



}

        3. 使用线程池处理Callable任务

Future<T> submit(Callable<T> task)执行Callable任务,返回未来任务对象,用于获取线程返回的结果
public class MyCallable implements Callable<String> {
    private int num;
    public MyCallable(int num) {
        this.num = num;
    }
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 0; i < num; i++) {
            sum = sum + i;
        }
        return Thread.currentThread().getName() + "--线程sum:" + sum;
    }
}



public static void main(String[] args) throws Exception {
        /*
        * public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
        * long keepAliveTime, TimeUnit unit, BockingQueue<Runnable> workQueue,
        *  ThreadFactory threadFactory, RejectedExecutionHandler handler)
        * */
        ExecutorService pool = new ThreadPoolExecutor(3,5,8, TimeUnit.MINUTES,
                new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());


        //Future<T> submit(Callable<T> task)	执行Callable任务,返回未来任务对象,用于获取线程返回的结果
        Future<String> f1 = pool.submit(new MyCallable(10));
        Future<String> f2 = pool.submit(new MyCallable(20));
        Future<String> f3 = pool.submit(new MyCallable(30));
        Future<String> f4 = pool.submit(new MyCallable(40));
        Future<String> f5 = pool.submit(new MyCallable(50));
        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
        System.out.println(f5.get());


    }

        4. 创建线程池方式二Executors工具类实现线程池

                Executors是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。

常用方法说明
public static ExecutorService newFixedThreadPool(int nThreads)创建固定数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代
public static ExecutorService newSingleThreadExecutor()创建只要一个线程的线程池对象,如果该线程出现异常而结束,那么线程会补充一个新线程
public static ExecutorService newCachedThreadPool()线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收
public static ExecutorService ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务

这些方法的底层,都是通过线程池的实现类对象ThreadPoolExecutor创建的线程池对象

public static void main(String[] args) {
        //public static ExecutorService newFixedThreadPool(int nThreads)	创建固定数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代
        ExecutorService pool = Executors.newFixedThreadPool(3);

        //public static ExecutorService newSingleThreadExecutor()	创建只要一个线程的线程池对象,如果该线程出现异常而结束,那么线程会补充一个新线程
        ExecutorService pool2 = Executors.newSingleThreadExecutor();

        //public static ExecutorService newCachedThreadPool()	线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收
        ExecutorService pool3 = Executors.newCachedThreadPool();

        //public static ExecutorService ScheduledExecutorService newScheduledThreadPool(int corePoolSize)	创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务
        ExecutorService pool4 = Executors.newScheduledThreadPool(3);
}

        计算密集型任务:核心线程数量=CPU的核数 + 1

        IO密集型任务:核心线程数量=CPU的核数 + 2

5. Executors使用可能存在的陷阱

        大型并发系统环境中使用Executors如果不注意可能会出现系统风险

 八. 并发、并行、生命周期

        1. 进程

                进程:正在运行的程序(软件)就是一个独立的进程;线程是属于进程的,一个进程中可以同时运行多个线程;进程中的多个线程其实是并发和并行执行的

        2. 并发

                进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮巡为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。

        3. 并行

                在同一时刻上,同时有多个线程在被CPU调度执行

                多线程是并发和并行同时进行的

        4. 线程的生命周期

                线程从生到死的过程中,经历的各种状态及状态转换

                Java线程的状态:6中状态定义在了Thread类内部枚举类中

public enum State {
       
        //新建状态
        NEW,

        //可运行状态
        RUNNABLE,

        //锁 阻塞状态
        BLOCKED,

     
        //无限等待状态
        WAITING,

       //计时等待状态
        TIMED_WAITING,

        //被终止
        TERMINATED;
    }

线程状态说明
NEW (新建)线程刚被创建,但是并未启动
Runnable(可运行)线程已经调用了start(),等待CPU调度
Blocked(锁阻塞)线程在执行的时候未竞争到锁对象,该线程进入Blocked状态
Waiting(无限等待)一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够用唤醒
Timed Waiting(计时等待)同waiting状态,有几个方法(sleep,wait)有超时参数,调用他们则进入Timed Waiting(计时等待)状态
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止run方法而死亡

九. 悲观锁、乐观锁 

        1. 悲观锁:一上来就加锁,每次只能一个线程进入访问完毕后,在解锁。线程安全,性能较差

        

public class MyRunnable implements Runnable {
    private int num;
    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //悲观锁
            synchronized (this) {
                System.out.println(Thread.currentThread().getName() + ":" + ++num);
            }
        }
    }
}


public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        //悲观锁
        for (int i = 0; i < 10; i++) {
            new Thread(runnable).start();
        }
}

        2. 乐观锁:一开始不上锁,认为是没有问题的,等要出现线程安全问题的时候才开始控制。线程安全,性能较好。

public class MyRunnable2 implements Runnable {
   /* private int num;*/
    //整数修改的乐观锁 原子类实现
    private AtomicInteger num = new AtomicInteger();
    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //乐观锁
            System.out.println(Thread.currentThread().getName() + ":" + num.incrementAndGet());
        }
    }
}


 public static void main(String[] args) {
        Runnable runnable = new MyRunnable2();
        //乐观锁
        for (int i = 0; i < 10; i++) {
            new Thread(runnable).start();
        }
    }

         

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/965736.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

20.[前端开发]Day20-王者荣耀项目实战(三)

01_(掌握)王者荣耀-main-赛事新闻列表实现 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" …

【Langchain学习笔记(一)】Langchain介绍

Langchain介绍 Langchain介绍前言1、Langchain 是什么2、为什么要用 Langchain3、Langchain 的核心4、Langchain 的底层原理5、Langchain 的应用场景 Langchain介绍 前言 想象一下&#xff0c;如果你能让聊天机器人不仅仅回答通用问题&#xff0c;还能从你自己的数据库或文件…

IDEA2024版本创建Sping项目无法选择Java 8

目录 一、背景二、解决方式&#xff08;替换创建项目的源地址&#xff09; 一、背景 IDEA2024创建一个springboot的项目&#xff0c;本地安装的是1.8&#xff0c;但是在使用Spring Initializr创建项目时&#xff0c;发现版本只有17、21、23。 二、解决方式&#xff08;替换创…

C++11(四)

目录 包装器 function包装器 bind绑定 更改实参传递的顺序和实参传递的个数 线程库 本期我们将继续进行C11新特性的学习。 包装器 function包装器 function包装器&#xff0c;我们也称之为适配器&#xff0c;本质上就是一个类模板&#xff0c;为什么要引入function包…

MySQL 数据库编程-C++

目录 1 数据库基本知识 1.1 MYSQL常见命令 1.2 SQL注入 1.3 ORM框架 1 数据库基本知识 MySQL 为关系型数据库(Relational Database Management System), 这种所谓的"关系型"可以理解为"表格"的概念, 一个关系型数据库由一个或数个表格组成&#xff1a…

【算法篇】贪心算法

目录 贪心算法 贪心算法实际应用 一&#xff0c;零钱找回问题 二&#xff0c;活动选择问题 三&#xff0c;分数背包问题 将数组和减半的最小操作次数 最大数 贪心算法 贪心算法&#xff0c;是一种在每一步选择中都采取当前状态下的最优策略&#xff0c;期望得到全局最优…

5 计算机网络

5 计算机网络 5.1 OSI/RM七层模型 5.2 TCP/IP协议簇 5.2.1:常见协议基础 一、 TCP是可靠的&#xff0c;效率低的&#xff1b; 1.HTTP协议端口默认80&#xff0c;HTTPSSL之后成为HTTPS协议默认端口443。 2.对于0~1023一般是默认的公共端口不需要注册&#xff0c;1024以后的则需…

动态规划LeetCode-1035.不相交的线

在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#xff0c;这些直线需要同时满足&#xff1a; nums1[i] nums2[j]且绘制的直线不与任何其他连线&#xff08;非水平线&#xff09;相…

禅道社区版项目管理软件部署(记录篇)

系统要求&#xff08;这里推荐使用docker容器化方式&#xff09;安装前的准备Docker快速安装最后通过查看地址验证是否部署成功开始界面化安装配置 禅道&#xff08;ZenTao&#xff09;是一款国产开源的项目管理软件&#xff0c;专注于敏捷开发流程&#xff0c;支持 Scrum 和 K…

数据结构-基础

1、概念&#xff1a; 程序 数据结构 算法 2、程序的好坏 可读性&#xff0c;稳定性&#xff0c;扩展性&#xff0c;时间复杂度&#xff0c;空间复杂度。 3、数据结构 是指存储、组织数据的方式&#xff0c;以便高效地进行访问和修改。通过选择适当的数据结构&#xff0c; 能…

从零开始:OpenCV 图像处理快速入门教程

文章大纲 第1章 OpenCV 概述 1.1 OpenCV的模块与功能  1.2 OpenCV的发展 1.3 OpenCV的应用 第2章 基本数据类型 2.1 cv::Vec类 2.2 cv&#xff1a;&#xff1a;Point类 2.3 cv&#xff1a;&#xff1a;Rng类 2.4 cv&#xff1a;&#xff1a;Size类 2.5 cv&#xff1a;&…

1-kafka服务端之延时操作前传--时间轮

文章目录 背景时间轮层级时间轮时间轮降级kafka中的时间轮kafka如何进行时间轮运行 背景 Kafka中存在大量的延时操作&#xff0c;比如延时生产、延时拉取和延时删除等。Kafka并没有使用JDK自带的Timer或DelayQueue来实现延时的功能&#xff0c;而是基于时间轮的概念自定义实现…

Java 注解使用教程

简介 Java 1.5 引入了注解&#xff0c;现在它在 Java EE 框架&#xff08;如 Hibernate、Jersey 和 Spring &#xff09;中被大量使用。Java 注释是该语言的一个强大特性&#xff0c;用于向 Java 代码中添加元数据。它们不直接影响程序逻辑&#xff0c;但可以由工具、库或框架…

第17章 读写锁分离设计模式(Java高并发编程详解:多线程与系统设计)

1.场景描述 对资源的访问一般包括两种类型的动作——读和写(更新、删除、增加等资源会发生变化的动作)&#xff0c;如果多个线程在某个时刻都在进行资源的读操作&#xff0c;虽然有资源的竞争&#xff0c;但是这种竞争不足以引起数据不一致的情况发生&#xff0c;那么这个时候…

强化学习 DAY1:什么是 RL、马尔科夫决策、贝尔曼方程

第一部分 RL基础&#xff1a;什么是RL与MRP、MDP 1.1 入门强化学习所需掌握的基本概念 1.1.1 什么是强化学习&#xff1a;依据策略执行动作-感知状态-得到奖励 强化学习里面的概念、公式&#xff0c;相比ML/DL特别多&#xff0c;初学者刚学RL时&#xff0c;很容易被接连不断…

【STM32系列】利用MATLAB配合ARM-DSP库设计FIR数字滤波器(保姆级教程)

ps.源码放在最后面 设计IIR数字滤波器可以看这里&#xff1a;利用MATLAB配合ARM-DSP库设计IIR数字滤波器&#xff08;保姆级教程&#xff09; 前言 本篇文章将介绍如何利用MATLAB与STM32的ARM-DSP库相结合&#xff0c;简明易懂地实现FIR低通滤波器的设计与应用。文章重点不在…

DeepSeek-R1 本地电脑部署 Windows系统 【轻松简易】

本文分享在自己的本地电脑部署 DeepSeek&#xff0c;而且轻松简易&#xff0c;快速上手。 这里借助Ollama工具&#xff0c;在Windows系统中进行大模型部署~ 1、安装Ollama 来到官网地址&#xff1a;Download Ollama on macOS 点击“Download for Windows”下载安装包&#x…

Llama最新开源大模型Llama3.1

Meta公司于2024年7月23日发布了最新的开源大模型Llama 3.1&#xff0c;这是其在大语言模型领域的重要进展。以下是关于Llama 3.1的详细介绍&#xff1a; 参数规模与训练数据 Llama 3.1拥有4050亿&#xff08;405B&#xff09;参数&#xff0c;是目前开源领域中参数规模最大的…

Linux之安装docker

一、检查版本和内核是否合格 Docker支持64位版本的CentOS 7和CentOS 8及更高版本&#xff0c;它要求Linux内核版本不低于3.10。 检查版本 cat /etc/redhat-release检查内核 uname -r二、Docker的安装 1、自动安装 Docker官方和国内daocloud都提供了一键安装的脚本&#x…

2022年全国职业院校技能大赛网络系统管理赛项模块A:网络构建(样题3)-网络部分解析-附详细代码

目录 附录1:拓扑图 附录2:地址规划表 1.SW1 2.SW2 3.SW3 4.SW4 5.SW5 6.SW6 7.SW7 8.R1 9.R2 10.R3 11.AC1 12.AC2 13.AP2 14.AP3 15.EG1 16.EG2 附录1:拓扑图 附录2:地址规划表 设备