黑马程序员——第十一天(Java面向对象-多线程一)



---------------------- android培训java培训、期待与您交流! ----------------------

一,多线程(概述)

进程:就是正在进行中的程序

在某一时刻,CPU只能执行某一个应用程序,CPU其实在做着快速的切换,才导致看到的现象,各个程序在同时的执行

一个进程当中,有可能会出现多条执行路径。

线程是进程中的内容,每一个应用程序它们里面至少都有一个线程,因为线程的特点:

它的程序中的控制单元 或者 叫 执行路径


进程:是一个正在执行中的程序

每一个进程执行都有一个执行的顺序,该顺序就是一个执行路径 或者 叫一个控制单元

其实进程就是定义空间 ,用于标识空间的,用于封装里面的控制单元

线程:就是进程中的一个独立的控制单元

线程在控制着进程的执行

一个进程中,至少有一个线程(控制单元)

JVM启动时会有一个进程 java.exe

该进程中至少有一个线程在负责Java程序的执行

而且这个线程运行的代码存在于main方法中

执行java程序的线程,该线程称之为主线程


javac.exe  编译进程

java.exe  运行进程


扩展:其实更袭击说明JVM

虚拟机启动的时候,它就是多线程

主线程在调用方法的同时,垃圾被垃圾回收机制给回收了

所以,虚拟机至少有两个线程,一个主线程,一个负责垃圾回收的线程


有多条执行路径的情况下,我们就称这个程序为多线程程序


二.多线程(创建线程-继承Thread类)

1.如何在自定义的代码中,自定义一个线程呢 ?

线程真正创建是系统(Windows或者Linux....)来创建

Thread 用来描述控制单元 的对象

通过对API的查找,Java已经提供了对线程这类事物的描述,就是Thread类

创建线程的第一种方式,继承Thread类

步骤:

1.定义类继承Thread

2.复写Thread类中的run方法

3.调用线程的start()方法  该方法有两个作用,

启动线程   

调用run方法

发现运行结果每一次都不同:

因为多个线程都在获取CPU的执行使用权

CPU执行到谁  谁就运行

明确一点,在某一时刻,只能有一个程序在运行(多核除外)

CPU在做着快速的切换,已达到看上去是同时运行的效果

我们可以形象把对线程的运行  形容为在互相抢夺CPU的资源(使用权)


这就是多线程的一个特性,随机性。谁抢到,谁执行,至于多长时间,CPU说的算


创建好一个对象  就创建好一个线程 

 

多个线程在抢劫CPU资源 谁抢到CPU的执行权  谁执行


双核以后  内存(临时处理空间)就是瓶颈


三.多线程(创建线程-run和start特点)

为什么要覆盖run方法呢?

目的:将自定义的代码存储在run方法中,让线程运行。

Thread类用于描述线程

该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法


也就是说run方法,Thread类中的run方法,用于存储线程要运行的代码。

主线程要运行的代码要存在main方法里 


Thread e = new Thread();//这已经创建线程


start() 开启线程并执行该线程的run方法

run() 仅仅是对象调用方法,而线程创建了,并没有运行。


四.多线程(线程练习)

/*
练习:
创建两个线程,和主线程交替运行。
*/

public class ThreadTest2 {
	public static void main(String[] args) {
		Test2 test1 = new Test2("one ");
		Test2 test2 = new Test2("two ");
		test1.start();
		test2.start();
		
		for (int i = 0; i < 60; i++) {
			System.out.println("main run "+ i);
		}
	}
}
class Test2 extends Thread{
	private String name ;
	public Test2(String name ) {
		this.name = name ;
	}
	public void run(){
		for (int i = 0; i < 60; i++) {
			System.out.println(name + "Test2 run" + i);
		}
	}
}


五.多线程(线程运行状态)



Thread这个类 这个类的对象会不会调用底层?

Java会创建线程吗 ?

不会,依赖于Windows帮你创建,


没有执行资格:     冻结状态

有执行资格:      临时状态

既有资格,又有运行权:运行状态


六.多线程(获取线程对象以及名称)

原来线程都有自己默认的名称。

