020.day20 线程概述 多线程优缺点 线程的创建 线程常用方法 生命周期 多线程同步

多线程

一、线程概述

1. 进程

正在执行的应用程序(java.exe),一个可执行的程序一次运行的过程

  • 独立性:不同进程之间相互独立
  • 动态性:是一直活动的
  • 并发性:多个进程可以在单个处理器上同时运行

2. 线程

线程是程序中的一个执行流,每个线程都有自己的专有寄存器,但代码区是共享的,即不同的线程可以执行同样的函数

  • 各线程在运行过程中可能会出现资源竞争
  • CPU在不同的线程之间切换
  • 每个线程的执行机会相对随机

3. 进程与线程

  • 一个线程只能属于一个进程
  • 一个进程可以有多个线程,至少有一个主线程
  • 同一进程的所有线程共享系统分配给该进程的资源
  • 线程是指进程内的一个执行单元,也是进程内的可调度实体
  • 系统创建进程时需要重新分配资源,而创建线程相对容易,因此效率更高

4. 多线程

多线程是指程序中包含多个执行流,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务

5. 主线程

任何一个Java程序启动时,一个线程立刻运行,执行main方法(程序的入口),这个线程称为程序的主线程

  • 主线程是产生其它子线程的线程
  • 通常必须最后关闭,因为它要执行其它子线程的关闭工作

二、多线程优缺点

(1)优点

  • Java支持编写多线程程序
  • 可以并发执行多个任务
  • 最大限度的减少CPU的闲置时间,提高CPU的利用率

(2)缺点

  • 线程越多占用内存越多
  • 线程之间对共享资源访问会互相影响
  • 线程过多会导致控制复杂化

三、线程的创建

Thread类也实现了Runnable接口

1. 继承Thread类

Thread类中的run方法本身并不执行任何操作,需要继承Thread类后重写该方法

  • MyThread
// TODO 线程实现实现方式:继承Thread类
		System.out.println(111);
		// 1.声明一个定义的线程对象 - 父类引用指向子类对象
		Thread thread1 = new MyThread();
		thread1.setName("子线程1");
		// 2.使一个线程进入到就绪状态:start()方法
		// 线程启动后会调用相应的run方法
		thread1.start();
		// 直接调用run方法相当于执行一个普通类下的普通方法
		// 此时程序会等待方法调用完成后按顺序执行
		 thread1.run();
		System.out.println(222);
		// 多个子线程在执行时,执行的机会相对随机
		Thread thread2 = new MyThread("子线程2");
		thread2.start();
		 thread2.run();
		System.out.println(333);
  • ThreadTest
// 实现线程的方式 - 继承Thread类
public class MyThread extends Thread {
	
	// 显示声明空的构造方法
	public MyThread() {
		
	}
	
	// 子类中调用父类的构造方法
	public MyThread(String name) {
		// 在线程实例化时指定线程名称
		super(name);
	}
	
	// 重写run方法
	public void run() {
		for (int i = 0; i < 10; i++) {
			// Thread.currentThread()获得当前所在线程的对象
			if (i == 5) {
				if (Thread.currentThread().getName().equals("子线程1")) {
					// 当某一个线程中出现异常时,不会影响另外一个线程
					System.out.println(1/0);
				}
			}
			System.out.println(Thread.currentThread() + ":" + i);
		}
	}

}
  • 小练习
// TODO 多线程练习:相对独立的线程 - 继承Thread
		// 某个商城进行促销活动,商城内存在多个商家
		// -》类比思想:商场开门 - 主线程启动
		System.out.println("商场开门");
		// 每个商家售卖不同的商品,并有不同的库存
		Thread thread1 = new ShopThread("夏季T恤", 35, "李宁品牌服装店");
		Thread thread2 = new ShopThread("洁面神器", 50, "韩国护肤品牌店");
		Thread thread3 = new ShopThread("kindle", 30, "亚马逊直销店");
		// -》封装实体:能够刻画商家售卖商品以及表示库存
		// 描述该商城当天开门直到所有商家商品售空的过程
		thread1.start();
		thread2.start();
		thread3.start();
		thread1.join();
		thread2.join();
		thread3.join();
		// -》类比思想:商品售空,库存为0时线程正常停止
		// 使用两种方式实现
		System.out.println("商场关门");
// 多家店铺同时售卖商品
public class ShopThread extends Thread {

	public ShopThread() {

	}

	public ShopThread(String productName, Integer count, String name) {
		super(name);
		this.count = count;
		this.productName = productName;
	}

	// 售卖的商品
	private String productName;
	// 商品的库存
	private Integer count;

	public String getProductName() {
		return productName;
	}

	public void setProductName(String productName) {
		this.productName = productName;
	}

	public Integer getCount() {
		return count;
	}

	public void setCount(Integer count) {
		this.count = count;
	}

	// 使用相对随机的方式减少商品的库存
	public void run() {
		for (int i = count; i > 0;) {
			if ((--i) != 0) {
				// 通过线程的name属性刻画商家的名称
				System.out.println(Thread.currentThread().getName() + "卖出一件" + this.productName + ",仅剩" + i + "件");
			} else {
				System.out.println(Thread.currentThread().getName() + "商品已售完,关店");
			}
		}
	}

}

