Java多线程(创建,管理)

概念

程序

程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

进程:

定义:进程是程序在一个数据集合上的运行过程,是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。是系统进行资源分配和调度的一个独立的基本单位
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

进程的特征

  1. 动态性:进程是程序的一次运行过程,有生命周期。

  2. 并发性

  3. 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位

  4. 异步性:进程按各自独立的

线程:

概念:线程是进程中的一个实体是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行

线程与进程的关系
a、一个进程可以有多个线程,但至少有一个线程;而一个线程只能在一个进程的地址空间内活动
b、资源分配给进程,同一个进程的所有线程共享该进程所有资源。
c、CPU分配给线程,即真正在处理器运行的是线程。
d、线程在执行过程中需要协作同步,不同进程的线程间要利用消息通信的办法实现同步。

线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小

进程和线程的比较

a. 调度性:在传统的操作系统中,拥有资源的基本单位和独立调动、分派的基本单位都是进程,在引入线程的OS中,则把线程作为调度和分派的基本单位,而把进程作为资源拥有(分配)的基本单位
b. 并发性:在引入线程的OS中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间也可并发执行,因而使OS具有更好的并发性
c. 拥有资源:无论是传统的操作系统还是引入线程的操作系统,进程始终是拥有资源的一个基本单位,而线程除了拥有一点在运行时必不可少的资源外,本身基本不拥有系统资源,但它可以访问其隶属进程的资源。
d. 系统开销:由于创建、撤销和切换进程时所付出的开销将显著地大于线程。

多进程和多线程示例
多进程执行:启动两个java.exe
多线程执行:只启动一个java.exe

单核CPU和多核CPU

  • 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。(只是速度非常快,察觉不到)

  • 如果是多核的话,才能更好的发挥多线程的效率。(多核cpu某一时刻可以执行多个线程)。

  • 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

并行与并发

 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

使用多线程的优点

  • 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。

  • 提高计算机系统CPU的利用率

  • 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

何时需要多线程

  • 程序需要同时执行两个或多个任务。

  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。

  • 需要一些后台运行的程序时。

JDK1.5之前多线程的实现

在Java语言中,多线程的机制是通过虚拟CPU来实现的,每台CPU可以获取所需的代码和数据,因此能独立执行任务,相互间还可以共享代码和数据。
Java的线程是通过java.lang.Thread类来实现的,它的内部实现了的虚拟CPU功能。

使用Runnable接口创建线程

• java.lang.Runnable接口
虚拟CPU、代码和数据分开,形成清晰的模型。
run()方法所在的类还可以从其他类继承一些有用的属性和方法。
有利于保持程序风格的一致性。

Runnable方式:

  1. 创建一个实现了Runnable接口的类

  2. 实现类去实现Runnable中的抽象方法:run()

  3. 创建实现类的对象

  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象(2,实现Runnable的对象必须包装在Thread类里面,才可以启动;例如下面:)

  5. 通过Thread类的对象调用start()

注:准确的说实现了Runnable的类只是定义了一个任务,一般还要把实现了Runnable的类的对象传递给一个Thread对象才能开启线程

class MyRunnable implements Runnable{
			public void run(){
				……//重写接口中的run方法

			}
		}
		class Demo1{
			public static void main(String [ ] args){
			MyRunnable r = new MyRunnable ( );
			Thread t=new Thread(r);
			t.start( );
			}
		}

继承Thread类创建线程

java.lang.Thread
继承Thread类的子类无法再继承其它类。
编写简单: run()方法的当前对象就是线程对象,可以直接调用。

Thread方式:

  1. 定义子类继承Thread类。

  2. 子类中重写Thread类中的run方法。

  3. 创建Thread子类对象,即创建了线程对象。

  4. 调用线程对象start方法:启动线程,调用run方法。

class MyThread extends Thread{
			public void run(){
					……//重写父类中的run方法
			}
		}
		class Demo2{
			public static void main(String [ ] args){
			Thread t=new MyThread( );
			t.start( );
			}
		}

四条规则

第一条规则:.
1,调用run方法,来启动run方法,将会是串行运行;(就是man中调用方法)
2,调用start方法,来启动run方法,将会是并行运行(多线程运行)。

第二条规则:.
1. main程可能早于子线程结束;
2, main线程和子线程都结束了,整个程序才算终止。

第三条规则:
1,实现Runnab le的对象必须包装在Thread类里面,才可以启动;
2,不能直接对Runnab le的对象进行start方法。

第四条规则:
1,一个线程对象不能多次start,多次start将报异常;(只能创建出另外的对象t1,t2,t3)

2,多个线程对象都start后,哪一个先执行,完全由JVM/操作系统来主导,程序员无法指定。

两种方式对比

• Thread vs Runnable
– Thread占据了父类的名额,不如Runnable方便
– Thread 类实现Runnable
– Runnable启动时需要Thread类的支持
– Runnable 更容易实现多线程中资源共享(Thread里面,你必须用static变量,才能实现变量共享(天然共享,一个对象放到三个therad,对象还是一个,被共享)
• 结论:建议实现Runnable接口来完成多线程

Thread类

Thread类的特性

每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体

构造器:

  • Thread():创建新的Thread对象

  • Thread(String threadname):创建线程并指定线程实例名

  • Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法

  • Thread(Runnable target, String name):创建新的Thread对象

总结Thread中的常用方法

  1. void start(): 启动线程,并执行对象的run()方法

  2. run(): 线程在被调度时执行的操作

  3. String getName(): 返回线程的名称

  4. void setName(String name):设置该线程名称

  5. static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类

  6. static void yield():线程让步
    6.1暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
    6.2若队列中没有同优先级的线程,忽略此方法

  7. join():当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
     低优先级的线程也可以获得执行

  8. static void sleep(long millis):(指定时间:毫秒)
     令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
     抛出InterruptedException异常

  9. stop(): 强制线程生命期结束,不推荐使用
     boolean isAlive():返回boolean,判断线程是否还活着

其他说明:
Thread.currentThread()可以获取当前线程的引用,一般都是在没有线程对象又需要获得线程信息时通过Thread.currentThread()获取当前代码段所在线程的引用。
Thread.currentThread()是Thread的引用newThread, 而this依旧是MyThread的引用,所以是不一样的,打印的内容也不一样

Java多线程管理

线程状态

– NEW 刚创建(new)
– RUNNABLE 就绪态(start)
– RUNNING 运行中(run)
– BLOCK 阻塞(sleep)
– TERMINATED 结束
在这里插入图片描述

Thread的部分API已经废弃

– 暂停和恢复 suspend/resume
– 消亡 stop/destroy

线程的阻塞的两种途径
Thread类的suspend()方法提供线程的挂起操作
“挂起”是指暂停当前运行中的线程转入阻塞状态,并且不会自动恢复运行。
Thread类的resume()方法可以使阻塞的线程恢复运行
调用该线程被锁定资源的wait()方法可以让当前线程等待,调用notify()或notifyAll()方法通知等待结束。
区别:
suspend() 方法所导致的挂起不会释放占有的资源

线程阻塞/和唤醒

sleep,时间一到,自己会醒来
wait/notify/notifyAll,等待,需要别人来唤醒
join,等待另外一个线程结束
interrupt,向另外一个线程发送中断信号,该线程收到信号,会触发InterruptedException(可解除阻塞),并进行下一步处理

(Interrupted ()是Thread类的方法,用来测试当前线程是否收到一个INTERRUPT的信号。如果收到,该方法返回true,否则回false.)


  • wait() 与 notify() 和 notifyAll()

①wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。(一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。)


②notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待 (一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。)


③notifyAll ():唤醒正在排队等待资源的所有线程结束等待.(一旦执行此方法,就会唤醒所有被wait的线程。)


  • 这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。

  • 因为这三个方法必须由锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。

  • wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。

面试题:sleep() 和 wait()的异同?

1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
2.不同点:
1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

区别

wait()方法可以使调用该线程的方法释放持有当前对象的锁,然后从运行状态退出,进入等待队列,直到再次被唤醒。
notify()方法可以随机唤醒等待队列中等待的一个线程,并使得该线程退出等待状态,进入可运行状态

代码:
InterruptTest .java

public class InterruptTest {

	public static void main(String[] args) throws InterruptedException {
		TestThread1 t1 = new TestThread1();
		TestThread2 t2 = new TestThread2();

		t1.start();
		t2.start();

		// 让线程运行一会儿后中断
		Thread.sleep(2000);
		t1.interrupt();
		t2.flag = false;
		System.out.println("main thread is exiting");
	}

}

class TestThread1 extends Thread {
	public void run() {
		// 判断标志,当本线程被别人interrupt后,JVM会被本线程设置interrupted标记
		while (!interrupted()) {
			System.out.println("test thread1 is running");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
				break;
			}
		}
		System.out.println("test thread1 is exiting");
	}
}

