多线程
以下是我在学习Java多线程时的一些总结,如有不对敬请指正!
进程与线程
进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程。多进程操作系统能同时达运行多个进程(程序),由于 CPU 具备分时机制,所以每个进程都能循环获得自己的CPU 时间片。由于 CPU 执行速度非常快,使得所有程序好像是在同时运行一样。
多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,线程是进程的基础之上进行进一步的划分。所谓多线程是指一个进程在执行过程中可以产生多个更小的程序单元,这些更小的单元称为线程,这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程。
Java程序的运行原理?
答:由 Java 命令启动 JVM,JVM 启动就相当于启动了一个进程。接着由该进程创建了一个主线程去调用 main 方法。
JVM虚拟机的启动是单线程还是多线程?
答:多线程。原因是垃圾回收线程也要先启动,否则很容易出现内存溢出。现在的垃圾回收线程加上主线程最低启动了两个线程,所以,JVM 的启动其实是多线程的。
线程实现方式(重要)
在 Java 中实现多线程有两种手段,一种是继承 Thread 类,另一种就是实现 Runnable 接口。下面我们就分别来介绍这两种方式的使用。
继承Thread类
public class demo1 {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程1");
MyThread mt2 = new MyThread("线程2");
mt1.start();
mt2.start();
}
}
//继承 Thread 类,作为线程的实现类
class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name = name;
}
//重写 run() 方法
public void run() {
for(int i = 0;i < 10;++i) {
System.out.println(name + ":" +i);
}
}
}
获取当前线程名称:Thread.currentThread.getName()
实现 Runnable 接口
public class demo4 {
public static void main(String[] args) {
ThreadRun tr = new ThreadRun();
Thread t1 = new Thread(tr, "线程1");
Thread t2 = new Thread(tr, "线程2");
t1.start();
t2.start();
}
}
class ThreadRun implements Runnable{
public void run() {
for(int i = 0;i < 10;++i) {
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}
两种实现多线程的方式比较
继承Thread类:
- 自定义类 MyThread 继承 Thread 类(extends Thread)
- 在 MyThread 类中重写 run()
- 创建 MyThread 类的对象
- 启动线程对象
实现 Runnable 接口:
- 自定义类 MyRunnable 实现 Runnable 接口(implements Runnable)
- 在 MyRunnable 里面重写 run()
- 创建 MyRunnable 类的对象
- 创建 Thread 类的对象,并把C步骤的对象作为构造参数传递
为什么有了继承Thread类
还要有实现 Runnable 接口
呢?
答:1.实现接口方式可以避免由于 Java 单继承带来的局限性。
2.适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码、数据有效分离,较好的体现了面向对象的设计思想。
因此,我们大部分情况都使用实现 Runnable 接口
。
匿名内部类的方式实现多线程程序
继承Thread类
public class Demo2 {
public static void main(String[] args) {
//继承 Thread 类
new Thread() {
public void run() {
System.out.println("匿名类实现多线程----继承 Thread 类");
}
}.start();;
}
}
实现Runnable接口
public class Demo2 {
public static void main(String[] args) {
//实现 Runnable 接口
new Thread(new Runnable() {
public void run() {
System.out.println("匿名类实现多线程----实现 Runnable 接口");
}
}) {}.start();
}
}
同步
案例:三个窗口卖100张电影票:
public class SellTicketsDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr, "窗口1");
Thread t2 = new Thread(mr, "窗口2");
Thread t3 = new Thread(mr, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
class MyRunnable implements Runnable{
private int tickets = 100;
private Object obj = new Object();
public void run() {
synchronized(obj) {
while(tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售第" + (tickets--) + "张票");
}
}
}
}
同步代码块:
synchronized(对象){
代码块
}
对象:随便创建
代码块:把多条语句共享数据的代码的部分给包起来
当任何一个对象进来后,锁关闭,相当于门关上,其他对象不能再进来。对象出来后,锁再开,相当于门开了。
注意:同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。因此,多个线程必须是同一把锁!
同步的特点:
-
**前提:**1.多个线程 2.多个线程使用的是同一个锁对象(比如电影票被三个窗口同时卖)。
-
**同步的好处:**解决了多线程的安全问题。
-
**同步的弊端:**1.当线程相当多时,因为每个线程都会去判断同步上的锁,很耗费资源,会降低程序的运行效率。2.容易产生死锁
死锁
死锁:两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象。
Demo:
public class DieLockDemo implements Runnable{
private boolean flag;
public DieLockDemo(boolean b) {
this.flag = b;
}
@Override
public void run() {
// TODO Auto-generated method stub
if(flag) {
synchronized(MyLock.obj1) {
System.out.println("if ---- 1");
synchronized(MyLock.obj2) { //线程1(true)可能卡在这
System.out.println("if ---- 2");
}
}
}else {
synchronized(MyLock.obj2) {
System.out.println("if ---- 2");
synchronized(MyLock.obj1) { //线程2(false)可能卡在这
System.out.println("if ---- 1");
}
}
}
}
}
线程控制
线程休眠
通过 Thread.sleep()
实现线程休眠(单位:ms):
public class demo1 {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程1");
MyThread mt2 = new MyThread("线程2");
mt1.start();
mt2.start();
System.out.println(Thread.currentThread().getName());
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name = name;
}
public void run() {
for(int i = 0;i < 10;++i) {
try {
Thread.sleep(1000); //线程休眠1000ms
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name + ":" +i);
}
}
}
加入线程
让某一线程执行完毕再执行其他线程:
public class demo1 {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程1");
MyThread mt2 = new MyThread("线程2");
mt1.start();
try {
mt1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mt2.start();
}
}
//自定义的 MyThread 类省略不写
守护线程
Thread.setDaemon(boolean b)
设置守护线程
当正在运行的线程都是守护线程时,Java 虚拟机退出,该方法必须在启动线程前调用。
逻辑实例:
public class demo1 {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程1");
MyThread mt2 = new MyThread("线程2");
//设置mt1、mt2为守护线程
mt1.setDaemon(true);
mt2.setDaemon(true);
mt1.start();
mt2.start();
Thread.currentThread().setName("被守护的主线程");
for(int i = 0;i < 5;++i) {
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name = name;
}
public void run() {
for(int i = 0;i < 100;++i) {
System.out.println(name + ":" +i);
}
}
}
运行结果:
被守护的主线程----0
被守护的主线程----1
被守护的主线程----2
被守护的主线程----3
被守护的主线程----4
线程1:0
线程1:1
线程1:2
线程1:3
线程1:4
线程1:5
线程1:6
线程1:7
线程2:0
线程2:1
线程2:2
线程2:3
线程中断(重要)
public void interrupt()
:中断线程。把线程的状态终止,并抛出一个 InterruptionException 。
public class demo3 {
public static void main(String[] args) {
MyThread1 m = new MyThread1();
m.start();
try {
Thread.sleep(2000);
m.interrupt(); //休眠时间超过2000ms,中断线程
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class MyThread1 extends Thread {
public void run() {
System.out.println("1、进入run()方法");
try {
Thread.sleep(10000); // 线程休眠10秒
System.out.println("2、已经完成了休眠");
} catch (InterruptedException e) {
System.out.println("3、休眠被终止");
return; // 返回调用处
}
System.out.println("4、run()方法正常结束");
}
};
输出结果:
1、进入run()方法
3、休眠被终止
设置线程的优先级
线程的优先级默认为5,范围是1 - 10 的int型数据。
通过setPriority()
方法我们可以设置线程的优先级,通过getPriority()
方法我们可以得到线程的优先级。
public class demo2 {
public static void main(String[] args) {
MyThreadDemo mt1 = new MyThreadDemo("线程1");
MyThreadDemo mt2 = new MyThreadDemo("线程2");
//获取 mt1 和 mt2 的线程优先级
System.out.println(mt1.getPriority());
System.out.println(mt2.getPriority());
//设置mt1的线程优先级为10
mt1.setPriority(10);
mt1.start();
mt2.start();
}
}
class MyThreadDemo extends Thread{
private String name;
public MyThreadDemo(String name) {
this.name = name;
}
public void run() {
for(int i = 0;i < 10;++i) {
System.out.println(name + "----" + i);
}
}
}
注意:线程优先级高仅仅表示线程获取的 CPU 时间片的几率高,但要在次数比较多,或者多次运行的时候才能看到效果。
面试题
为什么要重写 run() 方法?
run() 里面封装的是被线程执行的代码。
run() 和 start() 的区别?
run():仅仅是封装被线程执行的代码,直接调用是普通方法。
start():首先启动了线程,然后再由 JVM 去调用该线程的 run() 方法。
线程的生命周期?
新建:创建线程对象
就绪:线程有执行资格,没有执行权
运行:有执行资格,有执行权
阻塞:由于一些操作让线程处于了该状态。没有执行资格,没有执行权,而另一些操作却可以把它给激活,激活后处于就绪状态。
死亡:线程对象变成垃圾,等待被回收
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XZaTZoZp-1591231041889)(C:\Users\思考的机器\Desktop\QQ截图20200520192014.png)]
注意:必须先有执行资格才能有执行权!
sleep() 和 wait() 方法的区别
sleep() :必须指定时间;不释放锁
wait() :可以不指定时间,也可以指定时间;释放锁。
为什么 wait() , notify(), notifyAll() 等方法都定义在 Object 类中
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁,Object 代表任意的对象,所以定义在这里面。