一、线程简介
- 程序:为完成特定任务,有某种语言编写的一组指令集和。即指一段静态的代码,静态对象。
- 进程:是程序执行一次的过程,或正在运行中的程序。是一个动态的过程,有它自身的产生、存在和消亡的过程。
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时为每个进程分配不同的内存区域 - 线程:进程可以进一步细分为线程,是一个程序内部的一条执行路径
(1)一个进程同时并行执行多个线程,就是支持多线程
(2)线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
(3)一个进程中的多个线程共享相同的内存单元和内存地址空间-------->它们从同一堆中分配对象,可以访问相同的变量和对象,这就使得线程间的通信更简便、高效,但多个线程操作共享的系统资源可能会带来安全隐患。
(4)线程开启不一定立即执行,由CPU调度执行
(5)线程创建四种方式,解决线程安全有三种方式
(6)一个java程序至少 有三个线程:main()主线程、gc()垃圾回收线程、异常处理线程,发生异常 会影响主线程 - 线程分类
(1)守护线程
(2)用户线程
二、线程创建
1.继承Thread类(重点)
(1)继承thread类
package thread.demo;
/**
* 概述:
* 作者:zhujie
* 创建时间:2021/07/22 14:12
*/
public class ExtendsThread extends Thread{
private static int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
(2)创建线程
package thread.demo;
import java.util.concurrent.*;
/**
* 概述:
* 作者:zhujie
* 创建时间:2021/07/22 14:19
*/
public class TicketTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//继承Thread方式卖票
ExtendsThread t1 = new ExtendsThread();
ExtendsThread t2 = new ExtendsThread();
ExtendsThread t3 = new ExtendsThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
2.实现Runnable接口(重点)
(1)实现Runnable接口
package thread.demo;
/**
* 概述:
* 作者:zhujie
* 创建时间:2021/07/22 14:18
*/
public class ImplementsRunnable implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
(2)创建线程
package thread.demo;
import java.util.concurrent.*;
/**
* 概述:
* 作者:zhujie
* 创建时间:2021/07/22 14:19
*/
public class TicketTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//实现Runnable方式
ImplementsRunnable run = new ImplementsRunnable();
Thread t4 = new Thread(run);
Thread t5 = new Thread(run);
Thread t6 = new Thread(run);
t4.setName("窗口4");
t5.setName("窗口5");
t6.setName("窗口6");
t4.start();
t5.start();
t6.start();
}
}
3.实现Callable接口
(1)实现Callable接口
package thread.demo;
import java.util.concurrent.Callable;
/**
* 概述:
* 作者:zhujie
* 创建时间:2021/11/04 14:42
*/
public class ImplementsCallable implements Callable<String> {
private static int ticket = 100;
@Override
public String call() throws Exception {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
ticket--;
} else {
break;
}
}
return "成功了";
}
}
(2)创建线程
package thread.demo;
import java.util.concurrent.*;
/**
* 概述:
* 作者:zhujie
* 创建时间:2021/07/22 14:19
*/
public class TicketTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//实现Callable接口
ImplementsCallable callable = new ImplementsCallable();
FutureTask<String> task1 = new FutureTask<>(callable);
FutureTask<String> task2 = new FutureTask<>(callable);
FutureTask<String> task3 = new FutureTask<>(callable);
Thread t7 = new Thread(task1);
Thread t8 = new Thread(task2);
Thread t9 = new Thread(task3);
t7.setName("窗口7");
t8.setName("窗口8");
t9.setName("窗口9");
t7.start();
t8.start();
t9.start();
}
}
4.使用线程池
三、线程的状态
-
新建
当一个Thread类或其子类被声明并创建,新的线程对象处于新建状态 -
就绪
新建的线程调用start()方法之后,将进入线程队列等待CPU时间片,此时已具备运行条件,只是没有分配到CPU资源:sleep()时间结束 jion()线程结束 获取同步锁 notify()/notifyAll()
-
运行
当就绪的线程被调度并获取到CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能 -
阻塞
在某种特殊情况下,线程被认为挂起或执行输入输出操作时,让出CPU并临时终止执行,进入阻塞状态:sleep() join() 等待同步锁 wait()
-
死亡
线程完成了它的全部工作或线程被提前强制性的终止(调用stop()方法)或出现异常导致结束
五、线程调度策略
- 时间片:同优先级先进先出队列,使用时间片策略
- 抢占式:对比高优先级,高优先级的线程抢占CPU,使用抢占式策略
- 涉及方法:
(1)getPriority():获取线程优先级
(2)setPriority():设置线程优先级
六、线程安全问题
1.同步机制synchronized
(1)同步代码块
package thread.unsafe;
/**
* 概述:
* 作者:zhujie
* 创建时间:2021/11/05 12:13
*/
public class UnSafeBank {
public static void main(String[] args) {
Account account = new Account(1000,"结婚基金");
Drawing you = new Drawing(account,50,"老公");
Drawing girlFriend = new Drawing(account,100,"老婆");
you.start();
girlFriend.start();
}
}
class Account{
int money;//账户余额
String name;//账户名称
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
class Drawing extends Thread{
Account account;
int drawingMoney;//取多少钱
int nowMoney;//取钱的人
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run(){
synchronized (account) {
if (account.money - drawingMoney < 0) {
System.out.println(this.getName() + "取钱时,账户余额不足!");
return;
}
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额
account.money = account.money - drawingMoney;
//手里的前
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name + "余额为:" + account.money);
System.out.println(this.getName() + "手里的钱:" + nowMoney);
}
}
}
(2)同步方法
package thread.unsafe;
/**
* 概述:
* 作者:zhujie
* 创建时间:2021/11/05 11:50
*/
public class SellTicket implements Runnable{
private static int ticketNum = 10;
private Boolean flag = true;
@Override
public void run() {
while (flag){
buy();
}
}
public synchronized void buy(){
if (ticketNum > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticketNum);
ticketNum--;
} else {
this.stop();
}
}
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
SellTicket sellTicket = new SellTicket();
new Thread(sellTicket,"窗口1").start();
new Thread(sellTicket,"窗口2").start();
new Thread(sellTicket,"窗口3").start();
}
}
2.产生死锁的四个必要条件
(1)互斥条件:一个资源每次只能被一个进程使用
(2)一个进程因请求资源而阻塞时,保持的资源不放
(3)不剥夺条件:进程已获得的资源,未使用完之前,不可剥夺
(4)循环等待条件:若干进程之间形成头尾相接的循环等待资源关系
3.Lock锁
(1)ReenTrantLock:可重入锁
package thread.lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 概述:
* 作者:zhujie
* 创建时间:2021/11/05 13:27
*/
public class TestLock {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket, "窗口1").start();
new Thread(buyTicket, "窗口2").start();
new Thread(buyTicket, "窗口3").start();
}
}
class BuyTicket implements Runnable {
private static int ticket = 10000;
private Boolean flag = true;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
buyTicket();
}
public void buyTicket() {
while (flag) {
try {
lock.lock();
if (ticket < 1) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + ":售出票号" + ticket);
ticket--;
}finally {
lock.unlock();
}
}
}
}
4.使用顺序
Lock锁----->同步代码块----->同步方法
七、线程通讯
- wait():一旦执行此方法,线程进入阻塞状态,并释放锁(同步监视器)
- notify():一旦执行此方法,会唤醒执行了wait的一个线程,如果存在多个,则唤醒优先级高的
- notifyAll():一旦执行此方法,会唤醒所有执行了wait的线程
- wait()、notify()、notifyAll()三个方法必须使用在同步代码块或同步方法中
- wait()、notify()、notifyAll()三个方法调用者必须是同步代码块或同步方法中的同步监视器
- 生产者消费者模式–>管程法:利用缓冲区
package thread.communication;
/**
* 概述:生产者消费者模型-->管程法:缓冲区
* 生产者、消费者、产品、缓冲区
* 作者:zhujie
* 创建时间:2021/11/05 14:47
*/
public class TestProducerConsumer {
public static void main(String[] args) {
//创建缓冲区对象
Buffer buffer = new Buffer();
//创建生产者线程
Producer producer = new Producer(buffer);
producer.setName("生产者");
producer.start();
//创建消费者线程
Consumer consumer = new Consumer(buffer);
consumer.setName("消费者");
consumer.start();
}
}
//生产者
class Producer extends Thread{
Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("生产了"+i+"个产品");
buffer.add(new Product(i));
}
}
}
//消费者
class Consumer extends Thread{
Buffer buffer;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("消费了第"+buffer.consumption().id+"个产品");
}
}
}
//产品
class Product{
int id;
public Product(int id) {
this.id = id;
}
}
//缓冲区
class Buffer{
//设置产品缓冲区容器大小
Product[] products = new Product[10];
//缓冲区容器计数器
int count = 0;
//生产者放入产品方法
public synchronized void add(Product product){
//判断缓冲区容器是否已满,满了通知消费者消费
if (count == products.length){
//通知消费者消费,生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//容器未满,添加进容器
products[count] = product;
count ++;
//可以通知消费者消费
this.notifyAll();
}
//消费者消费产品方法
public synchronized Product consumption(){
//判断缓冲区容器是否存在产品
if (count == 0){
//等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费
count --;
Product product = products[count];
//通知生产者生产
this.notifyAll();
return product;
}
}
- 生产者消费者模式–>信号灯发:利用标识位
package thread.communication;
/**
* 概述:生产者消费者模型-->信号灯发:标志位
* 作者:zhujie
* 创建时间:2021/11/05 15:42
*/
public class TestProducerConsumer2 {
public static void main(String[] args) {
Programme programme = new Programme();
new actor(programme).start();
new audience(programme).start();
}
}
//生产者--演员
class actor extends Thread{
Programme programme;
public actor(Programme programme) {
this.programme = programme;
}
//录制节目
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2 == 0){
programme.play("快乐大本营");
}else {
programme.play("广告");
}
}
}
}
//消费者--观众
class audience extends Thread{
Programme programme;
public audience(Programme programme) {
this.programme = programme;
}
//观看节目
@Override
public void run() {
for (int i = 0; i < 20; i++) {
programme.watch();
}
}
}
//产品--节目
class Programme{
//节目
String programme;
//标识
//演员录制节目,观众等待T
//观众观看节目,演员等待F
boolean flag = true;
//演员录制节目
public synchronized void play(String name){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//录制节目
System.out.println("演员:录制了"+name);
//通知观众观看节目
this.notifyAll();
this.programme = name;
this.flag = !this.flag;
}
//观众观看节目
public synchronized void watch(){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众:观看了"+this.programme);
//通知演员录制节目
this.notifyAll();
this.flag = !this.flag;
}
}
八、sleep和wait的区别
- 相同点:
(1)都可使当前线程进入阻塞状态 - 不同点
(1)声明位置不同:sleep在Thread中声明,wait在Object类中声明
(2)调用要求不同:sleep可以在任何需要的地方调用,而wait只能在同步代码块或同步方法中调用
(3)都在同步代码块或同步方法中调用时,sleep不会释放锁(同步监视器),wait会释放锁(同步监视器)
(4)sleep在时间结束后自动唤醒,而wait需要调用notify方法唤醒
九、线程池
1.ExecutorService
- 真正的线程池接口,常见子类ThreadPoolExecutore
- void execute(Runnable command):执行任务,没有返回值,一般用来执行Runnable
- Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
- void shutdown():关闭连接池
2.Executors
- 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新的线程的线程池
- Executors.newFixedThreadPool():创建一个可重用固定线程数的线程池
- Executors.newSingleThreadPool():创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool():创建一个线程池,它可以安排在给定延迟后运行命令或定期执行