目录
什么是多线程
进程Process: 正在进行中的程序
线程Thread: 进程中一个负责程序执行的控制单元(执行路径),比进程更小的执行单位
——一个进程中可以多执行路径,称之为多线程
一个进程中至少要有一个线程
开启多个线程是为了同时运行多部分代码
应用程序的执行是CPU的快速切换
多进程: 运行多个任务(程序)
多线程: 同一应用程序中多条执行路径
线程和进程的区别:
- 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换开销大。
- 同一进程内的多个线程共享相同的代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程间的切换开销小。
通常,在以下情况中可能要使用到多线程: - 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
- 需要一些后台运行的程序时
JVM启动时至少有两个县丞可以分析得出来
- 执行main函数的线程(代码定义在main函数中)
- 负责垃圾回收的线程
补充知识:堆和栈
来源:https://www.cnblogs.com/cchHers/p/10010275.html
栈区: 存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了,其操作方式类似于数据结构中的栈。栈内存分配运算内置于CPU的指令集,效率很高,但是分配的内存量有限,比如iOS中栈区的大小是2M。
堆区: 就是通过new、malloc、realloc分配的内存块,编译器不会负责它们的释放工作,需要用程序区释放。分配方式类似于数据结构中的链表。在iOS开发中所说的“内存泄漏”说的就是堆区的内存。
堆和栈的区别:
堆空间的内存是动态分配的,一般存放对象,并且需要手动释放内存。当然,iOS引入了ARC(自动引用计数管理技术)之后,程序员就不需要用代码管理对象的内存了,之前MRC(手动管理内存)的时候,程序员需要手动release对象。另外,ARC只是一种中间层的技术,虽然在ARC模式下,程序员不需要像之前那么麻烦管理内存,但是需要遵循ARC技术的规范操作,比如使用属性限定符weak、strong、assigen等。因此,如果程序员没有按ARC的规则并合理的使用这些属性限定符的话,同样是会造成内存泄漏的。
栈空间的内存是由系统自动分配, 一般存放局部变量,比如对象的地址等值,不需要程序员对这块内存进行管理,比如,函数中的局部变量的作用范围(生命周期)就是在调完这个函数之后就结束了。这些在系统层面都已经限定住了,程序员只需要在这种约束下进行程序编程就好,根本就没有把这块内存的管理权给到程序员,肯定也就不存在让程序员管理一说。
从申请的大小方面讲:
栈空间比较小;
堆空间比较大。
从数据存储方面来说:
栈空间中一般存储基本数据类型,对象的地址;
堆空间一般存放对象本身,block的copy等。
多线程创建1:继承Thread
创建线程方法:
将类声明为Thread的子类,即继承Thread,调用run()方法(覆盖Thread的run方法),直接创建Thread的子类对象创建线程
步骤:
1 定义类继承Thread
2 覆盖Thread的run方法
3 直接床架Thread子类对象创建线程
4 调用start方法开启线程并调用线程任务run执行
class ThreadDemoclass extends Thread{
private String name;
public ThreadDemoclass(String name) {
this.name = name ;
}
public void run() {//覆盖run方法
for(int x=0;x<10;x++) {
for(int y=-9999999;y<999999999;y++) {}
System.out.println(name+".."+x+".."+getName());
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
//创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他的代码
//Thread thread = new Thread();
ThreadDemoclass threadDemoclass =new ThreadDemoclass("jia");
ThreadDemoclass threadDemoclass2 = new ThreadDemoclass("yi");
threadDemoclass.start();//开始线程,调用run方法
threadDemoclass2.start();
}
}
结果
jia..0..Thread-0
jia..1..Thread-0
yi..0..Thread-1
yi..1..Thread-1
yi..2..Thread-1
yi..3..Thread-1
yi..4..Thread-1
yi..5..Thread-1
yi..6..Thread-1
yi..7..Thread-1
yi..8..Thread-1
yi..9..Thread-1
jia..2..Thread-0
jia..3..Thread-0
jia..4..Thread-0
jia..5..Thread-0
jia..6..Thread-0
jia..7..Thread-0
jia..8..Thread-0
jia..9..Thread-0
线程中的异常
主线程(mian)中发生异常,子线程仍在运行
子线程中发生异常,子线程会异常停止
解决方法:来源于
https://www.cnblogs.com/yangfanexp/p/7594557.html
- 子线程中try… catch…
- 为线程设置“未捕获异常处理器”UncaughtExceptionHandler
- 通过Future的get方法捕获异常(推荐)
线程状态
垃圾回收
对象不再被引用时启动垃圾回收
class ThreadDemoclass1 extends Object{
public void finalize() {
System.out.println("..ok");
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
new ThreadDemoclass1();
new ThreadDemoclass1();
new ThreadDemoclass1();
System.gc();
System.out.println("Hello World!");
}
}
结果
Hello World!
..ok
..ok
..ok
多线程创建2:实现Runnable接口
步骤:
- 定义类实现(implements)Runnable接口
- 覆盖run方法
- 创建类对象,通过Thread构造函数将类对象名作为参数创建线程对象
- 调用线程对象的start方法开启线程
class ThreadDemoclass1 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
show();
}
public void show() {
for(int x=0;x<20;x++)
System.out.println("RunRun.."+x);
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
ThreadDemoclass1 t=new ThreadDemoclass1();
Thread thread = new Thread(t);
Thread thread2=new Thread(t);
thread.start();
thread2.start();
}
}
结果
RunRun..0
RunRun..0
RunRun..1
RunRun..2
RunRun..3
RunRun..4
RunRun..5
RunRun..6
RunRun..7
RunRun..8
RunRun..9
RunRun..10
RunRun..11
RunRun..1
RunRun..2
RunRun..3
RunRun..4
RunRun..12
RunRun..5
RunRun..6
RunRun..7
RunRun..8
RunRun..9
RunRun..10
RunRun..11
RunRun..12
RunRun..13
RunRun..14
RunRun..15
RunRun..16
RunRun..17
RunRun..18
RunRun..19
RunRun..13
RunRun..14
RunRun..15
RunRun..16
RunRun..17
RunRun..18
RunRun..19
多线程创建方法2:实现Runnable接口的好处:
- 将线程的任务从线程的子类中分离出来,进行了单独的封装
- 避免Java单继承的局限性
所以实现Runnable接口来创建线程的方法更常用
线程安全-同步代码块
线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况
产生原因:
- 多个线程在操作共享的数据
- 操作共享数据的线程代码有多条
解决
将多条操作共享数据的线程代码封装起来,有线程在执行这些线程代码的时候其他线程不能运行这些代码,执行完毕后才可以——同步代码块
同步代码块:synchronized(同步锁)
synchronized(对象)
{
需要被同步的代码
}
睡眠sleep()
程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
vs wait()
调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
简述:
wait():释放CPU执行权,释放锁
sleep():释放CPU执行权,不释放锁
class ThreadDemoclass2 implements Runnable{
private int num=20;
Object obj=new Object();
@Override
public void run() {
//Object obj=new Object();
// TODO Auto-generated method stub
while(true) {
synchronized (obj) {
if(num>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName()+"RunRun.."+num--);
}
}
}
}
}
public class ThreadExample {
public static void main(String[] args) {
ThreadDemoclass2 t=new ThreadDemoclass2();
Thread thread = new Thread(t);
Thread thread2=new Thread(t);
Thread thread3=new Thread(t);
Thread thread4=new Thread(t);
thread.start();
thread2.start();
thread3.start();
thread4.start();
}
}
结果:多个线程同时运行,等待其中一个让出资源空间
Thread-0RunRun..20
Thread-3RunRun..19
Thread-3RunRun..18
Thread-3RunRun..17
Thread-2RunRun..16
Thread-2RunRun..15
Thread-2RunRun..14
Thread-2RunRun..13
Thread-2RunRun..12
Thread-2RunRun..11
Thread-2RunRun..10
Thread-2RunRun..9
Thread-1RunRun..8
Thread-2RunRun..7
Thread-2RunRun..6
Thread-2RunRun..5
Thread-2RunRun..4
Thread-3RunRun..3
Thread-3RunRun..2
Thread-3RunRun..1
同步的好处: 解决线程安全问题
同步的弊端: 相对降低效率,因为同步外的线程都会判断同步锁
同步的前提: 必须有多个线程使用同一个锁,在一开始定义Object
【Object obj=new Object();】而不是放在同步块中
同步函数
用synchronized 修饰的函数
同步代码块的锁(对象object)vs同步函数的锁(对象object)
同步代码块的对象/锁:
是自定义的任意对象,通过锁来区别同步。是更加常见的
同步函数的对象/锁:
是固定的 this 。因此只需要一个同步时可以使用同步函数
静态同步函数的对象/锁:
随着类的加载而加载的,静态是被类名调用的。类加载后的.class 文件其实就是他的一个对象
获取方法:1. getclass() 2. 类名.class
单例模式的多线程
饿汉式:在程序启动或单件模式类被加载的时候,单件模式实例就已经被创建。
懒汉式:当程序第一次访问单件模式实例时才进行创建。
//多线程下的单例模式
//饿汉式
class SingleE{
private static final SingleE s=new SingleE();
private SingleE() {
}
public static SingleE getInstance() {
return s;
}
}
//懒汉式
class SingleL{
private static SingleL s=null;
private SingleL() {
}
public static SingleL getInstance() {
synchronized (SingleL.class) {
if(s==null)
s=new SingleL();
}
return s;
}
}
public class SingleThreadDemo {
public static void main(String[] args) {
}
}
死锁
所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
产生条件:四个
1、**互斥:**某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2、**占有且等待:**一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、**不可抢占:**别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、**循环等待:**存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
class Test implements Runnable {
private boolean flag;
Test(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag) {
while (true) {
synchronized (MyLock.locka) {
System.out.println(Thread.currentThread().getName() + ".t1 Get the lock of locka");
synchronized (MyLock.lockb) {
System.out.println(Thread.currentThread().getName() + ".t1 Get the lock of lockb");
}
}
}
} else {
while (true) {
synchronized (MyLock.lockb) {
System.out.println(Thread.currentThread().getName() + ".t2 Get the lock of lockb");
synchronized (MyLock.locka) {
System.out.println(Thread.currentThread().getName() + ".t2 Get the lock of locka");
}
}
}
}
}
}
class MyLock {
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
public class ThreadDeadDemo {
public static void main(String[] args) {
Test aTest = new Test(true);
Test bTest = new Test(false);
Thread t1 = new Thread(aTest);
Thread t2 = new Thread(bTest);
t1.start();
t2.start();
}
}
结果(不一定是以下的结果,但一定会输出一定行数)
Thread-0.t1 Get the lock of locka
Thread-1.t2 Get the lock of lockb
解决办法:将else后的locka和lockb互换
线程间通信
概念:多个线程在处理同一资源,但是执行不同
//一个简单的例子
//这是多个线程处理的同一个资源
class Resource {
String name;
String sex;
}
//对资源进行数据存储功能
class Input implements Runnable {
Resource resource;
Object o = new Object();
Input(Resource r) {
this.resource = r;
}
public void run() {
int x = 0;
// synchronized (o) {
while (true) {
synchronized (resource) {
if (x == 0) {
resource.name = "yangxiao";
resource.sex = "girl";
} else {
resource.name = "chenjunhui";
resource.sex = "boy";
}
}
x = (x + 1) % 2;
}
}
}
//对资源进行数据显示功能
class Output implements Runnable {
Resource resource;
Object o = new Object();
Output(Resource r) {
this.resource = r;
}
public void run() {
while (true) {
synchronized (resource) {
System.out.println(resource.name + ".." + resource.sex);
}
}
}
}
public class ResourceDemo {
public static void main(String[] args) {
Resource resource = new Resource();
Input input = new Input(resource);
Output output = new Output(resource);
Thread t1 = new Thread(input);
Thread t2 = new Thread(output);
t1.start();
t2.start();
}
}
结果(随机,乱序)
yangxiao..girl
yangxiao..girl
chenjunhui..boy
chenjunhui..boy
chenjunhui..boy
chenjunhui..boy
yangxiao..girl
yangxiao..girl
.........
.........
.........
等待/唤醒机制
wait():冻结线程,被wait的线程会存储到线程池中
notify():唤醒线程池中的一个线程
notifyAll():唤醒线程池中的所有线程
上述三种方法位监视器的方法,定义在Object类中,监视器其实就是锁,锁可以为任意对象
class Resource2 {
String name;
String sex;
boolean flag;
}
class Input2 implements Runnable {
Resource2 resource;
Object o = new Object();
Input2(Resource2 r) {
this.resource = r;
}
public void run() {
int x = 0;
// synchronized (o) {
while (true) {
synchronized (resource) {
if(resource.flag)
try {
resource.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (x == 0) {
resource.name = "yangxiao";
resource.sex = "girl";
} else {
resource.name = "chenjunhui";
resource.sex = "boy";
}
resource.flag=true;
resource.notify();
}
x = (x + 1) % 2;
}
}
}
class Output2 implements Runnable {
Resource2 resource;
Object o = new Object();
Output2(Resource2 r) {
this.resource = r;
}
public void run() {
while (true) {
synchronized (resource) {
if(!resource.flag)
try {
resource.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(resource.name + ".." + resource.sex);
resource.flag=false;
resource.notify();
}
}
}
}
public class ResourceDemo2 {
public static void main(String[] args) {
Resource2 resource = new Resource2();
Input2 input = new Input2(resource);
Output2 output = new Output2(resource);
Thread t1 = new Thread(input);
Thread t2 = new Thread(output);
t1.start();
t2.start();
}
}
结果
yangxiao..girl
chenjunhui..boy
yangxiao..girl
chenjunhui..boy
yangxiao..girl
chenjunhui..boy
yangxiao..girl
chenjunhui..boy
......
......
......
代码优化:由于直接访问资源带来的不安全性,需要对资源内部的属性进行私有化,进行内部维护
package com.yyxx.day13;
class Resource2 {
private String name;
private String sex;
private boolean flag;
public synchronized void set(String name,String sex) {
this.name=name;
this.sex=sex;
}
public synchronized boolean getflag() {
return flag;
}
public synchronized void setflag(boolean flag) {
this.flag=flag;
}
public synchronized void out() {
System.out.println(name + ".." + sex);
}
}
class Input2 implements Runnable {
Resource2 resource;
Object o = new Object();
Input2(Resource2 r) {
this.resource = r;
}
public void run() {
int x = 0;
// synchronized (o) {
while (true) {
synchronized (resource) {
if(resource.getflag())
try {
resource.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (x == 0) {
resource.set("yangxiao", "girl");
// resource.name = "yangxiao";
// resource.sex = "girl";
} else {
resource.set("chenjunhui","boy");
// resource.name = "chenjunhui";
// resource.sex = "boy";
}
resource.setflag(true);
resource.notify();
}
x = (x + 1) % 2;
}
}
}
class Output2 implements Runnable {
Resource2 resource;
Object o = new Object();
Output2(Resource2 r) {
this.resource = r;
}
public void run() {
while (true) {
synchronized (resource) {
if(!resource.getflag())
try {
resource.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// System.out.println(resource.name + ".." + resource.sex);
resource.out();
resource.setflag(false);
resource.notify();
}
}
}
}
public class ResourceDemo2 {
public static void main(String[] args) {
Resource2 resource = new Resource2();
Input2 input = new Input2(resource);
Output2 output = new Output2(resource);
Thread t1 = new Thread(input);
Thread t2 = new Thread(output);
t1.start();
t2.start();
}
}
多生产者多消费者问题
package com.yyxx.day13;
//多生产者多消费者
class Resource3 {
private String name;
private boolean flag = false;
private int count = 1;
public synchronized void set(String name) {
/*这里用while不用if
* 是为了避免多个生产者生产同一物品
* 或者多个消费者购买同一物品的情况出现
*/
while (flag)
try {
this.wait();
} catch (InterruptedException e) {
// TODO: handle exception
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName() + ".生产者." + this.name);
flag = true;
/*
* 这里用notifyAll不用notify
* notifyAll可以用本方线程唤醒对方线程
* notify:只能唤醒一个线程,本方唤醒本方没有意义
* while+notify会导致死锁
*/
this.notifyAll();
}
public synchronized void out() {
while (!flag)
try {
this.wait();
} catch (InterruptedException e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + ".消费者....." + name);
flag = false;
this.notifyAll();
}
}
class Producer implements Runnable {
private Resource3 resource;
public Producer(Resource3 resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
synchronized (resource) {
resource.set("烤鸭");
}
}
}
}
class Consumer implements Runnable {
private Resource3 resource;
public Consumer(Resource3 resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
synchronized (resource) {
resource.out();
}
}
}
}
public class ResourceExample {
public static void main(String[] args) {
Resource3 resource = new Resource3();
Producer p = new Producer(resource);
Consumer c = new Consumer(resource);
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(c);
Thread t4 = new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
结果
......
......
Thread-0.生产者.烤鸭104875
Thread-3.消费者.....烤鸭104875
Thread-1.生产者.烤鸭104876
Thread-2.消费者.....烤鸭104876
Thread-0.生产者.烤鸭104877
Thread-3.消费者.....烤鸭104877
Thread-1.生产者.烤鸭104878
Thread-2.消费者.....烤鸭104878
Thread-0.生产者.烤鸭104879
Thread-3.消费者.....烤鸭104879
Thread-1.生产者.烤鸭104880
Thread-2.消费者.....烤鸭104880
Thread-0.生产者.烤鸭104881
Thread-3.消费者.....烤鸭104881
......
......
停止线程
方法:
- 定义线程循环结束标记
- join、yield等方法停止线程
- 使用interrupt(中断)方法
——结束线程的冻结状态,使线程回到运行状态中来
//定义循环停止标志停止线程
class StopThread implements Runnable{
private boolean flag=true;
public void run() {
while (flag) {
System.out.println(Thread.currentThread().getName()+"..");
}
}
public void setFlag_fault() {
flag=false;
}
}
public class StopThreadDemo {
public static void main(String[] args) {
StopThread s=new StopThread();
Thread t1 =new Thread(s);
Thread t2 =new Thread(s);
t1.start();
t2.start();
int num=1;
for (;;) {
if(++num==50) {
s.setFlag_fault();
break;
}
System.out.println("main.."+num);
}
System.out.println("main..over");
}
}
//Interrupt停止线程
//如果线程处于冻结状态,无法读取标志flag,可以用Interrupt将线程强制从冻结状态转换为运行状态
//强制转换会产生InterruptedException,需要处理
class InterruptThread implements Runnable{
private boolean flag=true;
public synchronized void run() {
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+".."+e.toString());
flag=false;
}
System.out.println(Thread.currentThread().getName()+"...............");
}
}
public void setFlag_fault() {
flag=false;
}
}
public class InterruptThreadDemo {
public static void main(String[] args) {
InterruptThread s=new InterruptThread();
Thread t1 =new Thread(s);
Thread t2 =new Thread(s);
t1.start();
t2.start();
int num=1;
for (;;) {
if(++num==50) {
s.setFlag_fault();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println("main.."+num);
}
System.out.println("main..over");
}
}
结果
main..2
main..3
main..4
main..5
main..6
main..7
main..8
main..9
main..10
main..11
main..12
main..13
main..14
main..15
main..16
main..17
main..18
main..19
main..20
main..21
main..22
main..23
main..24
main..25
main..26
main..27
main..28
main..29
main..30
main..31
main..32
main..33
main..34
main..35
main..36
main..37
main..38
main..39
main..40
main..41
main..42
main..43
main..44
main..45
main..46
main..47
main..48
main..49
main..over
Thread-0..java.lang.InterruptedException
Thread-0...............
Thread-1..java.lang.InterruptedException
Thread-1...............
守护线程
setDaemon():将该线程标记为守护线程或用户线程。
当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则不会退出