Thread-编号 该编号从0开始

static Thread currentThread() 返回对当前正在执行的线程对象的引用

getName():获取线程名称

设置线程名称:setName或者构造函数


Thread.currentThread() == this  ---------->  true

局部的变量在每一个线程区域当中都是独立的一份


七.多线程(售票的例子)

start()从创建状态 到了运行状态 

已经在运行的程序是不需要再次开启的  


八.多线程(创建线程-实现Runnable接口)

创建线程的第二种方式:实现Runnable接口

步骤:
1.定义类实现Runnable接口

2.覆盖Runnbale接口中的run方法

将线程要运行的代码存放在该run方法中

3.通过Thread类建立线程对象

4.将Runnbale接口的子类对象作为实际参数传递给Thread类的构造函数

为什么要将Runnable接口的子类对象传递给Thread的构造函数?

因为,自定义的run方法所属的对象是Runnable接口的子类对象

所以要让线程去执行指定对象的run方法 就必须要明确该run方法所属的对象

5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法



实现方式和继承方式有什么区别呢 ?

实现方式好处:避免了单继承的局限性

在定义线程时,建议使用实现方式.


两种方式区别:

继承Thread:线程代码存放在Thread子类run方法中

实现Runnbale:线程代码存放在接口的子类的run方法 。


Runnable接口的定义 其实 就是在确立线程要运行代码所存放的位置  就是run方法  


九.多线程(多线程的安全问题)

Thread.sleep(time)  会抛出InterruptedException    中段异常

需要进行try catch 进行捕捉

为什么不能抛出呢 ?

run方法是复写了接口Runnable 的方法

Runable接口中的run方法,没有抛出异常  其实现类也不能抛出异常

/*
	通过分析,发现打印出了0 , -1 ,-2等错票
	多线程的运行出现了安全问题

	写多线程的时候一定要小心安全问题

问题的原因:
	当多条语句在操作同一个线程共享数据时,一个线程对多条语句,只执行了一部分,还没有执行完,
	另一个线程参与进来执行,导致了共享数据的错误。
	
	如果说一个线程进来  把多条语句执行完  再让下一个线程进来  就没有问题了  
	
解决办法:
	对多条操作共享数据的语句,只能让某一时间段 让一个线程执行完,在执行过程中,其他线程不可以参与执行
	
Java对多线程的安全问题提供了专业的解决方式
就是同步代码块 
synchronized(对象){
	需要被同步的代码
}
哪些代码需要同步?怎么判定?
就是需要确定哪些代码是在操作共享数据

*/

class Ticket1 implements Runnable{
	private int tick = 100 ;//多个线程共享的数据
	@Override
	public /*synchronized*/ void run() {
		A:
		while(true){
			synchronized(Ticket.class){
				if(tick > 0){
					try {
						Thread.sleep(10); 
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + " run	" +  tick--);
				}				
				else break A;
			}
		}
	}
}

