Java-进阶-day11-多线程

Java进阶-day11-多线程

今日内容

  • 多线程
  • 同步
  • 生产者和消费者

一.多线程

进程和线程

总结:

  • 进程:

    • 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
  • 线程:

    • 进程内部的一个独立执行单元;一个进程可以同时并发的运行多个线程,可以理解为一个进程便相当于一个单 CPU 操作系统,而线程便是这个系统中运行的多个任务。
  • 线程的生命周期:
    Java-进阶-day11-多线程_第1张图片

多线程程序实现

第一种方式:继承Thread类实现多线程

  • 总结:

    • 1, 继承Thread类

    • 2, 重写run方法

    • 3, 将要执行的代码写在run方法中

    • 4, 创建线程对象

    • 5, 调用start方法开启线程

    • 注意事项:

      • 调用run方法并不是启动线程,跟普通的对象调用方法是一样的线程要想启动,必须调用 start() 方法,启动线程
      • 线程并不能自己启动, 必须由一个程序来进行启动
  • 案例代码

public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

//        my1.run();
//        my2.run();

        //void start​() 导致此线程开始执行; Java虚拟机调用此线程的run方法
        my1.start();
        my2.start();
    }
}

public class MyThread extends Thread {
    @Override
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println(i);
        }
    }
}

  • 获取和设置线程名称
  • 总结:

    • A:获取线程名称的方法:

      • public String getName() : 获取当前执行线程的名称
        是在继承了Thread类中才能使用getName()

      • 如何获取主线程(main)的名称?

        Thread.currentThread().getName()

    • B:设置线程名称的方法

      • public Thread(String name) : 创建线程并指定线程的名称
      • public void setName(String name) : 设置线程的名称
      • Thread(Runnable target, String name) :
  • 问题:

    • 给线程取名字的作用?
      • 为了方便区分当前执行的线程是那个线程
  • 案例代码:


public class MyThreadDemo {
    public static void main(String[] args) {
//        MyThread my1 = new MyThread();
//        MyThread my2 = new MyThread();
//
//        //void setName(String name):将此线程的名称更改为等于参数 name
//        my1.setName("高铁");
//        my2.setName("飞机");

        //Thread(String name)
//        MyThread my1 = new MyThread("高铁");
//        MyThread my2 = new MyThread("飞机");
//
//        my1.start();
//        my2.start();

        //static Thread currentThread​() 返回对当前正在执行的线程对象的引用
        System.out.println(Thread.currentThread().getName());
    }
}

public class MyThread extends Thread {

    public MyThread() {}

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}

  • 设置线程优先级
  • final int getPriority(): 返回此线程的优先级

  • final void setPriority(int newPriority): 更改此线程的优先级线程默认优先级是5;

    • 线程优先级的范围是:1-10
  • 问题1: 线程默认的级别是?

    • 5
  • 问题2: 线程级别的范围是?

    • 1 - 10
  • 线程调度:

    • 分时调度
    • 抢占式调度(java中默认的方式)
  • 案例代码:

public class ThreadPriorityDemo {
    public static void main(String[] args) {
        ThreadPriority tp1 = new ThreadPriority();
        ThreadPriority tp2 = new ThreadPriority();
        ThreadPriority tp3 = new ThreadPriority();

        tp1.setName("高铁");
        tp2.setName("飞机");
        tp3.setName("汽车");

        //public final int getPriority():返回此线程的优先级
//        System.out.println(tp1.getPriority()); //5
//        System.out.println(tp2.getPriority()); //5
//        System.out.println(tp3.getPriority()); //5

        //public final void setPriority(int newPriority):更改此线程的优先级
//        tp1.setPriority(10000); //IllegalArgumentException
//        System.out.println(Thread.MAX_PRIORITY); //10
//        System.out.println(Thread.MIN_PRIORITY); //1
//        System.out.println(Thread.NORM_PRIORITY); //5

        //设置正确的优先级
        tp1.setPriority(5);
        tp2.setPriority(10);
        tp3.setPriority(1);


        tp1.start();
        tp2.start();
        tp3.start();
    }
}


