黑马程序员_Java多线程

本文详细介绍Java中线程的概念、创建方式、线程状态及同步机制等核心内容,并提供了丰富的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

---------------------- <a href="http://www.itheima.com"target="blank">ASP.Net+Unity开发</a&gt;、<a href="http://www.itheima.com"target="blank">.Net培训</a>、期待与您交流! --------------------

一、线程概述

在操作系统中,每个运行着的应用程序都至少有一个后台运行程序,叫做进程,它是独占一定内存空间的程序片段。

一个进程当中可能会有多条执行路径(程序从开始执行到结束执行这一途径)。线程就是进程中的一个独立的控制单元。一个进程中至少有一个线程,线程在控制着进程的执行。

在Java语言中,main方法是程序入口,通常称为主线程,其他线程则称为子线程。

注意:程序中控制单元或执行路径每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。

二、在程序中如何定义一个线程

1.继承Thread类方式

实例代码1如下:

<span style="font-family:SimSun;font-size:14px;">package com.demo.thread;
/**
 * Main方法类
 */
public class Demo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		ThreadDemo td=new ThreadDemo();
		td.start();//第三步:调用Tread类中的start()方法,启动线程并调用run()方法
		for(int x=0;x<60;x++){
			System.out.println("demo run"+x);
		}
		
		
		Thread t=new Thread();
		/*
		 * t.run()和t.start()区别:
				t.run();//仅仅是对象调用方法。而线程创建了,但并没有运行
				t.start();//开启线程并执行该线程的run方法
		*/

	}

}


/**
 * 线程类
 * 沿袭父类功能,重写父类run方法。
 */
class ThreadDemo extends Thread{//第一步:定义类继承Thread类
	@Override
	public void run(){//第二步:重写Tread类中的run()方法,目的是为了将自定义的代码存储在run方法中,用于运行。
		for(int x=0;x<60;x++){
			System.out.println("ThreadDemo RUN"+x);
		}
	}
}</span>

2.实现Runnable方式

步骤:
 1.定义类实现Runnable接口
 2.覆盖Runnable接口中的run方法。
 3.通过Thread类创建线程对象。
 4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
 5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

示例代码2如下:

<span style="font-family:SimSun;font-size:14px;">/**
 * 线程类
 * 需求:简单的卖票程序。多个窗口同时买票。
 */
class Ticket implements Runnable{
	private int tick=100;
	Object obj=new Object();
	@Override
	public void run(){
		while(true){
			synchronized (obj) {//同步锁,火车卫生间标准同步
				if(tick >0){
					System.out.println(Thread.currentThread().getName()+"......sale : "+ tick--);
				}
			}
		}
	}

}
/**
 * 运行类
 */
 public class TicketDemo{
	public static void main(String[] args) {
		Ticket t=new Ticket();//创建线程的是Thread类对象或Thread子类对象,而当前对象只是实现了接口,没有继承Thread,所以该对象没有创建线程。
		
		Thread th1=new Thread(t);
		Thread th2=new Thread(t);
		Thread th3=new Thread(t);
		Thread th4=new Thread(t);
		
		th1.start();
		th2.start();
		th3.start();
		th4.start();
	}
}</span>

疑问1:为什么要将Runnable接口的子类对象传递给Thread的构造函数?
     因为,自定义的run方法所属的对象是Runnable接口的子类对象。
     所以要让线程去调用指定对象的run方法,就必须明确该run方法所属对象。
 疑问2:实现接口方式和继承方式有什么区别呢?
 区别一:实现方式避免了单继承的局限性。推荐使用接口方法。
 例子,Student继承Persion类实现Runnable接口,表示Student类中既有Persion的类似行为,又可以写多线程的代码供jvm调用。
     如果Student继承Thread类,和Persion类似的行为必须全部copy一份,因为java不允许多重继承,这样就造成了代码大量冗余,违反了重用原则。
 区别二:
 继承Thread:线程代码存放Thread子类run方法中。
 实现Runnable,线程代码存放在接口的子类的run方法中。

三、线程的状态

共5个状态:创建、运行、阻塞、消亡、冻结(睡眠或等待)

说明:new Thread()方法使得线程处于已创建状态,然后由start()方法使得线程处于运行状态,对于运行着的线程可以使用sleep()方法或wait()方法使得线程处于冻结状态

最后可由notify()方法使得线程具备了执行资格,但不一定有执行权限,处于抢CPU资源时期,此时的该线程的状态称为临时状态或阻塞状态,最后可以使用stop()方法使得线程处于消亡状态,即线程结束运行。

四、线程同步块和同步函数

1.语法说明:

<span style="font-family:SimSun;font-size:14px;">synchronized(对象)
{

	需要被同步的代码,就是线程之间共享数据

}</span>

因为在一个进程中多个线程之间是共享内存数据的,例如上述实例代码2中的下述代码:

<span style="font-family:SimSun;font-size:14px;">synchronized (obj) {//同步锁,火车卫生间标准同步
	if(tick >0){
		System.out.println(Thread.currentThread().getName()+"......sale : "+ tick--);
	}
}</span>
如果4个线程共同执行这段代码,就有可能造成tick的值变为负数,假设此时的tick值为1,由线程1执行到if语句进行判断,此时判断通过,在将要打印输出方法时,CPU资源被线程2抢占,则线程2执行if语句进行判断,又因为线程1判断语句没有执行,所以此时tick的值还是1,那么线程2也通过了判断,在刚要执行输出语句时,同理CPU资源又被线程3抢占,并直接执行输出语句,此时输出值为1,但tick的实际值为0,就在同时线程1有抢占了CPU资源,获得了执行权限,则打印tick值为0,此时tick内存中实际值为-1,最后由线程2打印值为-1,但要注意,此时内存中tick的存储值为-2。这样就导致了数据不同步,票数出现了负数,与现实生活中不符。而使用synchronized同步块就解决了这个问题。

注意: 添加同步的前提是(1).必须要有两个或两个以上的线程。(2).必须是多个线程使用同一个锁。

2.原理

在每个线程执行到同步块内容时,对该同步块加上锁机制,在该线程没有执行结束之前,其他任何线程都无法执行该加锁的同步块内容,即使获得执行权限也只能等待当前线程执行结束后,释放了锁,其他线程才有可能进入同步块内部执行。这就保证了数据安全性。

3.同步块的利与弊

对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。( 火车上的卫生间-----经典)
好处:解决了多个线程数据安全问题。

弊端:多个线程需要判断锁,较为消耗资源

锁说明:

同步函数,就是在函数声明前加synchronized关键字。同步函数用的锁是this
如果同步函数被static关键字修饰后,则它使用的锁是该类的字节码对象。

4.单例模式的懒汉式和饿汉式代码

//饿汉式

class SingleOne {
	private static final SingleOne s = new SingleOne();

	private SingleOne() {
	}

	public static SingleOne getInstance() {
		return s;
	}

}
//懒汉式
class Single{
	private  static Single s=null;
	private Single(){}
	public static  Single getInstance(){

		if(s==null){
			synchronized(Single.class){
				if(s==null){//多线程时存在安全隐患,双重判断能减少锁的判断次数
					s=new Single();		
				}
			}
		}
		return s;
	} 
}


 ---------------------- <a href="http://www.itheima.com"target="blank">ASP.Net+Unity开发</a&gt;、<a href="http://www.itheima.com"target="blank">.Net培训</a>、期待与您交流! ----------------------
详细请查看:<a href="http://www.itheima.com" target="blank">
www.itheima.com</a>
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值