多线程基础回顾
1. 线程的三种创建方式
程序(program):是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。 (程序是静态的)
进程(process):是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。 (进程是动态的)是一个动的过程 ,进程的生命周期 : 有它自身的产生、存在和消亡的过程
线程(thread),进程可进一步细化为线程, 是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程的。
1.1 通过继承Thread类
public class TestThread extends Thread {
public TestThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++){
System.out.println(this.getName() + i);
}
}
}
1.2 通过实现Runnable接口
public class TestThread implement Runnable {
public TestThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++){
System.out.println(this.getName() + i);
}
}
}
1.3 实现Callable接口
public class TestRundomNum implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return new Random().nextInt(10);//返回一个10以内的随机数
}
}
class Test{
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建子线程
TestRundomNum trn = new TestRundomNum();
FutureTask<Integer> ft = new FutureTask<>(trn);
Thread t = new Thread(ft);
t.start();
Integer integer = ft.get();
System.out.println(integer);
}
}
对比第一种和第二种创建线程的方式发现,无论第一种继承Thread类的方式还是第二种实现Runnable接口的方式,都需要有一个run方法,
但是这个run方法有不足:
(1)没有返回值
(2)不能抛出异常
基于上面的两个不足,在JDK1.5以后出现了第三种创建线程的方式:实现Callable接口:
实现Callable接口好处:(1)有返回值 (2)能抛出异常
缺点:线程创建比较麻烦
2. 线程的声明周期
3. 线程常用方法
- start() : 启动当前线程,表面上调用start方法,实际在调用线程里面的run方法
- run() : 线程类 继承 Thread类 或者 实现Runnable接口的时候,都要重新实现这个run方法,run方法里面是线程要执行的内容
- currentThread :Thread类中一个静态方法:获取当前正在执行的线程
- setName 设置线程名字
- getName 读取线程名字
- setPriority设置优先级,同优先级别的线程,采取的策略就是先到先服务,使用时间片策略,如果优先级别高,被CPU调度的概率就高,级别:1-10 默认的级别为5。
- join方法:当一个线程调用了join方法,这个线程就会先被执行,它执行结束以后才可以去执行其余的线程。
注意:必须先start,再join才有效。 - sleep,线程睡眠
- setDeamon将线程设置为守护线程
4. 线程安全问题
买火车票程序:
//买火车票线程
public class BuyTicketThread extends Thread{
public BuyTicketThread(String name){
super(name);
}
static int ticketNum = 10;
@Override
public void run() {
for(int i = 1; i <= 100; i++){
if(ticketNum > 0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了从武汉到上海的第" + ticketNum-- + "火车票");
}
}
}
public class Test {
public static void main(String[] args) {
BuyTicketThread t1 = new BuyTicketThread("窗口1");
BuyTicketThread t2 = new BuyTicketThread("窗口2");
BuyTicketThread t3 = new BuyTicketThread("窗口3");
t1.start();
t2.start();
t3.start();
}
}
存在问题,相同编号票或者编号为负数的票
4.1同步代码块
public class BuyTicketThread extends Thread{
public BuyTicketThread(String name){
super(name);
}
static int ticketNum = 10;
@Override
public void run() {
for(int i = 1; i <= 100; i++){
//同步代码块加锁
synchronized(BuyTicketThread.class){
if(ticketNum > 0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了从武汉到上海的第" + ticketNum-- + "火车票");
}
}
}
}
4.2同步方法
public class BuyTicketThread extends Thread{
public BuyTicketThread(String name){
super(name);
}
static int ticketNum = 10;
@Override
public void run() {
for(int i = 1; i <= 100; i++){
buyTicket();
}
}
public static synchronized void buyTicket(){
if(ticketNum > 0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了从武汉到上海的第" + ticketNum-- + "火车票");
}
}
}
4.3 Lock锁
JDK1.5后新增新一代的线程同步方式:Lock锁
与采用synchronized相比,lock可提供多种锁方案,更灵活synchronized是Java中的关键字,这个关键字的识别是靠JVM来识别完成的呀。是虚拟机级别的。
但是Lock锁是API级别的,提供了相应的接口和对应的实现类,这个方式更灵活,表现出来的性能优于之前的方式。
public class BuyTicketThread implements Runnable{
Lock lock = new ReentrantLock();//获得一把锁
int ticketNum = 10;
@Override
public void run() {
for(int i = 1; i <= 100; i++){
lock.lock();
try{
if(ticketNum > 0){
Thread.sleep(200);
System.out.println("我在"+Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张火车牌");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();//释放锁
}
}
}
}
Lock和synchronized的区别
1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock----同步代码块(已经进入了方法体,分配了相应资源)----同步方法(在方法体之外)
线程安全,效率低
线程不安全,效率高
可能造成死锁:
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
5.线程通信
应用场景:生产者和消费者问题
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
5.1同步代码块
//产品类
public class Product {
private String brand;//品牌
private String name;//名字
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//生产者
public class ProducerThread extends Thread{
private Product product;
public ProducerThread(Product product) {
this.product = product;
}
@Override
public void run() {
for(int i = 1; i <= 10; i++){
synchronized (product){
if(i%2 == 0){
//生成小米手机
product.setBrand("小米");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setName("手机");
}else{
//生成华为平板
product.setBrand("华为");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setName("平板");
}
//输出信息
System.out.println("生产者生产了一个" + product.getBrand() + "的" + product.getName());
}
}
}
}
//消费者线程
public class CustomerThread extends Thread{
private Product product;
public CustomerThread(Product product){
this.product = product;
}
@Override
public void run() {
//消费十个产品
for(int i = 0; i <= 10; i++){
synchronized(product){
System.out.println("消费者消费了" + product.getBrand() + "的" + product.getName());
}
}
}
}
//测试
Product p = new Product();
ProducerThread pt = new ProducerThread(p);
CustomerThread ct = new CustomerThread(p);
pt.start();
ct.start();
5.2同步方法
public class Product {
private String brand;//品牌
private String name;//名字
//true表示有商品,false表示没有商品
private boolean flag = false;//默认没有商品
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//生成产品
public synchronized void setProduct(String brand,String name){
if(flag){
//有商品
try {
wait();//进行等待池等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setBrand(brand);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setName(name);
System.out.println("生产者生产了一个" + this.getBrand() + "的" + this.getName());
flag = true;
notify();//唤醒消费者线程消费
}
public synchronized void getProduct(){
if(!flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者消费了" + this.getBrand() + "的" + this.getName());
flag = false;
notify();//唤醒生产者线程生产
}
}
//消费者线程
public class CustomerThread extends Thread{
private Product product;
public CustomerThread(Product product){
this.product = product;
}
@Override
public void run() {
//消费十个产品
for(int i = 1; i <= 10; i++){
product.getProduct();
}
}
}
//生产者
public class ProducerThread extends Thread{
private Product product;
public ProducerThread(Product product) {
this.product = product;
}
@Override
public void run() {
for(int i = 1; i <= 10; i++){
if(i % 2 == 0){
//生成小米手机
product.setProduct("小米","手机");
}else{
//生成华为平板
product.setProduct("华为","平板");
}
}
}
}
Product p = new Product();
ProducerThread pt = new ProducerThread(p);
CustomerThread ct = new CustomerThread(p);
pt.start();
ct.start();
5.3 Lock锁通信
public class Product {
private String brand;
private String name;
//表示是否有商品,true表示生产者已经生产了商品,false表示消费者已经消费者商品
private boolean flag = false;
Lock lock = new ReentrantLock();//锁机制
//生产者等待队列
Condition produceCondition = lock.newCondition();
//消费者等待队列
Condition customerCondition = lock.newCondition();
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setProduct(String brand,String name){
lock.lock();//获取锁
try{
if(flag){
//生产者进入等待队列
try {
produceCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产产品
this.setBrand(brand);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setName(name);
System.out.println("生产者生产了" + this.getBrand() + "的" + this.getName());
flag = true;
customerCondition.signal();//唤醒消费者队列中等待线程消费
}finally {
lock.unlock();//释放锁
}
}
public void getProduct(){
lock.lock();//上锁
try{
if(!flag){
try {
customerCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者消费了" + this.getBrand() + "的" + this.getName());
flag = false;
produceCondition.signal();//唤醒生产者线程生产
}finally {
lock.unlock();//释放锁
}
}
}
public class CustomerThread extends Thread{
private Product product;
public CustomerThread(Product product) {
this.product = product;
}
@Override
public void run() {
for(int i = 1; i <= 10; i++){
product.getProduct();
}
}
}
public class ProduceThread extends Thread {
private Product product;
public ProduceThread(Product product){
this.product = product;
}
@Override
public void run() {
for(int i = 1; i <= 10; i++){
if(i % 2 == 0){
product.setProduct("小米","手机");
}else{
product.setProduct("华为","笔记本");
}
}
}
}
Product p = new Product();
ProduceThread pd = new ProduceThread(p);
CustomerThread ct = new CustomerThread(p);
pd.start();
ct.start();
Condition是在Java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。
它的更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition
一个Condition包含一个等待队列。一个Lock可以产生多个Condition,所以可以有多个等待队列。
在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而Lock(同步器)拥有一个同步队列和多个等待队列。
Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。
调用Condition的await()、signal()、signalAll()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
-
Conditon中的await()对应Object的wait();
-
Condition中的signal()对应Object的notify();
-
Condition中的signalAll()对应Object的notifyAll()。
void await() throws InterruptedException
造成当前线程在接到信号或被中断之前一直处于等待状态。
与此 Condition 相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下四种情况之一 以前,当前线程将一直处于休眠状态:
- 其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;或者
- 其他某个线程调用此 Condition 的 signalAll() 方法;或者
- 其他某个线程中断当前线程,且支持中断线程的挂起;或者
- 发生“虚假唤醒”
在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁。在线程返回时,可以保证它保持此锁。
void signal()
唤醒一个等待线程。
如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。
void signalAll()
唤醒所有等待线程。
如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。