赞
踩
多线程:
- 多线程就是同时执行多个应用程序,需要硬件的支持
- 同时执行:不是某个时间段同时,cpu切换的比较快,所有用户会感觉是在同时运行
并发与并行:
并行(parallel):
指在同一时刻,有多条指令在多个处理器上同时执行。并行必须借助于多核cpu实现
并发(concurrency):
指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,通过cpu时间片轮转使多个进程快速交替的执行。
程序、线程、进程:
程序:是指编译好的可执行文件,程序启动的时候,进程作为支撑
进程:是正在运行的程序(比如360杀毒软件),进程可以产生多线程
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
并发性:任何进程都可以同其他进程一起并发执行
进程基本的状态有5种:分别为初始态、就绪态、运行态、挂起态、终止态
其中初始态为进程准备阶段,常与就绪态结合来看。
线程:是程序正在做的事情,线程是进程的单个控制流(比如360的杀毒,扫描木马)
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
进程与线程的区别:
- 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
- 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。
多线程同时执行原理:
比如我们同时运行qq和微信,其实不是同时运行的而是CPU在多个线程间快速切换,造成"同时"执行的假象
多线程的好处:
- 可以"同时"执行多个任务
- 可以提高资源的利用率(CPU/网络)
线程越多越好吗:
1.创建和销毁线程需要消耗CPU和内存资源
2.线程太多,CPU需要在大量的线程间切换,造成资源的浪费
进程与并发:
在使用进程实现并发时会出现以下问题:
孤儿进程
和僵尸进程
。正常情况下,子进程是通过父进程fork创建的,子进程再创建新的进程。并且父进程永远无法预测子进程 到底什么时候结束。 当一个 进程完成它的工作终止之后,它的父进程需要调用系统调用取得子进程的终止状态。孤儿进程:
父进程比子进程先结束,子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
僵尸进程:
僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
Windows&Linux进程:
Windows下的进程和Linux下的进程是不一样的,它比较懒惰,从来不执行任何东西,只是为线程提供执行环境。然后由线程负责执行包含在进程的地址空间中的代码。当创建一个进程的时候,操作系统会自动创建这个进程的第一个线程,成为主线程。
创建线程:
实现多线程有三种方法:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
开启线程
方法名 | 说明 |
---|---|
void run() | 在线程开启后,此方法将被调用执行 |
void start() | 使此线程开始执行,Java虚拟机会调用run方法() |
为什么要重写run()方法?
因为run()是用来封装被线程执行的代码
run()方法和start()方法的区别?
- run():封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程
- start():启动线程;然后由JVM调用此线程的run()方法
继承Thread:
实现步骤:
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程
多线程类:
public class ThreadDemo extends Thread {
// run是用来封装被线程执行的代码的,一定要重写
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程启动" + i);
}
}
}
测试类:
public class TestThread {
public static void main(String[] args) {
// 创建线程对象
ThreadDemo thread1 = new ThreadDemo();
ThreadDemo thread2 = new ThreadDemo();
// 开启线程
thread1.start();
thread2.start();
}
}
实现Runnable:
实现步骤:
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
对象类:
public class RunnableDemo implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程启动" + i);
}
}
}
测试类:
public class TestRunnable {
public static void main(String[] args) {
// 创建参数对象
RunnableDemo r1 = new RunnableDemo();
RunnableDemo r2 = new RunnableDemo();
// 创建线程
Thread thread1 = new Thread(r1);
Thread thread2 = new Thread(r2);
// 开启线程
thread1.start();
thread2.start();
}
}
实现Callable接口:
实现步骤:
- 定义一个类MyCallable实现Callable接口
- 在MyCallable类中重写call()方法
- 创建MyCallable类的对象
- 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
- 创建Thread类的对象,把FutureTask对象作为构造方法的参数
- 启动线程
- 再调用get方法,就可以获取线程结束之后的结果。
构造方法:
方法名 | 说明 |
---|---|
Thread(Runnable target) | 分配一个新的Thread对象 |
Thread(Runnable target, String name) | 分配一个新的Thread对象 |
对象类:
public class CallDemo implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("线程执行" + i);
}
// 返回值表示运行完以后的结果
return "执行完毕";
}
}
测试类:
public class TestCall {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建对象,线程要执行的代码
CallDemo call = new CallDemo();
// 获取线程执行完毕的结果,可以作为参数传给Thread
FutureTask<String> sft = new FutureTask<>(call);
Thread thread = new Thread(sft);
thread.start();
// get:获取线程运行后的结果,如果在线程没开启前就获取get方法会一直等待,所以get方法要写在start后面
System.out.println(sft.get());
}
}
三种实现方式对比:
实现Runnable、Callable接口
好处: 扩展性强,实现该接口的同时还可以继承其他的类
缺点: 编程相对复杂,不能直接使用Thread类中的方法
继承Thread类
好处: 编程比较简单,可以直接使用Thread类中的方法
缺点: 可以扩展性较差,不能再继承其他的类
获取与设置线程名称:
方法名 | 说明 |
---|---|
void setName(String name) | 将此线程的名称更改为等于参数name |
String getName() | 返回此线程的名称 |
Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
对象类:
public class ThreadDemo extends Thread {
// 要写构造,否则不能传线程名称
public ThreadDemo() {
}
public ThreadDemo(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + i);
// 获取当前线程对象
}
// 线程没有设置名字的话,返回的是默认的名字,每个线程都是有默认名字的
System.out.println("当前执行的线程是:" + Thread.currentThread().getName());
}
}
测试类:
public class Test {
public static void main(String[] args) {
ThreadDemo t1 = new ThreadDemo();
t1.start();
t1.setName("线程一");
// 通过构造设置,对象要创建有参构造
ThreadDemo t2 = new ThreadDemo("线程二:");
t2.start();
// 如果是通过接口创建线程那么是不能用getname的,所以currentThread就可以代替了
System.out.println(Thread.currentThread().getName());
}
}
线程睡眠:
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
对象类:
public class DemoRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + i);
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
DemoRunnable dr = new DemoRunnable();
Thread thread = new Thread(dr);
thread.start();
}
}
线程优先级:
- 分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU的时间
- 抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
- Java使用的是抢占式调度模型,线程优先级高只是枪战CPU的几率更大,不代表一定优先执行
方法名 | 说明 |
---|---|
final int getPriority() | 返回此线程的优先级 |
final void setPriority(int newPriority) | 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10 |
public class Test {
public static void main(String[] args) {
DemoRunnable dr = new DemoRunnable();
Thread thread = new Thread(dr);
thread.start();
// final int getPriority() 返回此线程的优先级
System.out.println(thread.getPriority());
// final void setPriority(int newPriority) 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
thread.setPriority(10);
System.out.println(thread.getPriority());
}
}
守护线程:
方法名 | 说明 |
---|---|
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 |
线程一:
public class Thread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + " :" + i);
}
}
}
线程二:
public class Thread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + " :" + i);
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
// 创建线程
Thread1 t1 = new Thread1();
Thread2 t2 = new Thread2();
// 设置线程名字
t1.setName("我女朋友");
t2.setName("我");
// 设置我女朋友为守护线程,我要挂了我女朋友也不能继续活,这就是家庭地位
// 因为此时守护线程还有执行权,cpu执行很快,所以守护线程不是马上停止的,要把执行权走完
t1.setDaemon(true);
t1.start();
t2.start();
}
}
线程安全:
卖票案例分析线程安全:
重复票:
定义三个线程去卖100张票,三个线程sleep后,第一个线程醒来拿到CPU执行权,此时将票减1,为99,刚减完线程二醒了。线程二拿到CPU执行权,此时将票减1,这时候是从99减1,为98,那这时候线程一也要变成98,因为执行的是同一个线程对象。刚减完线程三醒了。线程二拿到CPU执行权,此时将票减1,这时候是从98减1,为97…这时候就会出现重复票数
负数票:
此时票数为1,线程1、2、3开始sleep,线程1醒来后,拿到CPU执行权,做减1操作,此时票数为0,线程1被销毁。线程2醒过来,然后拿到CPU执行权,做减1操作,此时票数为-1,线程2被销毁。线程3醒过来拿到CPU执行权,做减1操作,此时票数为-2,线程3被销毁。所以就会出现负数票问题
安全问题出现的条件
- 是多线程环境
- 有共享数据
- 有多条语句操作共享数据
如何解决多线程安全问题呢?
把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行,Java提供了同步代码块的方式来解决,
例:两个人同时上厕所,但是就一个马桶,于是用户A进入后,用户B只能在外面等,只有用户A出来,用户B才能进去。
同步代码块:
synchronized(任意对象){ 多条语句操作共享数据的代码 }
// synchronized(任意对象):默认情况是打开的,只要有一个线程进去执行代码,锁就会关闭,当线程执行完出来,锁才会自动打开
同步代码块的好处和弊端:
好处:解决了多线程的数据安全问题
弊端:当线程很多时,每个线程都会去判断同步上的锁,很耗费资源,会降低程序的运行效率
对象类:
public class SellTicket implements Runnable {
// 定义总票数
private static int tickets = 100;
// 创建一个任意的对象
private final Object object = new Object();
@Override
public void run() {
while (true) {
synchronized (object) {
if (tickets > 0) {
try {
// 进来先睡一会
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
} else {
break;
}
}
}
}
}
测试类:
public class SellTicketDemo {
public static void main(String[] args) {
// 创建对象
SellTicket st = new SellTicket();
// 创建线程并设置名字,多线程同一个对象
Thread t1 = new Thread(st, "窗口一");
Thread t2 = new Thread(st, "窗口二");
// 启动线程
t1.start();
t2.start();
}
}
同步方法:
同步方法:锁对象是:this
修饰符 synchronized 返回值类型 方法名(方法参数) {方法体;}
对象类:
public class SellTicket2 implements Runnable {
// 定义总票数
private static int tickets = 100;
// 创建一个任意的对象
private final Object object = new Object();
@Override
public void run() {
while (true) {
// 同步方法
// 判断当前线程,是就调用方法,然后判断票数是不是0,不是就继续循环
if ("窗口一".equals(Thread.currentThread().getName())) {
boolean result = synchronizedMethod();
if (result) {
break;
}
}
// 同步代码块
if ("窗口二".equals(Thread.currentThread().getName())) {
// 因为同步方法的锁是this,所以代码块也要是this才能都用一把锁
synchronized (this) {
if (tickets > 0) {
try {
// 进来先睡一会
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
} else {
break;
}
}
}
}
}
private synchronized boolean synchronizedMethod() {
if (tickets > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
// 不是最后一张就继续循环
return false;
} else {
// 最后一张就返回true,停止
return true;
}
}
}
测试类:
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket2 st = new SellTicket2();
Thread t1 = new Thread(st, "窗口一");
Thread t2 = new Thread(st, "窗口二");
t1.start();
t2.start();
}
}
静态同步方法:锁对象是:类名.class
修饰符 static synchronized 返回值类型 方法名(方法参数) {方法体;}
静态方法在方法前加static,this换成类名.class就行了
Lock锁:
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock是接口不能直接实例化,可以用它的实现类ReentrantLock来实例化
ReentrantLock构造方法
方法名 | 说明 |
---|---|
ReentrantLock() | 创建一个ReentrantLock的实例 |
加锁解锁方法
方法名 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
对象类:
public class Ticket implements Runnable {
// 票的数量
private int ticket = 100;
// 创建Lock锁
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 获得锁
lock.lock();
if (ticket == 0) {
// 卖完了
break;
} else {
Thread.sleep(30);
ticket--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
测试类:
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket, "线程一");
Thread t2 = new Thread(ticket, "线程二");
// 设置优先级
t1.setPriority(10);
t2.setPriority(5);
t1.start();
t2.start();
}
死锁:
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
死锁产生的条件:
1.有多个线程
2.有多把锁
3.有同步代码块嵌套
解决办法:
干掉其中某个条件
public class Demo05 {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
new Thread(mr).start();
new Thread(mr).start();
}
}
class MyRunnable implements Runnable {
Object objA = new Object();
Object objB = new Object();
/*
嵌套1 objA
嵌套1 objB
嵌套2 objB
嵌套1 objA
*/
@Override
public void run() {
synchronized (objA) {
System.out.println("嵌套1 objA");
synchronized (objB) {// t2, objA, 拿不到B锁,等待
System.out.println("嵌套1 objB");
}
}
synchronized (objB) {
System.out.println("嵌套2 objB");
synchronized (objA) {// t1 , objB, 拿不到A锁,等待
System.out.println("嵌套2 objA");
}
}
}
}
生产者&消费者:
生产和消费主要是包含了两类线程:
生产和消费也称为
等待唤醒机制
生产者线程用于生产数据
消费者线程用于消费数据
实现:
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
Object类的等待和唤醒方法
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
对象类:
public class Desk {
// 定义一个标记判断食物的状态,为false表示没有食物,true代表有食物
private boolean flag;
// 食物的个数
private int count;
// 锁对象
private final Object lock = new Object();
public Desk() {
}
public Desk(boolean flag, int count) {
this.flag = flag;
this.count = count;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Object getLock() {
return lock;
}
@Override
public String toString() {
return "Desk{" +
"flag=" + flag +
", count=" + count +
", lock=" + lock +
'}';
}
}
生产者:
public class Cooker extends Thread {
private Desk desk;
public Cooker(Desk desk) {
this.desk = desk;
}
@Override
public void run() {
while (true) {
synchronized (desk.getLock()) {
if (desk.getCount() == 0) {
break;
} else if (!desk.isFlag()) {
System.out.println("生产者正在生产食物");
// 有食物就改状态让消费者去消费
desk.setFlag(true);
desk.getLock().notifyAll();
} else {
// 没有食物就线程等待唤醒
try {
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
消费者:
public class Foodie extends Thread {
private Desk desk;
public Foodie(Desk desk) {
this.desk = desk;
}
@Override
public void run() {
while (true) {
synchronized (desk.getLock()) {
if (desk.getCount() == 0) {
break;
// 布尔类型变量的getset是is开头的
} else if (desk.isFlag()) {
System.out.println("消费者消费了一个食物");
// 消费完毕,将标记设为false,当为false时可以让生产者去生产
desk.setFlag(false);
// 唤醒等待中的所有线程
desk.getLock().notifyAll();
// 消费一个减少一个
desk.setCount(desk.getCount() - 1);
} else {
try {
// 线程等待唤醒
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
测试类:
public class Demo {
public static void main(String[] args) {
Desk desk = new Desk(false, 10);
Foodie foodie = new Foodie(desk);
Cooker cooker = new Cooker(desk);
foodie.start();
cooker.start();
}
}
阻塞队列:
常见BlockingQueue:
ArrayBlockingQueue:底层是数组,有界
LinkedBlockingQueue:底层是链表,无界.但不是真正的无界,最大为int的最大值
BlockingQueue的核心方法:
方法名 | 说明 |
---|---|
put(anObject): | 将参数放入队列,如果放不进去会阻塞 |
take() | 取出第一个数据,取不到会阻塞 |
阻塞队列等待&唤醒:
阻塞队列底层是有自动加锁的,但是运行起来,可能打印出的是存两个打印两个,这个是控制台打印的问题。
生产者者线程
public class Cooker extends Thread {
private ArrayBlockingQueue<String> bd;
public Cooker(ArrayBlockingQueue<String> bd) {
this.bd = bd;
}
@Override
public void run() {
while (true) {
try {
bd.put("汉堡包");
System.out.println("厨师放入一个汉堡包");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者线程
private ArrayBlockingQueue<String> bd;
public Foodie(ArrayBlockingQueue<String> bd) {
this.bd = bd;
}
@Override
public void run() {
while (true) {
try {
String take = bd.take();
System.out.println("吃货将" + take + "拿出来吃了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试类:
public class Demo {
public static void main(String[] args) {
// 创建阻塞队列对象,容量为1
ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);
Foodie f = new Foodie(bd);
Cooker c = new Cooker(bd);
f.start();
c.start();
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。