java中的多线程
一.什么是进程?什么是线程?
进程是指一个内存中运行的应用程序,一个进程中有多个线程。 比如在windows系统里,一个运行的xx.exe就是一个进程。
java程序的进程里有几个线程:主线程,垃圾回收线程(后台线程)。
线程是指进程中的一个控制单元(执行任务),一个进程中可以运行多个线程,多个线程可以共享数据。
多进程:操作系统中同时运行的多个程序。
多线程:在一个进程总同时运行的多个任务。
二.进程和线程的比较
1. 进程A和进程B有独立的进程空间,他们的的内存独立不共享,进程中的数据存放空间(堆空间和栈空间)是独立的。
2. 线程A和线程B的堆空间和方法区内存共享,但是栈空间独立,一个线程一个栈。
三.创建线程的方式
Java语言提主要供了两种实现线程的方式:
- 继承Thread类创建线程类
- 实现Runnable接口创建线程类
1.继承Thread类创建线程类
(现在不推荐这种方法,应该面向接口编程,解耦合)
实现步骤:
- ①定义类继承Thread类,并重写Thread类的run()方法,
- ②创建Thread子类的实例,即创建了线程对象。
- ③调用线程对象的start()方法来启动该线程。
示例代码:
注意:代码中start()方法的作用
/**
* 2020/12/4,zhanglijun
*/
public class ThreadTest01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
//这段代码的作用只是为了开启一个新的栈空间,,只要新的栈空间开出来,start()方法就结束了。线程就自动启动成功了。
//启动成功后的线程会自动调用run方法,并且run方法在分支栈的底部(压栈)。
//run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的
myThread.start();
for(int i=0;i<50;i++){
System.out.println("主线程"+i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println("分支线程启动"+i);
}
}
}
2.实现Runnable接口创建线程类
实现步骤:
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
示例代码:
package com.多线程.Thread;
/**
* 2020/12/4,zhanglijun
*/
public class ThreadTest03 {
public static void main(String[] args) {
// MyRunnable myRunnable = new MyRunnable();
// Thread thread = new Thread(myRunnable);
Thread thread = new Thread(new MyRunnable());//合并代码
thread.start();
for(int i=0;i<100;i++){
System.out.println("主线程执行"+i);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("分支线程执行"+i);
}
}
}
顺便复习一下匿名内部类,也可以使用匿名内部类的方式来写。
public class ThreadTest04 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("分支线程执行---"+i);
}
}
});
//启动线程
thread.start();
for(int i=0;i<100;i++){
System.out.println("main线程执行--"+i);
}
}
}
3.两种进程创建方式的比较
A extend Thread:
简单
不能继承其他类了(java单继承)
同份资源不能共享
A implements Runnable:(推荐)
多个线程共享一个资源目标,适合多线程处理同一份资源。
该类还可以继承其他类,也可以实现其他接口。
小问题:为什么要覆盖run方法呢?
Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码。该代码功能就是run方法。也就是说Thread类中的run方法,用于存储线程要运行的代码。
*四.线程的生命周期
1. 新建状态(New):刚new出来的线程对象就处于这种状态。
2. 就绪状态(Runnable):就绪状态的线程又叫做可运行状态,表示当前线程具有抢夺CPU时间片的权利(CPU时间片就是执行权)。当一个线程抢夺到CPU时间片之后,就开始执行run方法,run方法开始执行标志着线程进入运行状态。
3. 运行状态(Running):run方法开始执行标志着线程进入运行状态,当之前占有的CPU时间片用完之后,会重新回到就绪状态,继续抢夺时间片,当再次抢到时间片之后,会重新进入run方法接着上一次的代码继续往下执行。
4. 阻塞状态(Blocked):当一个线程遇到阻塞事件,例如接受用户输入,或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片。
5. 死亡状态(Dead):已退出的线程,线程执行完了或者因异常退出了run()方法,处于这种状态。该线程结束生命周期。
五.线程控制(线程调度
)
java中提供了哪些方法是和线程调度有关系的呢?
- void setPriority(int newPriority) 设置线程的优先级
- int getPriority() 获取线程优先级
Thread4 t1 = new Thread4("t1");
Thread4 t2 = new Thread4("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
Java线程有优先级,优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
static int MAX_PRIORITY
线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
分配给线程的默认优先级,取值为5。
主线程的默认优先级 Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
-
static void yield() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。 -
void join()
等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
class MyThread1 extends Thread {
public void doSome(){
MyThread2 t = new MyThread2();
t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。
}
}
class MyThread2 extends Thread{
}
-
Thread.sleep(long millis)
线程睡眠方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
-
Object类中的wait()
线程等待方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
-
Object类中的notify()
线程唤醒方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
五.多线程安全
Java中有三大变量?【重要的内容。】
-
实例变量:在堆中。
-
静态变量:在方法区。
-
局部变量:在栈中。
以上三大变量中:
局部变量永远都不会存在线程安全问题。
因为局部变量不共享。(一个线程一个栈。)
局部变量在栈中。所以局部变量永远都不会共享。实例变量在堆中,堆只有1个。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。
如果使用局部变量的话:
建议使用:StringBuilder。
因为局部变量不存在线程安全问题。选择StringBuilder。
StringBuffer效率比较低。
ArrayList是非线程安全的。
Vector是线程安全的。
HashMap HashSet是非线程安全的。
Hashtable是线程安全的。
导致安全问题的出现的原因:
多个线程访问出现延迟。
线程随机性。
注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
我们可以通过 Thread.sleep(long time)方法来简单模拟延迟情况。
我的总结:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还
没有执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不
可以参与执行。
六.多线程安全解决方法(如何做到线程同步)
三种方法:
同步代码块:
synchronized(obj)
{
//obj 表示同步监视器,是同一个同步对象
/**…
TODO SOMETHING
*/
}
同步方法
格式:
在方法上加上 synchronized 修饰符即可。(一般不直接在 run 方法上加!)
…
…