要想了解多线程,必须先了解线程,要想了解线程,必须先了解进程,因为线程是依赖于进程而存在的。
什么是进程?
1、进程就是正在运行的程序
2、进程是系统进行资源分配和调用的独立单位,每一个进程都有自己独立的内存空间和系统资源
多进程的意义:
可以在一段时间内执行多个程序,提高cpu的使用率。例如可以一边玩游戏,一边听音乐。
注意:多个任务不是同时执行的,因为cpu在在一个时间点上只能做一件事,只不过是cpu在做着程序间的高效切换,让我们感觉像是同时执行的。
什么是线程?
在一个进程内可以执行多个任务,而每一个任务可以看成是一个线程
线程:线程是程序的执行单元或叫执行路径。是程序使用cpu的基本单位
单线程:程序只有一条执行路径
多线程:程序有多条执行路径
多线程的意义:
多线程的存在不是提高程序的执行速度,而是提高了程序的使用率。
程序的执行其实都是在抢cpu的资源,cpu的执行权。
如果某个程序执行路径比较多,就有更高的几率抢到cpu资源。
我们不敢保证哪个线程在哪个时刻抢到,所有线程的执行具有随机性。
并行和并发:
并行是逻辑上同时发生,指在一个时间段内同时运行多个程序
并发是物理上同时发生,指在一个时间点上同时运行多个程序
java程序的运行原理:
由java命令启动jvm,jvm启动,就相当于启动了一个进程,接着由该进程创建一个主线程去调用main方法
jvm虚拟机的启动时多线程的,原因是垃圾回收线程也要启动,否则会造成内存溢出。加上主线程,所有至少有两个线程启动。
java为我们提供了Tread类来实现多线程:
Thread类
常用方法:
String getName() :获取线程名
void setName():设置线程名
void setPriority(10); //设置线程优先级,取值为范围是 0-10,默认是5
static Thread currentTread() :返回当前正在执行的线程对象
static void sleep(long millis):休眠,单位为毫秒
final void join():等待该线程终止,在线程启动后调用,在此之后的线程等此线程结束后才执行
final void setDaemon(boolean on) :将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。
static final void stop():停止线程,已过时,但是还可以使用
void interrupt() :中断线程,把线程的状态终止,并抛出一个InterruptedException异常
创建方式一:
继承Thread类,步骤如下:
1、创建子类继承Tread类
2、重写run方法
3、创建子类实例对象
4、子类对象调用start()方法启动线程
注意:run方法和start方法的区别?
run() 仅仅是封装了线程执行的代码,和普通的方法一样
start() 首先是启动了线程,然后由jvm去调用该线程的run方法
创建代码如下:
//创建Mythread类继承Thread public class Mythread extends Thread{ //重写run方法 @Override public void run() { for(int i=0;i<100;i++){ System.out.println(getName()+"--"+i); } } }
public static void main(String[] args) { //创建子类对象 Mythread mythread=new Mythread(); //开启线程 mythread.start(); }
创建方式二:
实现Runnable接口,步骤如下:
1、自定义类实现Runnable接口
2、子类重写run方法
3、创建子类对象
4、调用Thread构造方法,将子类对象当作参数传入
和方式一比较,方式二的好处:
a、可以避免由于java单继承性带来的局限性 b、适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码有效分类,体现了较好的面向对象设计思想。
创建代码如下:
//创建 RunThread 类实现Runnable接口 public class RunThread implements Runnable { //重写run方法 @Override public void run() { } }
public static void main(String[] args) { //创建runThread接口实现类对象 RunThread runThread=new RunThread(); //调用Thread构造方法,传入runThread对象作为构造参数,创建线程 Thread thread=new Thread(runThread); thread.start(); }
多线程安全问题产生的原因及解决办法:
1、是否是由多线程
2、是否有共享数据
3、是否有多条语句操作共享数据
分析以上3点,如何3个条件都满足,就会产生安全问题。
解决办法:synchronized
为解决多线程安全问题,java提供了同步机制,将需要同步的代码写在同步代码块中
用法如下所示:
//同步能解决线程安全问题的根本原因就在于监视对象obj,该对象就如同一把锁,多个线程必须是同一把锁。此时的锁是任意对象 private void sysFunc1(){ Object obj=new Object(); synchronized (obj){ //需要同步的代码 } } //如果synchronized写在实例方法上,此时的锁是this private synchronized void sysFunc2(){ //需要同步的代码 } //如果synchronized写在静态方法上,此时锁是类的字节码文件对象。XXX.class private static synchronized void sysFunc3(){ //需要同步的代码 }
同步的好处:可以解决线程安全问题
同步的弊端:当线程较多,每个线程都会去判断同步锁对象,这是很耗费资源的,无形中会降低程序的运行效率
线程间的通信:
生产者与消费者问题:
public static void main(String[] args) { Student s=new Student(); Thread set=new Thread(new SetThread(s)); Thread get=new Thread(new GetThread(s)); set.start(); get.start(); }
生产者线程类代码:
/** * 生产者 */ public class SetThread implements Runnable { private Student s; private int x; public SetThread(Student s){ this.s=s; } @Override public void run() { while (true){ //SetThread是生产者,生产资源的代码如果不加锁,会出现名字和年龄对不上的情况 synchronized (s){ if(x%2==0){ s.setName("zhangsan"); s.setAge(30); }else { s.setName("lisi"); s.setAge(40); } x++; } } } }
消费者线程类代码:
/** * 消费者 */ public class GetThread implements Runnable { Student s; public GetThread(Student s){ this.s=s; } @Override public void run() { while (true){ //GetThread 为消费者,消费SetThread类生产的资源,必须加上锁否则会出现名字和年龄不一致。 synchronized (s){ System.out.println(s.getName()+"--"+s.getAge()); } } } }
生产资源Student类:
public class Student { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }