面试和学习必备--Java多线程

目录

一:多线程的基本概念

1.1什么是进程?

1.2什么是线程?

1.3多进程有什么作用?

1.4多线程有什么作用?

1.5java 程序的运行原理?

二:线程的创建和启动

2.1继承Thread类

2.2实现Runable接口

三:线程的生命周期

四:线程的调度和控制

4.1线程优先级

4.2Thread.sleep

4.3Thread.yield

4.4t.join(成员方法)

4.5t.interrupt(中断)

4.6如何正确的停止一个线程

五:线程的同步

1.为什么需要线程同步

2.使用线程同步

六:synchronized关键字的使用

6.1同步代码块

6.2在实例方法上使用synchorized和在静态方法上使用synchronized

6.3面试题:深入synchronized



一:多线程的基本概念

线程指 进程 中的一个 执行场景 ,也就是执行流程,那么进程和线程有什么区别呢?
  1. 每个进程是一个应用程序,都有独立的内存空间 ,每个进程都相当于一个独立的APP
     2.同一个进程中的线程 共享 其进程中的内存和资源
        ( java中共享的 内存是堆内存和方法区内存,栈内存不共享,每个线程有自己的 。)

1.1什么是进程?

一个进程对应一个应用程序。例如:在 windows 操作系统启动 Word 就表示启动了一个
进程。在 java 的开发环境下启动 JVM ,就表示启动了一个进程。现代的计算机都是支持多
进程的,在同一个操作系统中,可以同时启动多个进程。

1.2什么是线程?

线程是一个进程中的执行场景。一个进程可以启动多个线程。
简单来说: 进程是一个应用程序(软件),线程是一个进程中执行场景或者是执行单元,一个进程可以启动多个线程

1.3多进程有什么作用?

单进程计算机(单核CPU)同一时间只能做一件事情。
对于单核计算机来讲,在同一个时间点上,游戏进程和音乐进程是同时在运行吗?不是
因为计算机的 CPU 只能在某个时间点上做一件事。由于计算机将在“游戏进程”和“音乐
进程”之间频繁的切换执行,切换速度极高,人类感觉游戏和音乐在同时进行。
多进程的作用不是提高执行速度,而是提高 CPU 的使用率
进程和进程之间的内存是 独立 的。
单核CUP不能真正做到多线程并发,但是可以给人一种“多线程并发”的错觉,由于CUP的处理速度非常快,多个线程之间频繁切换执行,给人的感觉是,多个事情同时在做。

1.4多线程有什么作用?

多线程不是为了提高执行速度,而是提高 应用程序 的使用率。
在java中线程和线程共享“堆内存和方法区内存”,栈内存是独立的, 一个线程一个栈
可以给现实世界中的人类一种错觉:感觉多个线程在同时并发执行。

1.5java 程序的运行原理?

java 命令会启动 java 虚拟机,启动 JVM ,等于启动了一个应用程序,表示启动了一个
进程。该进程会自动启动一个“ 主线程 ”,然后主线程去调用某个类的 main 方法。所以 main
方法运行在主线程中。在此之前的所有程序都是单线程的。

二:线程的创建和启动

