JUC基础(一)

1. 多线程基础

1.1 线程和进程
进程:
是指一个内存中运行的应用程序,系统运行的程序的基本单位,一个进程可以包含多个线程
线程:
进程内部的一个独立执行单元

进程与线程的区别:

进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。

线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。

注意:

  1. 因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于CPU 的调度,程序员是不能完全控制的(可以设置线程优先级)。而这也就造成的多线程的随机性。
  2. Java 程序的进程里面至少包含两个线程,主线程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。
  3. 由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。

1.2多线程的创建

1.2.1继承Thread类
重写run()方法

public class MyThread {
    public static void main(String[] args) {
        CreateThread thread = new CreateThread();
        CreateThread thread2 = new CreateThread();
        thread.start();
        thread2.start();
    }
}
class CreateThread extends Thread {
    private int work = 15;
    public void run() {
        String name = Thread.currentThread().getName();
        for (int i = 0; i < 10; i++) {
            if (work > 0) {
                System.out.println(name + ":" + (work--));
            }
        }
    }
}

1.2.2实现Runnable重写run方法

public class MyRunnable {
    public static void main(String[] args) {
        CreateRunnable c = new CreateRunnable();
       Thread t =new Thread(c,"我是1号");
       Thread t2 =new Thread(c,"我是2号");

       t.start();
       t2.start();

    }
}
class CreateRunnable implements Runnable{
private int work = 15;
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        for (int i = 0; i < 10; i++) {
            if (work>0){

                System.out.println(name+":"+(work--));
            }
        }
    }
}

实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立。
  4. 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

1.2.3匿名内部类

public class Dome {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        },"小明").start();
    }
}

1.2.4守护线程

用户线程是指用户自定义创建的线程,主线程停止,用户线程不会停止。

守护线程当进程不存在或主线程停止,守护线程也会被停止

1.3线程安全
1.3.1卖票案例
synchronized同步方法

/*
 * 我们的学习是基于企业级的开发进行的;
 * 1、架构:高内聚,低耦合
 * 2、套路:线程操作资源类,资源类是单独的
 */
public class DemoTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
      new Thread(()->{
          for (int i = 0; i < 30; i++) {
              ticket.saleTicket();
          }
      },"窗口一").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.saleTicket();
            }
        },"窗口二").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.saleTicket();
            }
        },"窗口三").start();
    }
}
class  Ticket{
    private int ticketNum = 30;
   synchronized void saleTicket(){
        if (ticketNum>0){
            System.out.println(Thread.currentThread().getName()+"卖出"+(ticketNum--)+"张票还剩:"+ticketNum);
        }
    }
}

lock锁

public class DemoLock {
    public static void main(String[] args) {
        Ticket2 ticket = new Ticket2();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.saleTicket();
            }
        },"窗口一").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.saleTicket();
            }
        },"窗口二").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.saleTicket();
            }
        },"窗口三").start();
    }
}
class  Ticket2{
    private int ticketNum = 30;
    private Lock lock =new ReentrantLock();
     void saleTicket(){
         lock.lock();//枷锁
         try {
             if (ticketNum>0){
                 System.out.println(Thread.currentThread().getName()+"卖出"+(ticketNum--)+"张票还剩:"+ticketNum);
             }
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             lock.unlock();//解锁
         }
    }
}

Synchronized 和 Lock 区别

1、Synchronized 是一个关键字、Lock 是一个对象

2、Synchronized 无法尝试获取锁,Lock 可以尝试获取锁,判断;

3、Synchronized 会自动释放锁(a线程执行完毕,b如果异常了,也会释放锁),lock锁是手动释放锁!如果你不释放就会死锁。

4、Synchronized (线程A(获得锁,如果阻塞),线程B(等待,一直等待);)lock,可以尝试获取锁,失败了之后就放弃

5、Synchronized 一定是非公平的,但是 Lock 锁可以是公平的,通过参数设置;

6、代码量特别大的时候,我们一般使用Lock实现精准控制,Synchronized 适合代码量比较小的同步问题;

1.3.2死锁

多线程死锁:同步中嵌套同步,导致锁无法释放。

死锁解决办法:不要在同步中嵌套同步

1.4 线程状态

1.4.1 线程状态介绍

NEW(新建)
线程刚被创建,但是并未启动。
RUNNABLE(可运行)
线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
BLOCKED(锁阻塞)
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
WAITING(无限等待)
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
TIMED_WAITING(计时等待)
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
TERMINATED(被终止)
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

1.4.2图
在这里插入图片描述
1.4,3、生产者消费者问题

线程和线程之间本来是不能通信的,但是有时候我们需要线程之间可以协调操作:

/*
目的: 有两个线程:A  B ,还有一个值初始为0,
       实现两个线程交替执行,对该变量 + 1,-1;交替10次
 */
public class Demo01 {
    public static void main(String[] args) {
      Data data= new Data();
      new Thread(()->{
        for (int i = 0; i < 10; i++) {
            try {
                data.incr();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    },"路飞").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"索隆").start();
}
}
// 资源类
// 线程之间的通信: 判断  执行  通知
class Data{
    private int number = 0;
    //
    synchronized void incr() throws InterruptedException {
        //判断
        if (number!=0){
          this.wait();
        }
        number++;//执行
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        this.notifyAll();//通知
    }
    synchronized void decr() throws InterruptedException {
        if (number==0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        this.notifyAll();
    }
}

四条线程可以实现交替吗?不能,会产生虚假唤醒问题!
在这里插入图片描述注意if与whil

public class Demo02 {
    public static void main(String[] args) {
        Data02 data= new Data02();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"路飞1").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"索隆2").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"山治3").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"乔巴4").start();
    }
}
 //资源类
