JAVA自学笔记(基础多线程)

什么是进程

  • 正在运行的程序,是系统进行资源分配的基本单位。

  • 目前操作系统支持多线程,可以同时执行多个进程,通过进程ID区分。

  • 单核CPU在同一个时刻,只能运行一个进程,宏观并行、微观串行

什么是线程

  • 线程:又称轻量级进程

  • 进程中的一条执行路径,也是CPU的基本调度单位

  • 一个进程由一个或者多个线程组成,彼此之间完成不同的工作,

  • 同时执行,称为多线程。

  • JAVA虚拟机是一个进程,当中默认包含主线程(main),可以通过代码创建多个独立线程,与main并发执行。

进程和线程之间的区别

  • 进程是操作系统资源分配的基本单位,线程是CPU的基本调度单位

  • 一个程序运行后至少有一个进程

  • 一个进程可以包含多个线程,但是至少需要有一个线程(主线程),否则这个进程没有意义

  • 进程之间不能共享数据段地址,打但是同进程的线程之间可以共享数据段地址

线程的组成

  • 任何一个线程都具有基本的组成部分:

    • CPU时间片:操作系统(OS)会为每个线程分配执行时间

    • 运行数据:

      • 堆空间:存储线程需要使用的对象,多个线程可以共享堆中的对象

      • 栈空间:存储线程需要使用的局部变量,每个线程都拥有独立的栈

    • 线程的逻辑代码

线程的特点

  • 线程抢占式执行,效果随机性

    • 效率高

    • 可以防止单一线程长时间独占CPU

  • 在单核CPU中,宏观上同时执行,微观上顺序执行

