多线程

多线程

前言:
1、什么是进程?

一个正在运行的程序就是一个进程。进程是操作系统资源分配(计算资源,比如CPU,存储:内存)的最小单位。

2、什么是线程

线程是资源调度的最小单位(线程在消耗资源)。线程的主要作用执行任务,一个线程可以执行很多任务。

什么是单线程?

如果一个进程,只有一个线程。这样的程序叫做单线程程序。

好处:资源可以最大化使用。不会出现争夺资源的问题。

缺陷:效率很低,容易阻塞。无法处理并发任务(例如:多人聊天)。

当你程序启动的时候,JVM会创建一个线程执行你的main函数,这个线程称为主线程。

什么是多线程?

如果一个进程,拥有不止一个线程。这样的程序称为多线程程序。

优势:可以同时执行多个任务。提高运行的效率。

什么时候使用多线程?

  1. 多个任务互不影响,任务之间没有交集,谁先执行完,谁后执行完无所谓。这种时候可以使用多线程,让多个任务同时执行。
  2. 当你有一个任务很耗时,可以把这个耗时的任务放到一个单独线程里执行,这样就不会阻塞程序的执行。
  3. 你的需求只能靠多线程(多人聊天,英雄联盟各个角色的操作)完成的时候,要使用多线程。

如何使用多线程?

  1. 使用Thread的子类
  2. 让一个类实现Runable接口
一、使用Thread的子类
  1. 创建一个类继承于Thread。
  2. 重写这个类的run方法。
public class MyThread extends Thread {

	public MyThread() {
		super();
	}

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

	@Override
	public void run() {
		System.out.println("我是一个子线程");
		for(int i = 0; i < 500000; i++) {
			System.out.println(i);
		}
	}
}

MyThread是一个类,因此可以根据需要添加属性以及方法,也就是说普通类能有的这个类都可以有。

  1. 创建MyThread类的对象
  2. 启动线程
public class TestMultiplyThread2 {

	public static void main(String[] args) {
		
		Thread mt = new MyThread("线程A");
		//mt.run();//如果直接调用run方法,并不会新的线程中执行任务。
		mt.start();//start方法才在新的线程中执行run方法。
		
		System.out.println("hello world");
	}

}

匿名类的写法

public class TestMultiplyThread2 {

	public static void main(String[] args) {
		Thread t1 = new Thread("线程B") {
			@Override
			public void run() {
				System.out.println("我是一个子线程");
				for(int i = 0; i < 500000; i++) {
					System.out.println(i);
				}
			}
		};
		t1.start();
		
		System.out.println("hello world");
	}

}
二、让一个类实现Runnable接口
  1. 创建一个类实现Runnable接口
  2. 实现接口中的run方法
public class MyRunnable implements Runnable {

	@Override
	public void run() {
		System.out.println("我是一个子线程");
		for(int i = 0; i < 500000; i++) {
			System.out.println(i);
		}
	}
}
  1. 创建实现类的对象
  2. 实现类的对象作为Thread类的参数
  3. 启动线程
public class TestMultiplyThread3 {

	public static void main(String[] args) {
		Runnable mr = new MyRunnable();
		Thread t = new Thread(mr);
		t.start();
		System.out.println("hello world");
	}
}

匿名类的写法:

public class TestMultiplyThread3 {

	public static void main(String[] args) {
//		Runnable mr = new MyRunnable();
		Runnable mr = new Runnable() {
			@Override
			public void run() {
				System.out.println("我是一个子线程");
				for(int i = 0; i < 500000; i++) {
					System.out.println(i);
				}
			}
		};
		Thread t = new Thread(mr);
		t.start();
		System.out.println("hello world");
	}
}

lambda表达式写法:

public class TestMultiplyThread3 {

	public static void main(String[] args) {

		Runnable mr = () -> {
			System.out.println("我是一个子线程");
			for(int i = 0; i < 500000; i++) {
				System.out.println(i);
			}
		};
		Thread t = new Thread(mr);
		t.start();
		System.out.println("hello world");
	}
}

多线程的常用方法

currentThread() 获取当前线程对象。 类方法

setName(String name) 设置线程的名字。

getName() 获取线程的名字。

setPriority(int priority) 设置线程的优先级。 优先级的取值范围[1,10],默认是5

getPriority() 获取线程的优先级。

getState() 获取线程的状态

join() 执行该线程,会阻塞当前线程。

