Java并发(多线程)
Java并发(多线程)
线程与进程
-
进程:是程序的一次动态执行过程,是系统进行资源分配和调度的一个独立单位。
进程例子—>电脑上的任务管理器
进程的三种基本状态:
-
就绪状态:进程分配到除CPU以外的所有必要资源
-
执行状态:获得CPU,程序执行
-
阻塞状态:请求I/O,申请缓存空间等
-
-
线程:是一个比进程更小的执行单位。一个进程会有一个或多个线程。
(1)线程的六种基本状态:
-
New(新创建):new Thread(r) 创建一个新线程
-
Runnable(可运行):一旦调用start方法,线程处于runnable状态。一个可运行的线程是否在运行,取决于操作系统给线程提供运行的时间。
-
Blocked(阻塞):线程因为某种原因放弃 CPU 使用权,暂时停止运行。在可执行状态下,如果调用sleep(),suspend(),wait() 等方法,线程都将进入阻塞状态
-
Waiting(等待):当处于运行状态下的线程调用 Thread 类的 wait() 方法时,线程进入等待状态。进入等待状态的线程必须调用 Thread 类的 notify() 方法才能被唤醒。notifyAll() 方法是将所有处于等待状态下的线程唤醒。
-
Timed waiting(计时等待):当线程等待另一个线程通知调度一个条件时,自己进入等待状态
-
Terminated(死亡):①run方法正常退出而自然死亡 ②没有捕获的异常终止了run方法而意外死亡
-
(2)线程的属性:①轻型实体②独立调度和分派的基本单位③可开发执行④共享资源进程
(3)线程的实现方式:①用户级线程:仅存在于用户空间中,线程管理全由用户程序完成,该线程系统的调度仍以进程为单位进行的;②内核支持线程:在内核空间实现
3.多线程
多线程:在程序中执行多个线程,每个线程完成一个功能,并与其他线程并发执行。使用多线程的主要目的是提高系统的资源利用率
多线程面临的问题:内存泄露、死锁和上下文切换等
并发和并行
并发:同一时间段内同时执行多个任务。针对线程
并行:同一时刻内执行多个任务。针对进程
Java多线程的实现方式
两种:①继承Thread类 ②实现Runnable接口
继承Thread类
Thread 类的结构:
public class Thread implements Runnable
//由此看出Thread类实现Runnable接口(为实现多继承)
Thread 类有如下两个常用构造方法:
public Thread(String threadName)
public Thread()
Thread 方法:
-
public void start():开始执行线程;Java 虚拟机调用该线程的 run 方法
- public void run():如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回
- **public final void join(long millisec) ** :等待该线程终止的时间最长为 millis 毫秒
- public final void setPriority(int priority):更改线程的优先级。
- public final void setName(String name):改变线程名称,使之与参数 name 相同。
- public static void sleep(long millisec) :在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
继承 Thread 类实现线程:
class MyThread extends Thread{ //MyThread类继承自 Thread 类
public MyThread(String name){
super(name);
}
public void run(){ //线程实现
for(int i=1;i<=10;i++){
System.out.println(getName()+"正在运行"+i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread mt1=new MyThread("线程1"); //创建线程
MyThread mt2=new MyThread("线程2");
mt1.start(); //start() 执行线程
mt2.start();
}
}
两个线程对象是交错运行的,哪个线程对象先抢到了 CPU 资源,哪个线程就可以运行,所以程序每次的运行结果是随机的。类中的 start() 方法表示此线程已经准备就绪,等待调用线程对象的 run() 方法。这个过程其实就是让系统安排一个时间来调用 Thread 中的 run() 方法,使线程得到运行,启动线程,具有异步执行的效果。
实现 Runnable 接口
如果要创建的线程类已经有一个父类,这时就不能再继承 Thread 类,因为 Java 不支持多继承,所以需要实现 Runnable 接口来应对这样的情况。
实现 Runnable 接口的语法格式如下:
public class thread extends Object implements Runnable
使用上述两种构造方法之一均可以将 Runnable 对象与 Thread 实例相关联。使用 Runnable 接口启动线程的基本步骤如下:
-
创建一个 Runnable 对象。
-
使用参数带 Runnable 对象的构造方法创建 Thread 实例。
-
调用 start() 方法启动线程。
实现 Runnable 接口实现多线程:
class PrintRunnable implements Runnable { //实现Runnable接口
int i = 1;
@Override
public void run() { // 重写run()方法,作为线程的操作主体
while (i <= 10)
System.out.println(Thread.currentThread().getName() + "正在运行" + (i++));
//Thread.currentThread().getName():获得当前线程名字
}
}
public class Test {
public static void main(String[] args) {
PrintRunnable pr = new PrintRunnable(); //创建Runnable 对象
Thread t1 = new Thread(pr); //使用参数带 Runnable 对象的构造方法创建 Thread 实例
t1.start();//启动线程
//PrintRunnable pr1 = new PrintRunnable();
Thread t2 = new Thread(pr);
t2.start();
}
}
线程生命周期
线程的声明周期共有 6 种状态,分别是:新建 New
、运行(可运行)Runnable
、阻塞Blocked
、计时等待Timed Waiting
、等待Waiting
和终止Terminate
。
当你声明一个线程对象时,线程处于新建状态,系统不会为它分配资源,它只是一个空的线程对象
MyThread my1 = new MyThread();
调用
start()
方法时,线程就成为了可运行状态,至于是否是运行状态,则要看系统的调度了。my1.start();
调用了
sleep()
方法、调用wait()
方法和 IO 阻塞时,线程失去所占用资源之后,线程处于等待、计时等待或阻塞状态。class MyThread implements Runnable{ @Override public void run() { for(int i=1;i<=30;i++){ System.out.println(Thread.currentThread().getName()+"执行第"+i+"次!"); try { Thread.sleep(1000); //线程休眠1秒 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public class SleepDemo { public static void main(String[] args) { MyThread mt=new MyThread(); Thread t=new Thread(mt); t.start(); Thread t1=new Thread(mt); t1.start(); } }
当
run()
方法执行结束后,线程也就终止了。
Java 程序每次运行至少启动几个线程?
答:至少启动两个线程,每当使用 Java 命令执行一个类时,实际上都会启动一个 JVM,每个JVM实际上就是在操作系统中启动一个线程,Java 本身具备了垃圾的收集机制。所以在 Java 运行时至少会启动两个线程,一个是 main 线程,一个是垃圾回收线程。
线程的优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name=name;
}
public void run(){
for(int i=1;i<=50;i++){
System.out.println("线程"+name+"正在运行"+i);
}
}
}
public class PriorityDemo {
public static void main(String[] args) {
//获取主线程的优先级
int mainPriority=Thread.currentThread().getPriority();
//System.out.println("主线程的优先级为:"+mainPriority);
MyThread mt1=new MyThread("线程1");// 实例化线程对象
MyThread mt2=new MyThread("线程2");
MyThread mt3 = new Thread("线程3") ;
//mt1.setPriority(10);
mt1.setPriority(Thread.MAX_PRIORITY);//优先级最高
mt2.setPriority(Thread.MIN_PRIORITY);//优先级最低
mt3.setPriority(Thread.NORM_PRIORITY);//优先级中等
mt2.start();
mt1.start();
mt3.start();
//System.out.println("线程1的优先级为:"+mt1.getPriority());
}
}
线程同步
当多个线程操作同一个对象时,就会出现线程安全问题,被多个线程同时操作的对象数据可能会发生错误。线程同步可以保证在同一个时刻该对象只被一个线程访问。
在Java中,synchronized关键字是用来控制线程同步的,在多线程的环境下,synchronized把代码段锁起来,不被多个线程同时执行。
关键字 synchronized 是一种互斥锁, 它确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,保证了线程对变量访问的可见性和原子性。它有三种使用方法:
- 修饰普通方法,将会锁住当前实例对象。
- 修饰静态方法,将会锁住当前类的 Class 对象。
- 修饰代码块,将会锁住代码块中的对象。
-
修饰普通方法
public class Test { // 修饰普通方法 public synchronized void test() { // 需同步的代码 } }
-
修饰静态方法
public class Test { // 修饰静态方法 public static synchronized void test() { // 需同步的代码 } }
-
修饰代码块
public class Test { public void test() { // 修饰代码块 synchronized (this){ // 需同步的代码 } } }
线程死锁
线程同步保证了资源共享操作的正确性,但是过多的同步会带来死锁问题。比如说一手交钱一手交货问题,你不交钱我不交货,互相等待,这就造成死锁。
死锁:两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待的现象。
实例:(来自《Java多线程编程核心技术》)
package com.imooc.DealThread;
public class DealThread implements Runnable {
public String username;
public Object lock1 = new Object();
public Object lock2 = new Object();
public void setFlag(String username) {
this.username = username;
}
@Override
public void run() {
if (username.equals("a")) {
synchronized (lock1) {
try {
System.out.println("username = " + username);
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("按lock1->lock2代码顺序执行了");
}
}
}
if (username.equals("b")) {
synchronized (lock2) {
try {
System.out.println("username = " + username);
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("按lock2->lock1代码顺序执行了");
}
}
}
}
}
测试:
package com.imooc.DealThread;
public class DeadThreadTest {
public static void main(String[] args) {
try {
DealThread dtd1 = new DealThread();
dtd1.setFlag("a");
Thread thread1 = new Thread(dtd1);
thread1.start();
Thread.sleep(100);
dtd1.setFlag("b");
Thread thread2 = new Thread(dtd1);
thread2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
username = a
username = b
线程 a通过 synchronized (lock1) 获取了 lock1
的锁后休眠3s,执行线程b获取 lock2
的锁。两个线程休眠结束后,都想请求获取对方的资源,然后两个线程这样等待了下去,造成了死锁。
死锁产生的四个条件:
- 互斥条件:在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求该资源,则请求者只能等待,直至占有该资源的进程用毕释放。
- 请求和保持条件:指进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
- 不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
- 环路等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
如何避免线程死锁?
只需要破坏产生死锁的四个条件之一即可。
- **破坏互斥条件:**这个条件我们没有办法破坏,因为我们用锁本身就是想让他们互斥的(临界资源需要互斥访问)。
- **破坏请求与保持条件:**一次性申请所有的资源
- **破坏不剥夺条件:**占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- **破坏循环等待条件:**靠按顺序申请资源来预防。按照某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
问题
-
并发和并行的区别
答:并发是同一时间段内同时执行多个任务。针对线程。多个处理器或多核处理器同时处理多个任务。
并行是同一时刻内执行多个任务。针对进程。多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上看这些任务是同时进行的。
-
线程和进程的区别
答:进程是程序的一次动态执行,线程是一个比进程更小的执行单位。一个进程会有一个或多个线程。
-
创建线程有哪几种方式?
答:创建线程有三种方式:
①继承Thread重写run方法
②实现Runnable接口
③实现 Callable 接口
-
线程有哪些状态?
答:线程的状态:
· New:线程尚未启动
· Runnable: 正在执行中
· Blocked: 阻塞(被同步锁或者IO锁阻塞)
· Waiting: 等待状态
· Timed waiting: 等待指定的时间重新被唤醒的状态
· Terminated:执行完成
-
run() 和 start() 的区别
答:start() 用于启动线程,run() 用于执行线程的运行时代码。run() 可以被重复调用,而 start() 只能被调用一次。多线程的工作是start() 做线程的相应准备工作,然后自动执行 run() 方法的内容。如果直接执行 run() 方法,被认为是在主线程下的 thread 的一个普通方法,这不是多线程工作。 -
sleep() 和 wait()的区别
答:①sleep() 方法没有释放锁,而 wait() 方法释放了锁。②sleep() 来自 Thread,wait() 来自 Object。
③sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。
-
什么是死锁?
答:当线程1持有锁a,并尝试去获取锁b ,此时锁b已被线程2 获取,同时线程2也想获取锁 a ,两个线程进入僵持状态,而发生的阻塞现象。 -
如何保证线程安全?
答:①使用synchronized锁(自动锁) ②使用Lock锁(手动锁)
③使用安全类,例如java.util.concurrent 下的类
本文地址:https://blog.csdn.net/weixin_43795115/article/details/107694568
上一篇: 外婆菜是梅干菜吗