// 线程之间的通信: 判断  执行  通知
class Data02{
    private int number = 0;
    //
    synchronized void incr() throws InterruptedException {
        //判断
        while (number!=0){
            this.wait();
        }
        number++;//执行
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        this.notifyAll();//通知
    }
    synchronized void decr() throws InterruptedException {
        while (number==0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        this.notifyAll();
    }
}

如何精准唤醒
/*
实现线程交替执行!
主要的实现目标:精准的唤醒线程!
三个线程:A B C
三个方法:A p5 B p10 C p15 依次循环
*/

public class Demo03 {
    public static void main(String[] args) {
        Data03 data= new Data03();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.print();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"路飞1").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.print10();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"索隆2").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.print15();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"山治3").start();
    }
}
class Data03{
    private int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

     void print() throws InterruptedException {
        lock.lock();
        try {
            //判断
            while (number!=0){
                condition.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()+"\t"+i);
            }
            number = 1;
            condition2.signal();//通知
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
     void print10() throws InterruptedException {
        lock.lock();
        try {
            while (number!=1){
                condition2.await();
            }
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"\t"+i);
            }
            number = 2;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
     void print15() throws InterruptedException {
        lock.lock();
        try {
            while(number != 2){
                condition3.await();
            }
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName()+"\t"+i);
            }
            number = 0;
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}

wait/Sleep区别

1、类不同!

wait : Obejct 类     Sleep Thread

在juc编程中,线程休眠怎么实现!Thread.Sleep
     // 时间单位
     TimeUnit.SECONDS.sleep(3);

2、会不会释放资源!

sleep:抱着锁睡得,不会释放锁!wait 会释放锁!

3、使用的范围是不同的;

wait 和 notify 是一组,一般在线程通信的时候使用!

sleep 就是一个单独的方法,在那里都可以用!

4、关于异常;

sleep 需要捕获异常
在这里插入图片描述注意:
只要方法被 static synchronized 修饰,锁的对象就是 Class模板对象,这个则全局唯一
方法被 synchronized 修饰的方式,锁的对象是方法的调用者

1.5 线程停止

结束线程有以下三种方法:
(1)设置退出标志,使线程正常退出。
(2)使用interrupt()方法中断线程。
(3)使用stop方法强行终止线程(不推荐使用Thread.stop, 这种终止线程运行的方法已经被废弃,使用它们是极端不安全的!

public class Demo04 {
    public static boolean exit =true;
    public static void main(String[] args) {
        new Thread(()->{
           while (exit){

               try {
                   System.out.println("开启线程");
                   TimeUnit.SECONDS.sleep(5);
                   for (int i = 0; i < 10; i++) {
                       System.out.println("这可以吗");
                   }
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
        },"小明").start();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        exit = false;
        System.out.println("退出线程");
    }
}

1.5.2 使用interrupt()方法

使用interrupt()方法来中断线程有两种情况:

1)线程处于阻塞状态
如使用了sleep,同步锁的wait,socket中的receiver,accept等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,会抛出InterruptException异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后break跳出循环状态,从而让我们有机会结束这个线程的执行。

2)线程未处于阻塞状态
使用isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。

public class Demo04 {
    public static boolean exit = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            public void run() {
                while (exit) {
                    try {
                        System.out.println("线程执行!");
                        //判断线程的中断标志来退出循环
                        if (Thread.currentThread().isInterrupted()) {
                            break;
                        }

                        Thread.sleep(100l);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        //线程处于阻塞状态,当调用线程的interrupt()方法时,
                        //会抛出InterruptException异常,跳出循环
                     break;
                    }
                }
            }
        });
        t.start();

       Thread.sleep(1000l);
        //中断线程
        t.interrupt();
        System.out.println("线程中断了");
    }
}

1.6 线程优先级

1.6.1 优先级priority
现今操作系统基本采用分时的形式调度运行的线程,线程分配得到时间片的多少决定了线程使用处理器资源的多少,也对应了线程优先级这个概念。
在JAVA线程中,通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5。

public class Demo05 {
    public static void main(String[] args) {
        PrioritytThread prioritytThread = new PrioritytThread();

        // 如果8核CPU处理3线程,无论优先级高低,每个线程都是单独一个CPU执行,就无法体现优先级
        // 开启10个线程,让8个CPU处理,这里线程就需要竞争CPU资源,优先级高的能分配更多的CPU资源
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(prioritytThread, "线程" + i);
            if (i == 1) {
                t.setPriority(10);
            }
            if (i == 2) {
                t.setPriority(1);
            }
            t.setDaemon(true);
            t.start();
        }

        try {
            Thread.sleep(1000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("线程1总计:" + PrioritytThread.count1);
        System.out.println("线程2总计:" + PrioritytThread.count2);
    }

    static class PrioritytThread implements Runnable {
        public static Integer count1 = 0;
        public static Integer count2 = 0;

        public void run() {
            while (true) {
                if ("线程1".equals(Thread.currentThread().getName())) {
                    count1++;
                }
                if ("线程2".equals(Thread.currentThread().getName())) {
                    count2++;
                }

                if (Thread.currentThread().isInterrupted()) {
                    break;
                }
            }
        }
    }
}

1.6.2 join()方法

join作用是让其他线程变为等待。thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
1.6.3 yield方法

Thread.yield()方法的作用:暂停当前正在执行的线程,并执行其他线程。(可能没有效果)
yield()让当前正在运行的线程回到可运行状态,以允 许具有相同优先级的其他线程获得运行的机会。因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值