sleep(long millis) 休眠指定时间(单位毫秒),会阻塞当前线程。类方法

start() 启动线程

yield() 暂定该线程的执行,交出CPU的使用权。

线程的同步

什么是线程的同步?

并行指的是线程同时执行。

同步不是线程同时执行,而是线程不同时执行。同步本质指的是数据的同步。一般情况下,线程之间是相互独立,如果都去访问同一个变量,极有可能让这个数据变乱。如果不想让数据变乱,应在不让他们同时访问同一个变量。这个控制过程称为线程同步。

如何实现线程的同步?
一、同步代码块
synchronized(对象){
	共享资源//我们所谓的那个变量。
}

示例代码:

public class SellWindow implements Runnable {
	
	private int tickets = 100;
	private Object lock = new Object();
	
	@Override
	public void run() {
		while(tickets > 0) {
			String threadName = Thread.currentThread().getName();
			synchronized (lock) {
				tickets--;
				if(tickets >= 0) {
					System.out.println(threadName + "卖掉1张票,剩余" + tickets);
				}
			}
		}
	}

}

同步代码块synchronized (对象),多个线程要公用同一个对象,才能真正意义上加上锁。对象没有特殊要求,可以是任何继承于Object类的对象。包括this

二、同步方法

被synchronized修饰的方法称为同步方法。

public class SellWindow3 implements Runnable {
	
	private int tickets = 100; 
	
	@Override
	public void run() {
		while(tickets > 0) {
			
			//method();
			method2();
		}
	}
	
	public void method() {
		synchronized (this) {
			tickets--;
			String threadName = Thread.currentThread().getName();
			if(tickets >= 0) {
				System.out.println(threadName + "卖掉1张票,剩余" + tickets);
			}
		}
	}

	public synchronized void method2() {
		tickets--;
		String threadName = Thread.currentThread().getName();
		if(tickets >= 0) {
			System.out.println(threadName + "卖掉1张票,剩余" + tickets);
		}
	}
}
使用锁对象上锁和解锁
public class SellWindow4 implements Runnable {

	private int tickets = 100;
	Lock lock = new ReentrantLock();
	@Override
	public void run() {
		while (tickets > 0) {
			String threadName = Thread.currentThread().getName();
			lock.lock();
			tickets--;
			if (tickets >= 0) {
				System.out.println(threadName + "卖掉1张票,剩余" + tickets);
			}
			lock.unlock();
		}
	}
}
线程同步小节

同步不是线程同时执行,而是线程不同时执行。同步本质指的是数据的同步。一般情况下,线程之间是相互独立,如果都去访问同一个变量,极有可能让这个数据变乱。如果不想让数据变乱,应在不让他们同时访问同一个变量。这个控制过程称为线程同步。

在开发中,如果多个线程访问一个资源(某变量),为了保证数据的正确性,可以使用3种方式来实现线程同步:使用synchronized(){}代码块,使用synchronized方法,或者给共享资源加锁和解锁。

线程通信

什么是线程通信?

不同线程之间可以相互的发信号。这就是线程通信。之所以需要进行线程通信,是因为有些时候,一个线程的执行需要依赖另外一个线程的执行结果。在结果到来之前,让线程等待(wait),有了结果只之后再进行后续的操作。对于另外一个线程而言,计算完结果,通知(notify)一下处于等待状态的线程.

线程通信借助的是Object类的wait,notify,nitifyall方法。

wait作用是让当前线程阻塞,阻塞多久,取决于有没有其他线程唤醒它。

notify作用是唤醒处于wait状态的线程。必须是同一个监视器下的线程。

notifyall作用是唤醒所有处于wait状态的线程。必须是同一个监视器下的线程。

一般情况下,多线程里会出现线程同步的问题,我们不但要进行线程通信,还要解决线程同步的问题。

生产者-消费者模式

这是一个比较经典的多线程场景。有商品的时候,消费者才可以消费,没有商品的时候,消费者等待。商品库存充足的时候,生产者等待,库存不满的时候,生产者生产商品。