创建线程的三种方式

  • 1、继承Thread类,重写run方法(适合没有资源共享)

  • 内存图

    • public class MyThread extends Thread{  //1继承Thread接口
          @Override
          public void run() {  //2重写run方法(编写需要执行的功能)
              for(int i = 0;i<100;i++){
                  System.out.println("子线程-------"+i);
              }
          }
      }
    • public class TestMyThread {
          public static void main(String[] args) {
              //3创建子类对象
              MyThread myThread = new MyThread();
              //调用start()方法,启动线程
              myThread.start();
              for(int i = 0;i<100;i++){
                  System.out.println("主线程++++++++++++++++++++"+i);
              }
          }
      }
  • 2、实现Runnable接口(适合资源共享)

    • 操作相同,共享资源类实现Runnable接口

    • 操作不同,使操作类分开操作

  • public static void main(String[] args) {
        //创建实现类对象,表示线程要执行的功能
        MyRunnable myRunnable = new MyRunnable();
        //创建线程对象
        Thread thread = new Thread(myRunnable,"我的Runnable线程");
        //调用start()方法,启动线程
        thread.start();
    ​
        for(int i = 0;i<100;i++){
            System.out.println("main线程-----------------"+i);
        }
    }
    public class MyRunnable implements Runnable{  //1、子类继承Runnable接口
        @Override
        public void run() {   //实现run方法
            for(int i = 0;i<100;i++){
                System.out.println(Thread.currentThread().getName()+"+++++++"+i);
            }
        }
    • 匿名内部类实现Runnable

    Runnable runnable= new Runnable() {
        @Override
        public void run() {
            for(int i = 0;i<100;i++){
                System.out.println(Thread.currentThread().getName()+"+++++++"+i);
            }
        }
    ​
    } ;
    Thread thread1 =new Thread(runnable ,"我的匿名内部类线程");
    thread1.start();
  • 3.实现Callable接口(JDK1.5新添加的)

获取和修改线程名称

  • 获取线程ID和线程名称

    • 1、在Thread的子类中调用this.getId()和this.getName()

    • public void run() {  //2重写run方法(编写需要执行的功能)
          for(int i = 0;i<100;i++){
              //获取线程名称和线程id
              System.out.println(this.getId()+""+this.getName()+"子线程-------"+i);
          }
    • 2、使用Thread.currentThread().getId()和Thread.currentThread().getName(),获取当前线程

    • for(int i = 0;i<100;i++){
          //获取线程名称和线程id
      System.out.println(Thread.currentThread().getId()+Thread.currentThread().getName()+"子线程-------"+i);
      }
  • 修改线程名称(线程id不可更改)

    • 1、调用线程对象的setName()方法

    • //在线程执行之前,调用setName()来修改线程名称
      myThread.setName("小翠");
    • 2、使用线程子类的构造方法赋值

      //修改线程名称,在构造方法中赋值,子类线程重写带参数的构造方法,调用supper(String)进行改名
      MyThread myThread = new MyThread("我的子线程1");
      public class MyThread extends Thread{  //1继承Thread接口
          public MyThread(String naem){
              super(naem);
          }

案例:实现4个窗口共同卖100张票

  • public class TicketWin extends Thread{
        private int ticket  = 100;
        @Override
        public void run() {
            while (true){
                if(ticket<=0){
                    System.out.println(Thread.currentThread().getName()+"票已卖完");
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
                ticket--;
            }
        }
    }
    public static void main(String[] args) {
           Ticket ticket = new Ticket();
            Thread t1 = new Thread(ticket,"窗口1");
            Thread t2 = new Thread(ticket,"窗口2");
            Thread t3 = new Thread(ticket,"窗口3");
            t2.start();
            t1.start();
            t3.start();
  • 内存分析

线程的状态

  • New:初始状态:线程对象被创建,即为初始状态。只在堆中赖皮内存,与常规对象一样

  • Ready就绪状态:线程调用start()方法,进入就绪状态,等待OS选中,并且分配时间片

  • Running 状态:获取时间片之后,进入运行状态,如果时间片到期,回到就绪状态

  • Terminated终止状态:主线程main()或独立线程run()结束,进入终止状态,并且释放持有的时间片

线程常见方法

  • 休眠:

    • public static void sleep (long millis)

    • 当前线程主动休眠millis毫秒,在此期间,释放CPU,不在争抢CPU

    • public static void main(String[] args) {
          SleepThread s1 = new SleepThread();
          SleepThread s2 = new SleepThread();
          s1.start();
          s2.start();
      }
      ​
      //静态内部类
      static class SleepThread extends Thread{
          @Override
          public void run() {
              for (int i = 0;i<10;i++){
                  System.out.println(Thread.currentThread().getName()+"----"+i);
                  //休眠
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                 
                  }
              }
          }
  • 放弃

    • public static void yield()

    • 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片

    • 谦让,放弃:放弃CPU,让给优先级和他相同或者比它优先级高的线程

    • public static void main(String[] args) {
          YieldThread y1 = new YieldThread();
          YieldThread y2 = new YieldThread();
          y1.start();
          y2.start();
      ​
      }
      static class YieldThread extends Thread{
          @Override
          public void run() {
              for (int i = 0;i<10;i++) {
                  System.out.println(Thread.currentThread().getName() + "----" + i);
                  //谦让,放弃:放弃CPU,让给优先级和他相同或者比它优先级高的线程
                  Thread.yield();
              }
          }
      }
  • 加入

    • public static void join()

    • 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片

    • join();加入线程,阻塞当前线程,直到加入线程执行完毕

    • public static void main(String[] args) throws InterruptedException {
          joinThread join1 = new joinThread();
          join1.start();
          //加入线程
          join1.join();
          for(int i =0;i<10;i++) {
              System.out.println("主线程" + "....." + i);
              try {
                  Thread.sleep(100);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      ​
      }
      static class joinThread extends Thread{
          @Override
          public void run() {
              for(int i =0;i<10;i++){
                  System.out.println(Thread.currentThread().getName()+"....."+i);
                  try {
                      Thread.sleep(100);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
              }
          }
      }
  • 线程优先级

    • 线程对象.setPriority(int)

    • 线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多

    • 线程优先级,从1-10,10的优先级最高,1的优先级最低.p1.setPriority

    • public static void main(String[] args) {
          PrioeityThread p1 =new PrioeityThread("线程1");
          PrioeityThread p2 =new PrioeityThread("线程2");
          PrioeityThread p3 =new PrioeityThread("线程3");
          //设置p1的优先级为1
          p1.setPriority(1);
          p3.setPriority(10);
          p1.start();
          p2.start();
          p3.start();
      
      }
      static class PrioeityThread extends Thread{
          public PrioeityThread(String name ){
              super(name);
          }
          @Override
          public void run() {
              for (int i = 0; i < 10; i++) {
                  System.out.println(Thread.currentThread().getName() + "....." + i);
              }
          }
      }
  • 线程打断

    • 线程对象.interrupt();

    • 打断线程,被打断线程抛出InterruptedException异常

    • public static void main(String[] args) throws IOException {
          InterruptThread interruptThread = new InterruptThread();
          interruptThread.start();
          System.out.println("20秒内输入任意字符结束子线程");
          System.in.read();
          interruptThread.interrupt();//打断子线程
          System.out.println("主线程结束");
      
      }
      static class InterruptThread extends Thread{
          @Override
          public void run() {
              for(int i =0;i<10;i++) {
                  System.out.println(Thread.currentThread().getName() + "....." + i);
                  try {
                      Thread.sleep(10000);
                      System.out.println("子线程自然苏醒");
                  } catch (InterruptedException e) {
                     // e.printStackTrace();
                      System.out.println("子线程被打醒...");
                  }
                  System.out.println("子线程结束了....");
              }
          }
      }
  • 守护线程

    • 线程有两类:用户线程(前台线程)、守护线程(后台线程)。

    • 如果程序中所有前台程序都执行完毕,后台线程会自动结束

    • 垃圾回收器线程属于守护线程

    • setDaemon(true)设置为守护线程

    • DaemonThread daemonThread = new DaemonThread();
        daemonThread.setDaemon(true);
        daemonThread.start();
        for(int i =0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"....."+i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
      
      }
      static class DaemonThread extends  Thread{
          @Override
          public void run() {
              for(int i =0;i<100;i++){
                  System.out.println(Thread.currentThread().getName()+"....."+i);
                  try {
                      Thread.sleep(100);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      }

  • 线程状态(等待)

线程安全问题

  • 线程不安全

    • 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致

    • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才能保证其正确性

    • 原子操作:不可分割的多步操作,被视为一个整体,其顺序和步骤不可打乱或缺省

线程同步(同步代码块)

  • 同步代码块

    synchronized(锁){//对临界资源对象加锁(monitor)

    ** //代码 (原子操作)**

    }

    private Object lock = new Object();
    synchronized (lock){
            if(ticket>0){
            System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
            ticket--;
            }else {
            break;
            }
            }
  • 每个对象都有一个互斥锁标记,用来分配给线程

  • 只有拥有对象互斥锁标记的线程,才能进入到该对象加锁的同步代码块

  • 线程退出同步代码块时,会释放相应的互斥锁标记

线程的状态(阻塞)

线程安全(同步方法)

  • 同步方法

    synchronized 返回值类型 方法名称 (形参列表){ //对当前对象(this)加锁

    //代码(原子操作)

    }

    public class Ticket implements Runnable{
        private static int ticket = 100;
        //private Object lock = new Object();
    
        @Override
        public void run() {
            while (true){
                if(!sale())
                    break;
            }
        }
        public  synchronized  boolean sale(){
            if(ticket<=0){
                return false;
            }
    
                    System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
                    ticket--;
               return true;
            }
        }
  • 只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中

  • 线程退出同步方法时,会释放相应的互斥锁标记

  • 如果是静态方法,锁是【类名.class】

死锁

  • 当第一个线程拥有对A对象锁标记,并且等待B对象锁标记,同时第二个线程有B对象标记,并且等待A对象锁标记时,产生死锁

  • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁

  • public class MyLock {
        //A锁
        public static  Object objectA= new Object();
        //b锁
        public static  Object objectB= new Object();
    }
    
    public class Boy extends Thread{
        @Override
        public void run() {
            synchronized (MyLock.objectA){
                System.out.println("男孩拿到A锁");
                synchronized (MyLock.objectB){
                    System.out.println("男孩拿到b锁");
                    System.out.println("男孩可以吃饭");
                }
            }
        }
        
        public class Girl extends Thread{
        @Override
        public void run() {
            synchronized (MyLock.objectB){
                System.out.println("女孩拿到B锁");
                synchronized (MyLock.objectA){
                    System.out.println("女孩拿到A锁");
                    System.out.println("女孩可以吃饭");
                }
            }
        }
            
    public static void main(String[] args) throws InterruptedException {
            Boy boy = new Boy();
            Girl girl = new Girl();
            girl.start();
            Thread.sleep(100);
            boy.start();

线程通信

  • 等待

    • public final void wait()

    • public final void wait (long timeout)

    • 必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线会释放其拥有的所有标记,同时此线程阻塞在锁的等待队列中,释放锁,进入等待队列。

  • 通知

    • public final void notify():从等待队列中随机唤醒一个线程

    • public final void notifyAll():唤醒所有的等待线程

    • 必须在对obj加锁的同步代码块中,从obj的waiting中释放一个或者全部线程。对自身没有任何影响。

public class BankCard {
    private boolean flag;//默认false
    private double money;//默认0
    //存钱
    public synchronized void save(double m) throws InterruptedException {//this:锁

        if(flag){
            this.wait();//释放锁和CUP,进入等待
        }
        this.money = m + money;
        System.out.println(Thread.currentThread().getName() + "存了" + m + "元,余额是:" + this.money);
        this.flag =true;
        this.notify();
    }
    //取钱
    public synchronized  void take(double m) throws InterruptedException {// this:锁
        if(!flag){//没钱
            this.wait(); //取等待队列等待
        }
            this.money = money - m;
            System.out.println(Thread.currentThread().getName() + "取了" + m + "元,余额是:" + this.money);
            this.flag = false;
            //唤醒存钱线程
            this.notify();
    }

生产者和消费者

public class BreadCon {
    //创建数组,保存产品
    private Bread[] breads = new Bread[6];
    private  int size;

    //存放面包
    public synchronized  void input(Bread bread) throws InterruptedException {
        while (size>=breads.length){
            this.wait();
        }
        breads[size] = bread;
        System.out.println(Thread.currentThread().getName()+"生产了"+bread.getId()+"号面包");
        size++;
        this.notifyAll();// 通知消费者消费
    }

    public synchronized  void output() throws InterruptedException {

        while (size<=0){
            this.wait();
        }

        size--; //size-1代表下标
        Bread b = breads[size];
        System.out.println(Thread.currentThread().getName()+"消费了"+b.getId()+"号面包");
        breads[size] = null;
        this.notifyAll();// 通知生产者生产
    }
}

线程sleep时,只会释放CPU,不会释放锁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值