当前位置: 移动技术网 > IT编程>开发语言>Java > Java并发(多线程)

Java并发(多线程)

2020年07月31日  | 移动技术网IT编程  | 我要评论

Java并发(多线程)

线程与进程

  1. 进程:是程序的一次动态执行过程,是系统进行资源分配和调度的一个独立单位。

    进程例子—>电脑上的任务管理器

    进程的三种基本状态:

    • 就绪状态:进程分配到除CPU以外的所有必要资源

    • 执行状态:获得CPU,程序执行

    • 阻塞状态:请求I/O,申请缓存空间等

在这里插入图片描述

  1. 线程:是一个比进程更小的执行单位。一个进程会有一个或多个线程。

    (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 类有如下两个常用构造方法:

  1. public Thread(String threadName)
  2. public Thread()

Thread 方法:

  1. public void start():开始执行线程;Java 虚拟机调用该线程的 run 方法

    1. public void run():如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回
    2. **public final void join(long millisec) ** :等待该线程终止的时间最长为 millis 毫秒
    3. public final void setPriority(int priority):更改线程的优先级。
    4. public final void setName(String name):改变线程名称,使之与参数 name 相同。
    5. 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 接口启动线程的基本步骤如下:

  1. 创建一个 Runnable 对象。

  2. 使用参数带 Runnable 对象的构造方法创建 Thread 实例。

  3. 调用 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 对象。
  • 修饰代码块,将会锁住代码块中的对象。
  1. 修饰普通方法

    public class Test {
        // 修饰普通方法
        public synchronized void test() {
            // 需同步的代码  
        }
    }
    
  2. 修饰静态方法

    public class Test {
        // 修饰静态方法
        public static synchronized void test() {
            // 需同步的代码 
        }
    }
    
  3. 修饰代码块

    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 的锁。两个线程休眠结束后,都想请求获取对方的资源,然后两个线程这样等待了下去,造成了死锁。

死锁产生的四个条件:

  • 互斥条件:在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求该资源,则请求者只能等待,直至占有该资源的进程用毕释放。
  • 请求和保持条件:指进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
  • 不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
  • 环路等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

如何避免线程死锁?

只需要破坏产生死锁的四个条件之一即可。

  • **破坏互斥条件:**这个条件我们没有办法破坏,因为我们用锁本身就是想让他们互斥的(临界资源需要互斥访问)。
  • **破坏请求与保持条件:**一次性申请所有的资源
  • **破坏不剥夺条件:**占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  • **破坏循环等待条件:**靠按顺序申请资源来预防。按照某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

问题

  1. 并发和并行的区别

    答:并发是同一时间段内同时执行多个任务。针对线程。多个处理器或多核处理器同时处理多个任务。

    ​ 并行是同一时刻内执行多个任务。针对进程。多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上看这些任务是同时进行的。

  2. 线程和进程的区别

    答:进程是程序的一次动态执行,线程是一个比进程更小的执行单位。一个进程会有一个或多个线程。

  3. 创建线程有哪几种方式?

    答:创建线程有三种方式:

    ①继承Thread重写run方法

    ②实现Runnable接口

    ③实现 Callable 接口

  4. 线程有哪些状态?

    答:线程的状态:

    · New:线程尚未启动

    · Runnable: 正在执行中

    · Blocked: 阻塞(被同步锁或者IO锁阻塞)

    · Waiting: 等待状态

    · Timed waiting: 等待指定的时间重新被唤醒的状态

    · Terminated:执行完成

  5. run() 和 start() 的区别
    答:start() 用于启动线程,run() 用于执行线程的运行时代码。run() 可以被重复调用,而 start() 只能被调用一次。多线程的工作是start() 做线程的相应准备工作,然后自动执行 run() 方法的内容。如果直接执行 run() 方法,被认为是在主线程下的 thread 的一个普通方法,这不是多线程工作。

  6. sleep() 和 wait()的区别
    答:①sleep() 方法没有释放锁,而 wait() 方法释放了锁。

    ②sleep() 来自 Thread,wait() 来自 Object。

    ③sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。

  7. 什么是死锁?
    答:当线程1持有锁a,并尝试去获取锁b ,此时锁b已被线程2 获取,同时线程2也想获取锁 a ,两个线程进入僵持状态,而发生的阻塞现象。

  8. 如何保证线程安全?
    答:①使用synchronized锁(自动锁)

    ​ ②使用Lock锁(手动锁)

    ​ ③使用安全类,例如java.util.concurrent 下的类

本文地址:https://blog.csdn.net/weixin_43795115/article/details/107694568

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网