public class Saler {//售货员类
	private int productCount = 10;	//商品数量
	public synchronized void stockGoods() {
		if(productCount < 2000) {
			productCount++;
			System.out.println(Thread.currentThread().getName() + "生产了1件商品,库存是:" + productCount);
			this.notifyAll();
		}else {
			System.out.println("库存满了");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	public synchronized void sellGoods() {
		if(productCount > 0) {
			productCount--;
			System.out.println(Thread.currentThread().getName() + "购买了1件商品,库存剩余:" + productCount);
			 
			this.notifyAll();
		}else {
			System.out.println("库存不足");
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class Productor implements Runnable{//生产者类
	private Saler s;

	public Productor(Saler s) {
		super();
		this.s = s;
	}

	@Override
	public void run() {
		while(true) {
			s.stockGoods();
		}
	}
	
}
public class Customer implements Runnable{//消费者类

	private Saler s;
	
	
	public Customer(Saler s) {
		super();
		this.s = s;
	}


	@Override
	public void run() {
		while(true) {
			s.sellGoods();
		}
	}
	
}
public class TestTread {

	public static void main(String[] args) {
		//生产者-消费者模式。模拟生产和消费过程
		Saler s = new Saler();
		Customer c = new Customer(s);
		Productor p = new Productor(s);
		Thread t1 = new Thread(c, "客户1");
		t1.start();
		Thread t2 = new Thread(p,"厂家");
		t2.start();
		Customer c2 = new Customer(s);
		Thread t3 = new Thread(c2, "客户2");
		t3.start();
	}

}

线程的生命周期

线程的生命周期指的是线程从创建到销毁的过程。在整个过程中,不同的时期线程有不同的状态。而且在程序运行期间会发生状态的转换。

官方定义的线程状态如下:

NEW:新建状态,指的是线程已经创建,但是尚未start()。

RUNNABLE:可运行状态(已经调用了start方法),已经准备就绪,一旦抢到CPU就立即执行。

BLOCKED:阻塞状态,处于阻塞状态的线程正在等待进入Synchronized块(或方法)。

WAITING:等待状态,等待其他线程执行任务。直到其他线程任务结束或者收到notify信号。

TIMED-WAITING:等待状态,限定时间的等待状态。

TERMINATED:终止状态。线程要运行的任务已经结束。

生活中程序员会把线程划分为如下状态:

NEW:新建状态,指的是线程已经创建,但是尚未start()。

RUNNABLE:可运行状态(已经调用start方法),已经准备就绪,一旦抢到CPU就立即执行。

RUNNING:正在运行状态,已经抢到CPU,正在执行代码片段。

BLOCKED:阻塞状态。

DEAD:死亡状态。线程的任务已经结束。

线程的状态转换

线程的状态转换

线程池

什么是线程池?

水池:存放水的池子。

线程池:存放线程的池子。

Java中的线程池:是一个管理线程的池子。可以在需要的时候开辟线程,可以控制最大开辟的线程个数,可以在不需要的时候关闭线程,可以让任务排队执行。这些管理过程不需要我们干预,线程池能帮我们完成。我们所要做的就是往线程池中放任务。

为什么要有线程池?

多线程解决了任务并发问题,但是开辟和关闭线程很消耗系统的性能,开辟和关闭一个线程要处理很多细节,频繁的开辟和关闭线程会给系统增加很多开销。

线程池使用了重用的概念,可以控制线程开辟的数量,复用这些线程执行任务。这样就不用频繁的开辟和关闭线程了。

线程池的使用
public class TestThreadPool {

	public static void main(String[] args) {
		
		ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 20, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),new ThreadPoolExecutor.DiscardPolicy());
		//pool.allowCoreThreadTimeOut(false);
		pool.submit(new Runnable() {
			@Override
			public void run() {
				System.out.println("我是一个任务"+Thread.currentThread().getName());
			}
		});
		for(int i = 0; i < 200; i++) {
			pool.execute(new Runnable() {
				@Override
				public void run() {
					System.out.println("我是另外一个任务"+Thread.currentThread().getName());
				}
			});
		}
		
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(pool.getPoolSize());
	}

}

pool可以指定核心线程的个数,最大允许的线程的个数,超过核心线程数以后,多久关闭线程,任务队列,以及任务拒绝的机制。

线程池工具类

Exectors 是线程池工具类,可以帮我们快速构建线程池。

三种常见的线程池:

  1. 固定线程个数的线程池
  2. 不限线程个数的线程池
  3. 单个线程的线程池(串行任务池)
public class TestExecutors {

	public static void main(String[] args) {
		ExecutorService es = Executors.newFixedThreadPool(3);
//		ExecutorService es = Executors.newCachedThreadPool();
//		ExecutorService es = Executors.newSingleThreadExecutor();
		es.submit(new Runnable() {
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName());
				
			}
		});
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值