目录
进程
什么是进程。操作系统中运行的一个应用程序
例如qq,微信。操作系统都会运行一个进程
每个进程都是独立的,每个进程都运行在其专有且保护的内存空间里面
在windows可以通过任务管理器查看
线程
什么是线程.一个进程想要执行任务。就必须要有线程。
一个进程的所有任务都在线程中执行
一个线程的任务执行是串行的
例如下载一个3个文件。是一个一个下载
多线程
什么是多线程。一个进程可以开启多线程。所有线程可以同时执行不同的任务。叫多线程
例如同时下载多个文件
多线程的原理
在同一时间, cpu的一个核心只能处理一个线程
多线程并发执行,其实是cpu在多个线程中快速切换
如果cpu调度线程的时间足够快,就造成了多线程并发执行的假象
多核cpu才是真正的多个线程并发执行
多线程的优缺点
- 优点
- 能适当提高程序的执行效率
- 能适当提高资源的利用率
- 缺点
- 开启线程需要占用一定的内存空间,如果开启大量的线程。会占用大量的内存空间
- 线程越多,CPU在调度线程上的开销越大
- 程序的设计更复杂
开启线程方法
第一个方法
/// 创建一个线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
// to doing
}
});
// 开启
thread.start();
第二个方法
// 创建一个类。然后继承后重写run方法
public class MyThread extend Thread{
@Override
public void run{
}
Thread thread = new MyThread();
}
多线程的内存布局
- PC寄存器
每一个线程都有自己的PC寄存器.
即开启的每一个线程他们的执行进度不一样,都有属于自己的寄存器 - JAVA虚拟机栈
每一个线程都有自己的JAVA虚拟机栈空间
因为每一个线程都会执行,执行完成后就会销毁空间。如果他们共用一个则会出问题 - 堆
多线程共享堆 - 方法区
多个线程共享方法去 - 本地方法栈
每一个线程都有自己的本地方法栈
线程的状态
可以通过getState()获取线程的状态。一共有6个状态
- NEW 尚未启动
- RUNABLE 正在JVM运行
- BLOCKED 正在等待监视器锁
- WAITING 正在等待另外一个线程
- TIMED_WAITING 定时等待状态
- TERMINATED 已经执行完毕
线程的状态切换
线程可以通过slee[方法进入到WAITING状态。等待另外一个线程状态
在暂停期间,如果使用interrupt中断方法则会抛出异常
isAlive 查明线程是否或者
线程安全问题
多个线程访问同一块资源,比如访问同一个对象,同一个文件
当多线程访问同一块资源的时候,很容易引发数据错乱,称为线程安全问题
当多个线程共享同一块资源的时候,并且有一个线程正在写的操作则会引发安全问题
例如:
public static void main(String[] args) throws ParseException {
Station station = new Station();
for(int i = 0; i < 4; i++){
Thread thread = new Thread(station);
thread.setName(i + "");
thread.start();
}
}
package com.bona;
/**
*
*
* @author Zyuan
* @version 创建时间:2020年5月12日 下午11:03:17
*/
public class Station implements Runnable{
private int tickets = 100;
private boolean saleTicket(){
if(tickets < 1){
return false;
}
tickets --;
String threadName = Thread.currentThread().getName();
System.out.println("线程"+threadName+"卖了"+tickets+"张");
return tickets > 0;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (saleTicket()) {
}
}
}
线程安全解决方案
- 线程同步
使用同步语句使用 synchronized
在上面方法上使用synchronized
private boolean saleTicket(){
synchronized (this) {
if(tickets < 1){
return false;
}
tickets --;
String threadName = Thread.currentThread().getName();
System.out.println("线程"+threadName+"卖了"+tickets+"张");
return tickets > 0;
}
}
在同一时间只有一个人在卖票.
原理在于每一个对象都有一个与他对应的内部锁与监听器锁。
虽然我们有4条线程执行,但是都有一个先后顺序,第一条到达的线程会获取对象的内部锁。则加锁了。同一时间,只有一个线程拥有对象的内部锁。所以谁先来谁则先执行。等先来的执行完成后。剩下的再次抢到则开始执行。其他开始等待。当其他开始等待则进入BLOCKED状态
- 同步方法
在方法前面加 synchronized
构建方法不能使用synchronized
private synchronized boolean saleTicket(){
if(tickets < 1){
return false;
}
tickets --;
String threadName = Thread.currentThread().getName();
System.out.println("线程"+threadName+"卖了"+tickets+"张");
return tickets > 0;
}
虽然使用了线程同步后,虽然解决线程安全后,但是降低了性能效率。
几个常用的细节
ArrayList 非线程安全数组
Vector 有线程安全
StringBuilder 非线程安全
StringBuffer 线程安全
HashMap 非线程安全
HashTable 线程安全
死锁
两个线程相互永远堵塞,相互等待的叫死锁
线程之间的通信与示例
假如有两条线程,一条叫生产者,一条叫消费者。消费者需要等待生产者生产东西。生产者生产完成后需要主动通知消费着。
可以使用Object.wait,Object.notify,Object.notifyAll.
如果想要在线程A调用上面的方法。那么线程A必须只有Object的内部锁。
// // 在 使用wait过程中.Object.wait 必须要 notify与wait的Object一致。
// wait();
// // 随机通知一个等待他的线程
// notify();
// // 通知所有等待他的线程
// notifyAll();
例如:
生产者
package com.bona;
/**
*
* 生产者
*/
public class Producer implements Runnable{
private Drop drop;
public Producer(Drop drop){
this.drop = drop;
}
@Override
public void run() {
String foots[] = {"食物1", "食物2", "食物3", "食物4"};
for (int i = 0; i < foots.length; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.err.println("生产了食物"+foots[i]);
drop.add(foots[i]);
}
drop.add(null);
}
}
消费者
package com.bona;
/**
*
* 消费者
*/
public class Consumer implements Runnable{
private Drop drop;
public Consumer(Drop drop){
this.drop = drop;
}
@Override
public void run() {
String foot = null;
while ((foot = drop.get()) != null) {
System.out.println("收到食物"+foot);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
drop.add(null);
}
}
package com.bona;
/**
*
*
* @author Zyuan
* @version 创建时间:2020年5月16日 下午10:16:18
*/
public class Drop {
private String foot;
// 等待生产食物,如果生产了则为false,需要去通知,没有生产就等待
private boolean empty = true;
public synchronized String get() {
// 等待食物获取
while (empty) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
empty = true;
notifyAll();
return foot;
}
public synchronized void add(String foot) {
while (!empty) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
empty = false;
this.foot = foot;
// 通知所有等待的线程。告诉他们食物生产完成了
notifyAll();
}
// private Boolean empty = true;
}
最后执行:
public static void main(String[] args) throws ParseException {
Drop drop = new Drop();
(new Thread(new Producer(drop))).start();
(new Thread(new Consumer(drop))).start();
}
可重入锁
具有跟同步语句,同步方法一样的功能。但是功能更加强大.
ReentrantLock 方法
例如使用方法如上面卖票例子:
package com.bona;
import java.util.concurrent.locks.ReentrantLock;
/**
*
*
* @author Zyuan
* @version 创建时间:2020年5月12日 下午11:03:17
*/
public class Station implements Runnable{
private int tickets = 100;
private ReentrantLock lock = new ReentrantLock();
private boolean saleTicket(){
try {
lock.lock();
if(tickets < 1){
return false;
}
tickets --;
String threadName = Thread.currentThread().getName();
System.out.println("线程"+threadName+"卖了"+tickets+"张");
return tickets > 0;
} finally {
lock.unlock();
}
}
@Override
public void run() {
// TODO Auto-generated method stub
while (saleTicket()) {
}
}
}
线程池
线程对象占用大量的内存,在大型应用中,频繁的创建会产生大量的内存
使用线程池可以减少线程的创建,销毁带来的开销
线程池由工作线程组成:
- 线程执行完一个任务后生命周期就结束了(Thread)
- 工作线程可以执行多个任务。任务没来就等待。来了就执行。先吧任务添加到队列中,再从队列中取出任务提交到池中
// 线程池对象
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.execute(()->{
System.out.println(Thread.currentThread());
});
executorService.execute(()->{
System.out.println(Thread.currentThread());
});
executorService.execute(()->{
System.out.println(Thread.currentThread());
});
executorService.execute(()->{
System.out.println(Thread.currentThread());
});
executorService.execute(()->{
System.out.println(Thread.currentThread());
});
// 不用了需要关掉线程池
executorService.shutdown();