class TestThread2 extends Thread {
	public volatile boolean flag = true;
	public void run() {
		// 判断标志,当本线程被别人interrupt后,JVM会被本线程设置interrupted标记
		while (flag) {
			System.out.println("test thread2 is running");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("test thread2 is exiting");
	}
}

线程被动地暂停和终止

– 依靠别的线程来拯救自己
– 没有及时释放资源

线程主动暂停和终止

– 定期监测共享变量
– 如果需要暂停或者终止,先释放资源,再主动动作
– 暂停:Thread.sleep(),休眠
– 终止:run方法结束,线程终止

线程休眠
Thread类的静态方法sleep(long millis)可以在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
休眠结束的线程不是直接进入运行状态,而是在就绪队列中等待并尝试获取继续运行的机会

try{
	Thread.sleep(1000); //暂停1000毫秒(1秒)
}catch(InterruptedException e){
	e.printStackTrace();
}

多线程死锁

  • 每个线程互相持有别人需要的锁(哲学家吃面问题)

  • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

解决方法

  • 专门的算法、原则

  • 尽量减少同步资源的定义

  • 尽量避免嵌套同步
    预防死锁,对资源进行等级排序

import java.util.concurrent.TimeUnit;

public class ThreadDemo5
{
	public static Integer r1 = 1;
	public static Integer r2 = 2;
	public static void main(String args[]) throws InterruptedException
	{
		TestThread51 t1 = new TestThread51();
		t1.start();
		TestThread52 t2 = new TestThread52();
		t2.start();
	}
}

class TestThread51 extends Thread
{
	public void run() 
	{
		//先要r1,再要r2
		synchronized(ThreadDemo5.r1)
		{
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized(ThreadDemo5.r2)
			{
				System.out.println("TestThread51 is running");
			}
		}
	}
} 
class TestThread52 extends Thread
{
	public void run() 
	{
		//先要r2,再要r1
		synchronized(ThreadDemo5.r2)
		{
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized(ThreadDemo5.r1)
			{
				System.out.println("TestThread52 is running");
			}
		}
	}
} 

线程的优先级

Java的线程优先级用整数表示,范围从1(最低)~10(最高)。
主线程和一般线程的缺省优先级是5(中等)
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5 -->默认优先级
获得某个线程优先级的方法是:
public final int getPriority()
设置某个线程优先级的方法是:
public final void setPriority(int newPriority)

说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。

后台处理(Background Processing):在分时处理或多任务系统中,当高优先级的程序不再使用系统资源时,计算机去执行优先级较低程序的过程叫做后台处理。如:文件打印。

线程让步
Thread类的静态方法yield()可以令当前运行中线程“让步”于其它线程。
即使不显式调用yield()方法进行“让步”,当时间片到期时,系统一般也会令其“让步”,以使其它线程获得运行机会。调用yield()方法只是令当前线程主动在时间片到期前让出本次运行机会而已。

守护(后台)线程

– 普通线程的结束,是run方法运行结束
– 守护线程的结束,是run方法运行结束,或main函数结束
守护线程永远不要访问资源,如文件或数据库等
• 线程查看工具 jvisualvm

  • 后台线程(Daemon Thread):后台运行,为其它线程提供服务的线程,又称为守护线程。如:JVM垃圾回收。

  • 用户线程(User Thread):用户创建的完成用户指定任务的线程成为用户线程。

  • 主线程(Main Thread):调用main()方法所产生的线程。

  • 子线程(Sub Thread):在主线程中或某个线程中创建的另外一个线程。

Thread类提供的与后台线程相关的方法
判断某个线程是否是后台线程:
public final boolean isDaemon()
设置某个线程为后台线程:
public final void setDaemon(boolean on)
例如:

Thread t2=new MyRunner(300);
t2.setDaemon(true);

待用户线程包括主线程全部结束后,JVM检测到只剩下后台线程的时候,就退出当前程序的运行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值