一、提到多线程,就不得不理解以下几点:
1.程序,进程,线程这三者之间的关系?
简单来说,一程序可以调用多个进程,比如一个视频播放器程序,里面就存在两个进程:一个是播放视频的进程,一个是下载上传视频的进程。
一个进程又同时调用多个线程,这个线程是隐藏的,用进程管理器看不到,可用其它的进程管理软件来查看。
三者的逻辑关系是程序调用进程,进程调用线程,一般来说程序下面都是多进程,不同的进程分工不同;进程下面也基本上是多线程的。
可以这样下定义:进程是系统进行资源分配和调用的独立单位,每一个进程,都由它自己的内存空间和系统资源
线程是进程的执行单元,执行路径,线程也是程序使用CPU的最基本单位
2.单线程与多线程以及多线程的意义?
如果程序只有一条执行路径,这就是单线程;相反如果程序有多条路径,那么就是多线程
看下面这段程序:
public class Thread {
public static void main(String[]args) {
//code..........
............
...........
}
}
这里面至少存在两条程序,一条是主线程main,另外一条就是垃圾回收线程,即JVM一遍"执行"main,"一边"也在执行垃圾回收机制
所以说java支持多线程,而多线程的意义它可以让程序在一个时间段执行多个事情,提高了应用程序的使用率;
比如上面那个例子,如果是单线程,那么JVM不得不执行一会main线程然后停下来去执行垃圾回收机制
3.理解并发与并行
思考:我们在一边玩游戏,一边带上耳机听歌,请问玩游戏与听歌是同时进行的吗?
显然不是,因为CPU在某一个时间点上只能做一件事,我们玩游戏,CPU就切换到执行游戏进程,听歌,CPU就切换到听歌进程;CPU就这样反复做这高效率的切换动作,这种切换是随机的,但是让我们人感觉到就是同时发生的,这就是并发的概念
并发:通过CPU调度算法,让用户看上去同时执行,实际上,是通过CPU在高速切换,并不是真正的额同时
并行:多个CPU实例或者多台机器同时执行一段处理逻辑,这就是真正的同时;
二,如何创建线程?
了解了多线程的意义所在,我们在处理一些问题上,就可以创建多条线程,充分使用CPU
Thread,这是JAVA为我们提供的唯一的线程类
通过查询API文档可知Thread类为我们提供了很多处理线程是需要的方法
这里先提两个:run()方法,与start()方法,run()方法里面存放着Thread线程对象需要执行的代码;调用start()方法,让所创建的线程处于就绪状态
创建线程的方式一:
1.类去继承(extends)Thread 类
2.该类重写Thread类的run方法,并且将线程要执行的代码,存放到run方法中
3.线程对象调用start方法,开启线程,线程会自动执行run方法
代码实例如下:
class Demo extends Thread {
String name;
Demo(String name) {
this.name = name;
}
//这一步很关键,重写run方法,这里面是线程所要执行的
public void run() {
for(int i=1;i<30;i++) { //利用currentThread()方法来获取当前正在执行的线程的名称
System.out.println(Thread.currentThread().getName()+"::"+this.name);
}
}
}
publicclass ThreadDemo {
public static void main(String[]args) {
Demo d1 = new Demo("1111");//创建线程对象
Demo d2 = new Demo("2222");
d1.start();//开启线程,执行run方法;但是注意,此时只是具备执行资格,CPU的执行是随机的
d2.start();
}
}
创建线程的方式二:
1.存放线程执行代码的类去实现(implements) Runnable接口
2.重写所实现接口的run方法,并将线程执行代码存放在run方法中
3.创建Thread对象,也就是创建线程
4.Thread线程对象调用start方法,启动线程
class Demo implements Runnable {
String name;
public void run() {
for(int i=0;i<30;i++) {
System.out.println(Thread.currentThread().getName()+"::"+);
}
}
}
public class TheadTest {
public void static main(String[]args) {
Demo d1= new Demo("1111");//创建对象
Demo d2 = new Demo("2222");
Thread t1 = new Thread(d1);//创建线程
Thread t2 = new Thread(d2);
t1.start();//开启线程
t2.start();
}
}
总结:这两种方式建议是同第二种,第一种采用继承的方式,弊端非常明显,该类继承了Thread线程类就无法再去继承其他类了,这样提高了程序的耦合性,第二开发中常用
三、线程在程序中的几种状态
出生(Thread t = new Thread)--->就绪(t.start()准备执行)--->执行(获得CPU执行权)
执行--->等待(t.wait()):线程处于等待状态,自己醒不了,只能用notify()或者notifyAll()唤醒,处于等待状态的线程会释放CPU执行权,同时释放锁资源---->就绪(唤醒了)
执行--->睡眠(t.sleep()):在制定的毫秒数内让当前正在执行的线程休眠(并不是永久),只是暂停执行,它会释放CPU执行权,但是并不会释放锁资源,设定的毫秒时间到了,就会脱离睡眠状态
执行--->阻塞,当多条线程存在输入输出时,就会出阻塞状态
执行--->死亡,run方法执行完毕,线程就结束了也就是处于死亡状态
四、多线程的安全隐患以及synchronized监视器(锁)
多线程如果操作共享数据的多条语句,就有可能出现数据错乱问题
这个时候就需要用到多线程的同步机制,synchronized同步机制,
1.同步代码块:同步机制用于代码块中:
Object lock = new Object();
public void run( ){
synchronized(lock) { //这就是所谓的锁,开发常用这样方式解决线程同步问题
........code......
}
}
2.同步方法 :
public synchronized void run() {
........code............
}
总结:使用同步代码块可以解决线程安全问题