Java 虚拟机的 主线程 入口是 main 方法,用户可以自己创建线程,创建方式有 两种
        1.继承 Thread
        2.实现 Runnable 接口( 推荐使用 Runnable 接口

2.1继承Thread类

采用 Thread 类创建线程,用户只需要继承 Thread ,覆盖 Thread 中的 run 方法,父类 Thread中的 run 方法没有抛出异常,那么子类也不能抛出异常,最后采用 start 启动线程即可
/*
	在java语言中实现多线程的第一种方式:

		第一步:继承java.lang.Thread;
		第二步:重写run方法.
	
	三个知识点:
		如何定义线程?
		如何创建线程?
		如何启动线程?
*/
public class ThreadTest02
{
	public static void main(String[] args){
		
		//创建线程
		Thread t = new Processor();

		//启动
		t.start(); //这段代码执行瞬间结束。告诉JVM再分配一个新的栈给t线程.
					//run不需要程序员手动调用,系统线程启动之后自动调用run方法.
		
		//t.run(); //这是普通方法调用,这样做程序只有一个线程,run方法结束之后,下面程序才能继续执行。

		//这段代码在主线程中运行.
		for(int i=0;i<10;i++){
			System.out.println("main-->" + i);
		}

		//有了多线程之后,main方法结束只是主线程栈中没有方法栈帧了。
		//但是其他线程或者其他栈中还有栈帧。
		//main方法结束,程序可能还在运行。
			
	}
}

//定义一个线程
class Processor extends Thread
{
	//重写run方法
	public void run(){
		for(int i=0;i<30;i++){
			System.out.println("run-->" + i);
		}
	}
}

2.2实现Runable接口

其实 Thread 对象本身就实现了 Runnable 接口,但一般建议直接使用 Runnable 接口来写多线程程序,因为接口会比类带来更多的好处。
/*
	java中实现线程的第二种方式:
		第一步:写一个类实现java.lang.Runnable;接口
		第二步:实现run方法.
*/
public class ThreadTest03
{
	public static void main(String[] args){
		
		//创建线程
		Thread t = new Thread(new Processor1());

		//启动
		t.start();
	}
}

//这种方式是推荐的。因为一个类实现接口之外保留了类的继承。
class Processor1 implements Runnable {
	public void run(){
		for(int i=0;i<10;i++){
			System.out.println("run-->"+i);
		}
	}
}

三:线程的生命周期

线程的生命周期存在五个状态:新建、就绪、运行、阻塞、死亡

四:线程的调度和控制

通常我们的计算机只有一个 CPU CP U 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片 ,也就是 使用权 ,才可以执行指令。在单 CPU 的机器上线程不是并行运行的,只有 在多个 CPU 上线程才可以并行运行。 Java 虚拟机要负责线程的调度,取得 CPU 的使用权 目前有两种调度模型: 分时调度模型 抢占式调度模型 Java 使用抢占式调度模型。
        1.分时调度模型 :所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片。
        2.抢占式调度模型 :优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个, 优先级高的线程获取的 CPU 时间片相对多一些

4.1线程优先级


线 程 优 先 级 主 要 分 三 种 :
        MAX_PRIORITY( 最 高 级 )
        MIN_PRIORITY (最低级)
        NORM_PRIORITY(标准 ) 默认

/*
	线程优先级高的获取的CPU时间片相对多一些。

	优先级:1-10
	最低 1
	最高 10
	默认 5
*/
public class ThreadTest05
{
	public static void main(String[] args){
		
		System.out.println(Thread.MAX_PRIORITY); //10
		System.out.println(Thread.MIN_PRIORITY); //1
		System.out.println(Thread.NORM_PRIORITY); //5

		Thread t1 = new Processor3();
		t1.setName("t1");


		Thread t2 = new Processor3();
		t2.setName("t2");
		
		System.out.println(t1.getPriority()); //5
		System.out.println(t2.getPriority()); //5

		//设置优先级
		t1.setPriority(5);
		t2.setPriority(6);

		//启动线程
		t1.start();
		t2.start();



	}
}

/**
 *
 */
class Processor3 extends Thread
{
	public void run(){
		for(int i=0;i<50;i++){
			System.out.println(Thread.currentThread().getName()+"--->" + i);
		}
	}
}
从以上输出结果应该看可以看出,优先级高的线程 会得到的 CPU 时间多一些,优先 执行完成.即优先级越高的线程抢占到cup时间片的概率更高,所以执行的时候机会更多,更率先完成任务。

4.2Thread.sleep

sleep 设置休眠的时间 , 单位毫秒,当一个线程遇到 sleep 的时候,就会睡眠, 进入到阻塞状态 , 放弃 CPU ,腾出 cpu 时间片,给其他线程用,所以在开发中通常我们会这样做,使其他 的线程能够取得 CPU 时间片,当睡眠时间到达了,线程会进入 可运行状态 ,得到 CPU 时间 片继续执行,如果线程在睡眠状态被中断了,将会 抛出 IterruptedException异常
package com.xxx.thread.chapter09;

/*
	1.Thread.sleep(毫秒);
	2.sleep方法是一个静态方法.
	3.该方法的作用:阻塞当前线程.腾出CPU,让给其他线程。
*/
public class ThreadTest06
{
	public static void main(String[] args) throws InterruptedException{
		
		Thread t1 = new Processor4();
		t1.setName("t1");
		t1.start();
		
		//阻塞主线程
		for(int i=0;i<10;i++){
			System.out.println(Thread.currentThread().getName()+"--->"+i);
			Thread.sleep(500);
		}
	}
}

class Processor4 extends Thread
{
	//Thread中的run方法不抛出异常,所以重写run方法之后,在run方法的声明位置上不能使用throws
	//所以run方法中的异常只能try...catch...
	//方法重写:返回值 方法名 方法参数必须一致,子类重写方法的访问权限要比父类的大,
	//子类不能抛出比父类重写方法范围更大的异常和不能抛出父类重写方法没有的异常
	public void run(){
		
		for(int i=0;i<10;i++){
			System.out.println(Thread.currentThread().getName()+"--->"+i);
			try{
				Thread.sleep(1000); //让当前线程阻塞1S。
			}catch(InterruptedException e){
				e.printStackTrace();
			}
			
		}
	}

}

4.3Thread.yield

它与 sleep() 类似,只是不能由用户指定暂停多长时间,并且 yield() 方法只能让 同优先级 的线程有执行的机会.
原理:让当前线程放弃当前抢夺到的时间片,然后回到就绪状态,重新去抢夺时间片,如果在就绪状态中依旧是当前线程抢夺到了时间片,那么就依旧是当前线程执行。
/*
	Thread.yield();
	
	1.该方法是一个静态方法.

	2.作用:给同一个优先级的线程让位。但是让位时间不固定。

	3.和sleep方法相同,就是yield时间不固定。
*/
public class ThreadTest10
{

	public static void main(String[] args){

		Thread t = new Processor8();

		t.setName("t");

		t.start();

		//主线程中
		for(int i=0;i<100;i++){
			System.out.println(Thread.currentThread().getName()+"-->"+i);
		}
	}
}

class Processor8 extends Thread
{
	public void run(){

		for(int i=0;i<100;i++){
			System.out.println(Thread.currentThread().getName()+"-->"+i);
			if(i%20==0){
				Thread.yield();
			}

		}
	}
}

4.4t.join(成员方法)

当前线程可以调用另一个线程的 join 方法,调用后 当前线程会被阻塞不再执行 ,直到被调
用的线程执行完毕,当前线程才会执行
package com.xxx.thread.chapter09;

/*
	线程的合并
*/
public class ThreadTest11
{
	public static void main(String[] args) throws Exception{
		
		Thread t = new Thread(new Processor9());

		t.setName("t");

		t.start();
		
		//合并线程
		t.join(); //t和主线程合并. 单线程的程序.

		//主线程
		for(int i=0;i<10;i++){
			System.out.println(Thread.currentThread().getName()+"-->"+i);
		}


	}
}


class Processor9 implements Runnable
{
	public void run(){
		
		for(int i=0;i<5;i++){

			try{
				Thread.sleep(1000);
			}catch(InterruptedException e){}
			
			System.out.println(Thread.currentThread().getName()+"-->"+i);
		}

	}
}

4.5t.interrupt(中断)

如果我们的线程正在睡眠,可以采用 interrupt 进行中断
/*
	某线程正在休眠,如果打断它的休眠.

	以下方式依靠的是异常处理机制。
*/
public class ThreadTest08
{
	public static void main(String[] args) throws Exception{
		
		//需求:启动线程,5S之后打断线程的休眠.
		Thread t = new Thread(new Processor6());

		//起名
		t.setName("t");

		//启动
		t.start();

		//5S之后
		Thread.sleep(5000);

		//打断t的休眠.
		t.interrupt();

	}
}


class Processor6 implements Runnable
{
	public void run(){

		try{
			Thread.sleep(100000000000L);

			System.out.println("HelloWorld!");

		}catch(InterruptedException e){
			//e.printStackTrace();
		}		
		
		for(int i=0;i<10;i++){
			System.out.println(Thread.currentThread().getName()+"-->"+i);
		}

	}
}

4.6如何正确的停止一个线程

通常定义一个标记,来判断标记的状态停止线程的执行

/*
	如何正确的更好的终止一个正在执行的线程.

	需求:线程启动5S之后终止。
*/
public class ThreadTest09
{
	public static void main(String[] args) throws Exception{

		Processor7 p = new Processor7();
		Thread t = new Thread(p);

		t.setName("t");

		t.start();

		//5S之后终止.
		Thread.sleep(5000);
		
		//终止
		p.run = false;

	}
}

class Processor7 implements Runnable
{
	boolean run = true;

	public void run(){
		
		for(int i=0;i<10;i++){
			//给需要执行的线程的主体代码设置一个开关
			if(run){
				try{Thread.sleep(1000);}catch(Exception e){}
				System.out.println(Thread.currentThread().getName()+"-->" + i);
			}else{
				return;
			}
		}
	}
}

五:线程的同步

a.线程模型分为:异步编程模型和同步编程模型

        异步编程模型:

                线程t1和线程t2,各自执行各自的任务,谁也不用等谁,这种编程模型就叫异步编程模型。

        同步编程模型:

                线程t1和线程t2,执行的时候,需要排队执行,两个线程之间发生了等待关系,这就是同步编程模型。

          异步就是并发,同步就是排队

b.什么条件下数据在多线程并发的环境下会存在安全问题?

  三个条件

  1. 多线程并发
  2. 有共享数据
  3. 共享数据有修改的行为

满足以上三个条件就可能会存在安全问题

c.怎么解决线程安全问题:

线程排队执行,不能并发,用排队执行解决线程安全问题,这种机制称为“线程同步机制”

5.1.为什么需要线程同步

多线程带来的安全问题:

假设没有对线程进行处理,依旧是多线程并发:有两个人同时对一张银行卡进行取钱操作,当第一个人取了钱由于网络波动没有及时更新余额,这时另一个人也对该银行卡执行取钱操作,由于第一个人取了钱余额没有更新,这时候钱总数是不变的,当第二个人取了同样多的钱,之后账户上只会显示变了一份的钱,但实际上却取了两份钱,这就会出现数据错误。

5.2.使用线程同步

线程同步,指某一个时刻,指允许一个线程来访问共享资源,线程同步其实是对对象加锁如果对象中的方法都是同步方法,那么某一时刻只能执行一个方法,采用线程同步解决以上 的问题,我们只要保证线程一操作 s 时,线程 2 不允许操作即可,只有线程一使用完成 s 再让线程二来使用 s 变量

使用线程同步是通过synchronized关键字实现的。

六:synchronized关键字的使用

synchorized有三种写法:

一:同步代码块:灵活

synchorized(线程共享对象){}

二:在实例方法上使用synchorize

       表示共享对象一定是this

       并且同步代码块是整个方法体

  

三:在静态方法上使用synchorized

      表示类锁

      类锁永远只有一把

      就算创建100个对对象,类锁也只有一把

6.1同步代码块

package com.xxx.thread.chapter09;

/*
	以下程序使用线程同步机制保证数据的安全。
*/
public class ThreadTest13
{
	public static void main(String[] args){
		
		//创建一个公共的账户
		Account act = new Account("actno-001",5000.0);

		//创建线程对同一个账户取款
		Thread t1 = new Thread(new Processor11(act));
		Thread t2 = new Thread(new Processor11(act));

		t1.start();

		t2.start();
	}
}

//取款线程
class Processor11 implements Runnable
{
	//账户
	Account act;

	//Constructor
	Processor11(Account act){
		this.act = act;
	}

	public void run(){
		act.withdraw(1000.0);
		System.out.println("取款1000.0成功,余额:" + act.getBalance());
	}
}

//账户
class Account2
{
	private String actno;
	private double balance;

	public Account2(){}
	public Account2(String actno,double balance){
		this.actno = actno;
		this.balance = balance;
	}

	//setter and getter
	public void setActno(String actno){
		this.actno = actno;
	}

	public void setBalance(double balance){
		this.balance = balance;
	}

	public String getActno(){
		return actno;
	}

	public double getBalance(){
		return balance;
	}

	//对外提供一个取款的方法
	public void withdraw(double money){ //对当前账户进行取款操作
		
		//把需要同步的代码,放到同步语句块中.
		/*
			原理:t1线程和t2线程.
			t1线程执行到此处,遇到了synchronized关键字,就会去找this的对象锁,
			如果找到this对象锁,则进入同步语句块中执行程序。当同步语句块中的代码
			执行结束之后,t1线程归还this的对象锁。

			在t1线程执行同步语句块的过程中,如果t2线程也过来执行以下代码,也遇到
			synchronized关键字,所以也去找this的对象锁,但是该对象锁被t1线程持有,
			只能在这等待this对象的归还。

			synchronized后面的这个小括号中传递的数据非常重要,
			这个数据是多线程共享的数据,只有共享才能达多线程排队

			java语言都要一把锁,其实这把锁就是一个标记
			100个对象100把锁



		*/

		synchronized(this){

			double after = balance - money;
		
			//延迟
			try{Thread.sleep(1000);}catch(Exception e){}
			

			//更新
			this.setBalance(after);
		}
		
	}
}

6.2在实例方法上使用synchorized和在静态方法上使用synchronized

package com.xxx.thread.chapter09;

/*
	以下程序使用线程同步机制保证数据的安全。
*/
public class ThreadTest14
{
	public static void main(String[] args){
		
		//创建一个公共的账户
		Account3 act = new Account3("actno-001",5000.0);

		//创建线程对同一个账户取款
		Thread t1 = new Thread(new Processor12(act));
		Thread t2 = new Thread(new Processor12(act));

		t1.start();

		t2.start();
	}
}

//取款线程
class Processor12 implements Runnable
{
	//账户
	Account3 act;

	//Constructor
	Processor12(Account3 act){
		this.act = act;
	}

	public void run(){
		act.withdraw(1000.0);
		System.out.println("取款1000.0成功,余额:" + act.getBalance());
	}
}

//账户
class Account3
{
	private String actno;
	private double balance;

	public Account3(){}
	public Account3(String actno,double balance){
		this.actno = actno;
		this.balance = balance;
	}

	//setter and getter
	public void setActno(String actno){
		this.actno = actno;
	}

	public void setBalance(double balance){
		this.balance = balance;
	}

	public String getActno(){
		return actno;
	}

	public double getBalance(){
		return balance;
	}

	//对外提供一个取款的方法
	
	//synchronized关键字添加到成员方法上,线程拿走的默认是this的对象锁。
	//synchorized(this){}
	//synchorized出现在方法上,表示整个方法需要同步,可能会无故扩大程序同步的范围
	public synchronized void withdraw(double money){ //对当前账户进行取款操作
		
		//把需要同步的代码,放到同步语句块中.
		/*
			原理:t1线程和t2线程.
			t1线程执行到此处,遇到了synchronized关键字,就会去找this的对象锁,
			如果找到this对象锁,则进入同步语句块中执行程序。当同步语句块中的代码
			执行结束之后,t1线程归还this的对象锁。

			在t1线程执行同步语句块的过程中,如果t2线程也过来执行以下代码,也遇到
			synchronized关键字,所以也去找this的对象锁,但是该对象锁被t1线程持有,
			只能在这等待this对象的归还。
		*/

		double after = balance - money;
	
		//延迟
		try{Thread.sleep(1000);}catch(Exception e){}
		

		//更新
		this.setBalance(after);

	}

}

6.3面试题:深入synchronized

1.有无synchronized的代码执行顺序:
        当执行没有添加synchronized的关键字的方法的时候不用考虑是否相同的数据对象是否被加锁了,不能执行需要等待的问题。
package com.xxx.thread.chapter09;

/*
	面试题
*/
public class ThreadTest15
{
	public static void main(String[] args) throws Exception{

		MyClass mc = new MyClass();

		Processor13 p = new Processor13(mc);

		Thread t1 = new Thread(p);
		t1.setName("t1");
		Thread t2 = new Thread(p);
		t2.setName("t2");

		//启动线程
		t1.start();

		//延迟(保证t1线程先启动,并执行run)
		Thread.sleep(1000);

		t2.start();

	}
}

class Processor13 implements Runnable
{
	MyClass mc;

	Processor13(MyClass mc){
		this.mc = mc;
	}

	public void run(){
		
		if(Thread.currentThread().getName().equals("t1")){
			mc.m1();
		}
		
		if(Thread.currentThread().getName().equals("t2")){
			mc.m2();
		}

	}
}

class MyClass
{
	public synchronized void m1(){

		//休眠
		try{
			Thread.sleep(10000);
		}catch(Exception e){}
		
		System.out.println("m1....");
	}
	
	/*
	//m2方法的执行不需要等m1的结束,因为m2方法上没有synchronized
	public void m2(){
		System.out.println("m2....");
	}
	*/
	
	//m2方法会等m1方法结束,t1,t2共享同一个mc,并且m1和m2方法上都有synchronized
	public synchronized void m2(){
		System.out.println("m2....");
	}
}

2.是否共享一个相同的数据对象

        当synchronized(共享数据对象){},其中的“共享数据对象”不相同是不用考虑锁得问题,因为不是相同的数据对象。

package com.xxx.thread.chapter09;

/*
	面试题:
		synchronized(共享数据对象){}
*/
public class ThreadTest16
{
	public static void main(String[] args) throws Exception{

		MyClass1 mc1 = new MyClass1();
		MyClass1 mc2 = new MyClass1();

		Processor14 p1 = new Processor14(mc1);
		Processor14 p2 = new Processor14(mc2);

		Thread t1 = new Thread(p1);
		t1.setName("t1");
		Thread t2 = new Thread(p2);
		t2.setName("t2");

		//启动线程
		t1.start();

		//延迟(保证t1线程先启动,并执行run)
		Thread.sleep(1000);

		t2.start();

	}
}

class Processor14 implements Runnable
{
	MyClass1 mc;

	Processor14(MyClass1 mc){
		this.mc = mc;
	}

	public void run(){
		
		if(Thread.currentThread().getName().equals("t1")){
			mc.m1();
		}
		
		if(Thread.currentThread().getName().equals("t2")){
			mc.m2();
		}

	}
}

class MyClass1
{
	public synchronized void m1(){

		//休眠
		try{
			Thread.sleep(10000);
		}catch(Exception e){}
		
		System.out.println("m1....");
	}
	
	
	
	//m2方法不会等m1方法结束,t1,t2不共享同一个mc(对象)
	public synchronized void m2(){
		System.out.println("m2....");
	}
}

3.区分synchronized在静态方法上和实例方法上的区别

        对象锁可以有很多个,但是类锁之后一个,如果synchronized关键字是添加在静态方法上,则说名这是一个类锁,因为静态方法需要类来执行。

        当一个类被锁住,说明后面创建的任何实例对象对不能再用该类了,除非类锁的相关代码执行结束,类锁被放开。

package com.xxx.thread.chapter09;

/*
	面试题:
		类锁,类只有一个,所以锁是类级别的,只有一个.

*/
public class ThreadTest17
{
	public static void main(String[] args) throws Exception{
		
		Thread t1 = new Thread(new Processor17());
		Thread t2 = new Thread(new Processor17());

		t1.setName("t1");
		t2.setName("t2");

		t1.start();
		
		//延迟,保证t1先执行
		Thread.sleep(1000);

		t2.start();

	}
}

class Processor17 implements Runnable
{
	public void run(){
		
		if("t1".equals(Thread.currentThread().getName())){
			MyClass17.m1();
		}

		if("t2".equals(Thread.currentThread().getName())){
			MyClass17.m2();
		}
	}
}

class MyClass17
{
	//synchronized添加到静态方法上,线程执行此方法的时候会找类锁。
	public synchronized static void m1(){
		
		try{Thread.sleep(10000);}catch(Exception e){}

		System.out.println("m1....");
	}

	//不会等m1结束,因为该方法没有被synchronized修饰
	/*
	public static void m2(){
		System.out.println("m2...");
	}
	*/

	//m2方法等m1结束之后才能执行,该方法有synchronized
	//线程执行该代码需要“类锁”,而类锁只有一个。
	public synchronized static void m2(){
		System.out.println("m2...");
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值