2. 实现Runnable接口

  • MyRunnable
// 自定义类实现Runnable接口
public class MyRunnable implements Runnable {
    // 重写run方法
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
    		// 获得当前线程的名称
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
}
  • RunnableTest
public class RunnableTest {

	public static void main(String[] args) {
        // 将一个实现了Runnalbe接口的实现类的实例传入
		MyRunnable myRunnable1 = new MyRunnable();
		MyRunnable myRunnable2 = new MyRunnable();
		// 调用相应的构造方法,指定线程名
		Thread thread1 = new Thread(myRunnable1, "线程1");
        // 启动线程
		thread1.start();
        // 调用相应的构造方法,指定线程名		
		Thread thread2 = new Thread(myRunnable2, "线程2");
		// 启动线程
		thread2.start();
	}
}
  • 小练习
// TODO 多线程练习:相对独立的线程 - 实现Runnable
		System.out.println("商场开门");
		// 实例化Runnable接口的对象
		Runnable runnable1 = new ShopRunnable("夏季T恤", 350);
		Runnable runnable2 = new ShopRunnable("洁面神器", 500);
		Runnable runnable3 = new ShopRunnable("kindle", 300);
		// 通过线程控制目标对象
		Thread thread1 = new Thread(runnable1, "李宁品牌服装店");
		Thread thread2 = new Thread(runnable2, "韩国护肤品牌店");
		Thread thread3 = new Thread(runnable3, "亚马逊直销店");
		// 线程启动
		thread1.start();
		thread2.start();
		thread3.start();
		// 可以设置最长等待时长
		// 超过等待时长,继续运行当先线程内容
		// 不指定时长时,等待线程完全执行完毕再执行当前线程
		thread1.join(10);
		thread2.join(10);
		thread3.join(10);
		System.out.println("商场关门");
// 多家店铺同时售卖商品
public class ShopRunnable implements Runnable {

	public ShopRunnable() {
		
	}

	public ShopRunnable(String productName, Integer count) {
		this.count = count;
		this.productName = productName;
	}

	// 售卖的商品
	private String productName;
	// 商品的库存
	private Integer count;

	public String getProductName() {
		return productName;
	}

	public void setProductName(String productName) {
		this.productName = productName;
	}

	public Integer getCount() {
		return count;
	}

	public void setCount(Integer count) {
		this.count = count;
	}
	
	// 使用相对随机的方式减少商品的库存
	public void run() {
		for (int i = count; i > 0;) {
			if ((--i) != 0) {
				// 通过线程的name属性刻画商家的名称
				System.out.println(Thread.currentThread().getName() + "卖出一件" + this.productName + ",仅剩" + i + "件");
			}else {
				System.out.println(Thread.currentThread().getName() + "商品已售完,关店");
			}
		}
	}

}

3. 内部类方式创建

  • ThreadInner
public class ThreadInner {

	public static void main(String[] args) {
		// 使用匿名内部类实现线程执行具体内容
		new Thread("新线程") {
			
			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					System.out.println(this.getName() + ":" + i);
				}
			}
		// 启动线程
		}.start();
	}
}
  • RunnableInner
public class RunnableInner {
	
	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. 设置线程名称

  • setName(String name)
  • Thread(String name)

2. 设置线程休眠

  • sleep(long millis):进入阻塞状态,时间单位为毫秒

3. 设置线程优先级

默认情况下线程的优先级与创建它的父线程具有相同的优先级,提升抢到时间片的概率,从而获得更多的执行机会

  • setPriority(int newPriority):参数范围为1-10,需要在执行start()方法前设置

4. 等待某一线程终止

  • join():先执行start()方法,再执行join方法,当前线程执行完毕后,会继续执行其它线程

5. 后台线程

也称为守护线程或用户线程,如JVM的垃圾回收线程,必须在start()前执行,如果所有的前台线程都死亡,则会自动死亡

  • setDaemon(boolean on)

五、生命周期

线程启动后进入到就绪状态,多个线程在同时运行时,不断的在争抢CPU的时间片

  • 新生:实例化完成
  • 就绪:准备运行,暂时没有分配到时间片
  • 运行:分配到时间片,执行相关任务
  • 阻塞:由于某些特殊情况导致阻塞,会在合适的机会重新进入就绪状态
  • 死亡:线程结束(可以使用stop()方法强制结束)

六、多线程同步

把竞争访问的资源标识为private
使用synchronized(同步的)关键字同步方法或代码块

  • Ticket
public class Ticket implements Runnable {

	// 使用private定义竞争资源
	private int ticket = 100;

	@Override
	public void run() {
		while (true) {
			// 对当前线程进行同步加锁
			synchronized (this) {
				ticket--;
				// 当车票售空时跳出循环
				if (ticket < 0) {
					break;
				}
				System.out.println(Thread.currentThread().getName() + "购买,当前剩余票数:" + ticket);
			}
		}
	}
}
  • TicketTest
public class TicketTest {
    // 模拟两名旅客的抢票
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		Thread thread1 = new Thread(ticket,"旅客1");
		Thread thread2 = new Thread(ticket,"旅客2");
		thread1.start();
		thread2.start();
	}
}
posted @ 2018-08-08 19:21  Yokiia  阅读(299)  评论(0编辑  收藏  举报