public class TicketDemo2 {
	public static void main(String[] args) {
		Ticket1 t = new Ticket1();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

十.多线程(多线程同步代码块)

对象如同锁,持有锁的线程,可以在同步中执行

没有持有锁的线程,即使获取了CPU的执行权也进不去,因为没有获取锁


火车上的卫生间----经典。


同步的前提:

1.必须要有两个或者两个以上的线程.

2.必须是多个线程使用同一个锁.


必须保证:同步中只能有一个线程在运行

不能将run方法里的代码全部放到同步当中,那样相当于单线程


有些代码需要同步 ,有些代码不需要同步  


好处:解决了多线程的安全问题。

弊端:多个线程都需要判断锁,较为消耗资源(这个消耗是在允许范围)


十一.多线程(多线程-同步函数)

 /*
	需求:
	银行有一个金库
	有两个储户分别存300元,每次存100,存3次。
	
	目的:该程序是否有安全问题,如果有,如何解决?
如何找问题:
1.明确哪些代码是多线程运行代码
2.明确共享数据
3.明确多线程代码中,哪些语句是操作共享数据的

同步有两种表现形式:
1.同步代码快
2.同步函数
	
*/

class Bank{
	private int sum = 0 ;
	public synchronized void add(int m){
//		synchronized(this){
			sum += m ;
			try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();}
			System.out.println("Sum= " + sum);
//		}
	}
}
class Cus implements Runnable{
	private Bank bank = new Bank();
	@Override
	public void run() {
		for (int i = 0; i < 3; i++) {
				bank.add(100);
		}
	}
}

public class BankDemo {
	public static void main(String[] args) {
		Cus cus = new Cus();
		
		Thread t1 = new Thread(cus);
		Thread t2 = new Thread(cus);
		
		t1.start();
		t2.start();
	}
}

十二.多线程(多线程-同步函数的锁是this)

注意:不要给run方法加上Synchronized,这样相当于单线程

同步函数用的是哪一个锁呢?

函数需要被对象调用,函数都有一个所属对象的引用,就是this

所以同步函数使用的锁是this


加了同步,仍然会出现安全问题?

思考这个问题,至少有一个前提条件不能满足。

同步的两个前提:

1.必须是两个或两个以上的线程

2.必须是多个线程使用的是同一个锁


十三.多线程(多线程-静态同步函数的锁是Class对象)

如果同步函数被静态修饰后,使用是锁是什么呢 ?

通过验证,发现不再是this了,因为静态方法中不可以定义this。

静态的特点:

静态进内存的时候没有对象,由类调用

类进内存的时候,先要封装成一个Class类型的对象(字节码文件对象)


静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象

类名.class  该对象的类型是Class (可以用getClass()来获取某一对象运行的实例)


静态的同步方法,使用的锁是该方法所在累的字节码文件对象, 

类名.class   这个对象在内存中是唯一的

加载内存以后,只加载一次,不再重新加载


十四.多线程(多线程-单例设计模式-懒汉式)

//饿汉式
class Single{
	private Single(){};
	private static final Single SINGLE = new Single();
	public static Single getInstence(){
		return SINGLE;
	}
}

/*  
 懒汉式:
	对象被延迟加载
	懒汉式在多线程访问时,就会产生安全隐患
	懒汉式  函数加了同步 会比较低效
	用双重判断的形式  减少了判断锁的次数  来解决这个问题 稍微提高了一些懒汉式的效率
	所以一般使用饿汉式  
	
	1.懒汉式和饿汉式 有什么不同 ?
		懒汉式的特点在于实例的延迟加载
	2.懒汉式的延迟加载  有没有问题?	
		有,如果多线程访问时,出现安全问题,
	3.怎解决?
		可以加同步来解决,用同步代码快 或者 同步函数都可以  但是稍微有些低效
		用双重判断的方式,能够解决这个问题。
	4.加同步的时候  ,使用的锁是哪一个?
		该类所属的字节码文件对象*/

class Single2{
	private Single2 (){};
	private static Single2 single2 = null;
	public static Single2 getInstence(){
		if(single2 == null){
			synchronized(Single2.class){
				if(single2 == null)
					single2 = new Single2();
			}
		}
		return single2;		
	}
}

十五.多线程(多线程-死锁)

死锁:
通常情况下出现的死锁的情况是:
同步中,嵌套同步,而锁却不同

class Test3 implements Runnable{
	private boolean flag ;
	Object obj = new Object();
	Test3(boolean flag){
		this.flag = flag;
	}
	@Override
	public void run() {
		if(flag){
			while(true){
				synchronized(MyLock.locka){
					System.out.println("if locka");
					synchronized(MyLock.lockb){
						System.out.println("if lockb");
					}
				}
			}
		}else {
			while(true){
				synchronized(MyLock.lockb){
					System.out.println("else lockb");
					synchronized(MyLock.locka){
						System.out.println("else locka");
					}
				}
			}
		}
	}
	
}

class MyLock{
	static Object locka = new Object();
	static Object lockb = new Object();
}
public class DeadLockTest {
	public static void main(String[] args) {
		Thread t1 = new Thread(new Test3(true));
		Thread t2 = new Thread(new Test3(false));
		t1.start();
		t2.start();
		
	}
}

 


----------------------  android培训java培训、期待与您交流! ----------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值