public class ThreadPriority extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}

  • 线程控制
  • static void sleep(long millis)(重点掌握):使当前正在执行的线程停留(暂停执行)指定的毫秒数
  • void join():等待这个线程死亡
  • void setDaemon(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

第二种方式:实现Runnable接口

  • 实现步骤

    • 创建 Runnabel 接口的实现类
    • 重写 run方法
    • 创建 Runnabel 接口的实现类的对象
    • 创建 Thread 对象,将 Runnabel 接口的实现类的对象进行传递
    • 使用 Thread 对象 中的 start方法,开启线程
  • 使用Runnable接口的好处:

    • 避免了java中单一继承局限性
    • 可以方便的进行资源共享
    • 线程Thread 跟 线程要执行的任务run方法有效的分离了,但是要想执行run方法的代码
      必须要创建Thread对象,并启动线程
  • 案例代码:


public class MyRunnableDemo {
	    public static void main(String[] args) {
	        //创建MyRunnable类的对象
	        MyRunnable my = new MyRunnable();

	        //创建Thread类的对象,把MyRunnable对象作为构造方法的参数
	        //Thread​(Runnable target)
	//        Thread t1 = new Thread(my);
	//        Thread t2 = new Thread(my);
	        //Thread​(Runnable target, String name)
	        Thread t1 = new Thread(my,"高铁");
	        Thread t2 = new Thread(my,"飞机");

	        //启动线程
	        t1.start();
	        t2.start();
	    }
	}


public class MyRunnable implements Runnable {

    @Override
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

线程同步

  • 多线程卖票
  • 案例需求:

    • 某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
  • 实现步骤:

    • 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
    • 在SellTicket类中重写run()方法实现卖票,代码步骤如下
    • 判断票数大于0,就卖票,并告知是哪个窗口卖的
    • 卖了票之后,总票数要减1
    • 票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
    • 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
    • 创建SellTicket类的对象
    • 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
    • 启动线程
  • 案例代码:

SellTickets类


public class SellTickets implements Runnable {

	private static int tickets = 100;
	private Object obj = new Object();

	@Override
	public void run() {
		while (true) {

			synchronized (obj) {
				if (tickets > 0) {

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

					System.out.println(Thread.currentThread().getName() +
							"卖出了第" + tickets + "张票");
					tickets--;
				} else {
					System.out.println("票卖完了");
					break;
				}
			}

		}
	}
}


测试类

public class SellTicketsDemo {

	public static void main(String[] args) {

		SellTickets st = new SellTickets();

		new Thread(st, "窗口1").start();
		new Thread(st, "窗口2").start();
		new Thread(st, "窗口3").start();


	}
}

  • 卖票案例的问题
  • 总结:
    • 卖票所出现的问题有那些?

      • 卖重复的票
      • 有负数的票
      • 有0号票
    • 问题产生的原因?

      • 多线程随机的特点,导致卖票的时候出现了以上三个问题
  • 解决线程安全问题
  • 同步机制的介绍
    • 1.什么情况下需要同步
      • 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
      • 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
    • 2.同步技术分类
      • 同步代码块
      • 同步方法
      • Lock锁
  • 使用同步代码块解决数据安全问题
  • 总结:
    • 同步技术的原理

      • 使用 synchronized 同步块将共享数据进行锁起来,多个线程在执行的时候,会抢夺CPU的执行权

        当某一个线程抢到执行权时,会先判断这个锁是否存在,如果存在就获取锁,这个线程就带着这个锁执行

        同步代码块中的代码,直到执行完成之后,才释放锁. 没有锁的线程执行在通过代码块外等。
  • 使用同步方法解决线程安全问题
  • 非静态同步方法

    • 总结:

      • 同步方法:

        把多个线程操作共享数据的代码,放到一个方法中
      • 格式:

        修饰符 synchronized 返回值类型 方法名(参数列表){}
    • 注意:

      • 非静态同步方法的锁对象是谁?

        是this,即当前调用了该方法的对象

  • 静态同步方法

    • 格式:

      修饰符 static synchronized 返回值类型 方法名(参数列表){}
    • 注意:
      • 静态同步方法的锁对象是谁?

      • Class锁

      • 当前类名.class

      • 选择一个锁的对象的时候,优先选择当前类的字节码对象这个锁

  • 线程同步安全的类
  • 使用Lock锁解决线程安全问题
  • Lock概述 :

    • Lock锁中通过lock和unlock两个方法, 可以解决线程的安全问题, 因为是调用方法解决, 所以体现的更为面向对象
  • 方法摘要

    • public void lock() : 加同步锁。
    • public void unlock() :释放同步锁

补充: Lock锁是jdk1.5版本之后出现的.

生产者和消费者

  • 等待唤醒机制概述
  • 铺垫 : 实现线程间的通讯, 使用的就是等待唤醒机制

  • 问题 : 等待唤醒机制使用的是哪两个方法

  • 总结:

    • 等待唤醒机制使用的是哪两个方法
      1. 等待 -> wait : 让当前线程在此进入无限等待状态, 需要另一条线程对其进行唤醒.
      2. 唤醒 -> notify : 随机唤醒单个线程.
        notifyAll() : 唤醒所有等待的线程
  • 等待唤醒机制需求分析(生产者和消费者)
  • 安全步骤:

  • 生产者消费者案例中包含的类:

    • 奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
    • 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
    • 消费者类(Customer):实现Runna ble接口,重写run()方法,调用获取牛奶的操作
    • 测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下
      ①创建奶箱对象,这是共享数据区域
      ②创建消费者创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
      ③对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
      ④创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
      ⑤启动线程
  • 案例代码

奶箱类

public class Box {

	private int milk;	//表示第x瓶奶
	private boolean state = false;	//表示奶箱中是否有奶

	public Box() {
	}

	//存牛奶操作
	synchronized void put(int milk) {

		if (state) {	//如果奶箱中有奶,等待消费者消费
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.milk = milk;
		System.out.println("送奶工正在将第" + this.milk + "瓶奶装入奶箱");

		state = true;
		notifyAll();	//唤醒其他所有等待线程
	}

	//取牛奶操作
	synchronized void get() {

		if (!state) {	//如果奶箱中没有奶,等待生产者生产
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("用户取出了第" + this.milk + "瓶奶");
		state = false;	//消费完毕后更改状态
		notifyAll();	//唤醒其他所有等待线程

	}
}

生产者类

public class Producer implements Runnable {

	Box b;

	public Producer() {
	}

	public Producer(Box b) {
		this.b = b;
	}

	@Override
	public void run() {
		for (int i = 1; i <= 5; i++) {
			b.put(i);
		}
	}

}

消费者类

public class Consumer implements Runnable {

	Box b;

	public Consumer(Box b) {
		this.b = b;
	}

	public Consumer() {
	}

	@Override
	public void run() {

		while (true) {
			b.get();
		}
	}

}

测试类

public class BoxDemo {

	/*
		生产者消费者案例
	 */
	public static void main(String[] args) {

		Box b = new Box();

		Producer p = new Producer(b);
		Consumer c = new Consumer(b);

		new Thread(p, "生产者").start();
		new Thread(c, "消费者").start();


	}
}

你可能感兴趣的:(Java进阶)