黑马程序员_【总结】_多线程知识梳理1

多线程知识梳理1


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

---------------------------------------------------------------------------------------------------------------------------------------------
1、线程需要  extends Thread   OR    implements Runnable  
2、implements 方式线程更优 避免了单继承 适合操作共享数据
3、线程必须覆盖 run 方法 且必须是 public
4、线程获取 执行权,执行时间 完全随机。
5、Static Thread currentThread() 当前线程 引用 标准 this
6、功能代码,都存放在 run 方法当中
7、同步代码块 让多条操作共享数据的语句只让一个线程来执行,完成前其他线程不可参与
8、同步的前提: 1、必须2个或以上的线程 
      2、必须是多线程使用同一把锁
9、同步代码块: 优点:解决了多线程的安全问题
弊端:多个线需要判断锁,较为消耗资源
10、如何使用同步代码快?
1、明确那些代码是多线程运行代码
2、明确共享数据
凡是是出现2次的变量,中间加一句睡觉,必能看见问题。
3、明确多线程运行代码中那些语句是操作共享数据的。
11、同步代码块 锁 当前对象引用:this。
12、非静态同步代码方法、函数 锁 当前对象引用:this。
13、静态同步代码方法、函数 锁: 当前类字节码文件对象  类.class
14、不论是静态还是非静态,不论是组合还是非组合,锁最好选定为该类中唯一的 
15、死锁  通常是同步代码中签到同步代码,并别持有的锁,不相同。
16、懒汉式-- 延迟加载--单列设计模式
特点:延迟加载、多线程时容易出安全问题、
同步代码块或者同步方法可解决安全问题,但效率不高
同步代码块加上双重if判断,可解决效率问题
--------------------------------------------------------------------
【0】
线程常识

进程:在日常电脑应用中,每一个应用程序至少有一个进程。
这个进程中至少包含了一个或者多个独立的控制单元。

线程:线程就是这个独立的每一个单元。

线程并不难理解,日常生活中非常普及,
比如用下载器下载数据,使用了多个线程,同时下载
比如QQ聊天,同时打开多个窗口,和多个伙伴儿聊天。

线程的存在和由来,都是为了提高效率。
main 方法是一个主线程,假设只用这一个线程,完成上面的小例子,就会非常的消耗时间。
【1】
【1-1】
创建一个线程 extends Thread

1.继承 Thread----   
实现Runnable 的线程更适合资源共享。
2.必须覆盖 run 方法。且必须是public
run方法其实就是功能代码的躯壳。
3.构造方法可以通过super 指向父类。
4.启动线程是 start 方法,直接使用run 就不是线程的执行,而是方法的调用了。
至于 start 肯定是底层调用了某系东西,作为新手就不再深入了。
-------------------------------------------------
public class ThreadTest {
	public static void main(String[] args) {
		newThread td1=new newThread("线程A");
		td1.start();

		new Thread(new newThread("-----------b")).start();
	}
}
class newThread extends Thread
{
	newThread(String name){
		super(name);
	}
	public void run(){
		for(int i = 0 ;i<50 ; i++){
			System.out.println(this.getName());
		}
	}
}
-------------------------------------------------
1、
根据多次运行,知道 
每次运行结果不一样,
这里涉及到 CPU机制问题。CPU在同一时刻,只能执行一个程序。
并迅速的在不同程序间做着切换,由于速度太快,而给人以同时运行的效果。

线程的随机性:
线程的运行,仿佛就是相互争夺 CPU的执行权利。 谁抢到执行谁,至于执行时间,看CPU。


在Java中,所有的线程都是同时启动的, 但是什么时候执行那个个,执行多久,都是CPU来决定
在Java中,每次程序的运行至少启动了2个线程,1是main主线程,2是垃圾回收线程。
在Java中,只要前台有一个线程在运行,整个Java程序进程不会消失,可以设置一个后台程序,
即使Java进程小时,后台线程依然能够运行。
2、
线程的几个方法
Static Thread currentThread() 同this  当前线程的引用。

通常 定义好一个线程后,使用内部类方式书写:
new Thread(new threadName()).start()

setName() 更改线程名
getName() 获取线程名
start() 启动
sleep() 睡眠 sleep() 用处比较广泛
Thread.sleep(6000); 设置等待6000毫秒。然后干什么。
wait() 等待
notify() 唤醒
stop() 停止

【1-2】
// 模拟卖票小程序
//继承 Thread
class MyThreadT
{
	public static void main(String[] args) 
	{
		TicketDemo t1 = new TicketDemo();
		TicketDemo t2 = new TicketDemo();
		TicketDemo t3 = new TicketDemo();
		t2.start();
		t3.start();
		t1.start();
	}
}
class TicketDemo extends Thread
{
	private int  tick = 100;
	public void run(){
		while(true){
			if(tick>0){
				System.out.println(this.getName()+"..."+tick--);
			}
		}
	}
}


//2、实现	Runnable
class MyThreadR
{
	public static void main(String[] args) 
	{
		TicketDemo t = new TicketDemo();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
	}
}
class TicketDemo implements Runnable
{
	private int tick = 100;
	public void run(){
		while(true){
			if(tick>0){
				System.out.println(Thread.currentThread().getName()+"..."+tick--);
			}
		}
	}
}
相同的代码,只是 一个通过继承,一个是通过实现 来达到多线程的作用。

发现
继承 Thread 的线程  每一条线程都在从1到100的卖票,也就是300张票。数据
实现 Runnable 的线程  则是才卖100张票

结论:
1、避免了单继承的局限性
2、更适合多个线程去处理同一个资源。
建议使用 实现方式的 线程

区别就是 功能代码都存放在对应的run方法中。

【2】
线程的5个状态---线程的安全问题。

1、创建状态 2、运行状态 3、阻塞状态 4、消亡状态 5、冻结状态
当多个线程被创建时,在内存中会有这样一个现象:
1、都在具备运行资格,但没有执行权
2、其中一个有了执行权利,进入执行,可能会:而其他现在发生着类似情况。
1、直接运行完毕,也就是结束了。
2、运行一半 sleep
3、wait了 
当属于2,3时候,当 sleep,世间结束,当 wait被 notify() 唤醒后:
1、继续获取到执行权利执行
2、返回到等待状态,拥有资格没有权利
4、运行结束或者被 stop  线程消亡
(配图)



就上述 案例中,
	public void run(){
		while(true){
			if(tick>0){
				System.out.println(Thread.currentThread().getName()+"..."+tick--);
			}
		}
	}
由于 线程的执行权是争取的,而执行之间是CPU 决定的。逻辑上完全可能出现下来情况:
当 tick=1的时候:
1线程  判断 tick>0  进入   然后卧倒了。 这时2 线程进入 tick>0   然后又卧倒了。
然后3线程 同样进来了, 这时候也卧倒了,   
三个线程都挂这里后,1线程起来了,这时候不会在判断而直接打印了当前的tick--   也就是0
接着2 线程 起来了,也不会在判断而 打直接印了当前的tick--  也就是-1了
接着3 线程 起来了,同样不会在判断而 打直接印了当前的tick--  也就是-2了

打印出0 的时候 程序已经就出现问题了。

if(tick>0){
		try{Thread.sleep(10);}catch(Exception e){}
		System.out.println(Thread.currentThread().getName()+"..."+tick--);
	}
只需要在打印前,让该线程睡上10毫秒,即可达到上述状况

也就是说,涉及到了 线程的安全问题。

问题的产生在于:当一个线程对多条语句只操作一部分的时候,另一个线程参与执行后,导致共享数据的错乱
解决办法就是: 对多条操作共享数据的语句,只让一个线程来执行完成,完成前其他线程不可参与。


Java 对于多线程的安全问题提供了对应的专业技术:
【3】
同步代码块:

synchronized(对象){
需要被同步的代码。
}

那么上面的代码就更改为:
class TicketDemo implements Runnable
{
	private int tick = 100;
	public void run(){
		while(true){
			synchronized(this)
			{
				if(tick>0){
				try{Thread.sleep(10);}catch(Exception e){}
				System.out.println(Thread.currentThread().getName()+"..."+tick--);
				}
			}
		}
	}
}
这时候,既时写Thread.sleep(10000) 睡上一天,也不会在出现问题了。

对象如同锁,持有锁的线程可以在同步中执行,
没有持有锁,即使线程获取到了CPU执行权,因为没有锁,也无法进去
联想一下 老师 ,火车的  卫生间  例子。
里面的人不出来,外面的人是进不去的。 锁,生动的解释了这一现象。

同步代码 有2个前提:
1、必须2个或以上的线程
2、必须是多个线程使用同一把锁

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

优点:解决了多线程的安全问题
弊端:多个线需要判断锁,较为消耗资源

如何使用同步代码快?
1、明确那些代码是多线程运行代码
2、明确共享数据
凡是是出现2次的变量,中间加一句睡觉,必然能看见问题,也就是说需要同步在一起 如下:
3、明确多线程运行代码中那些语句是操作共享数据的。

同步函数、同步方法
class bank 
{
	public static void main(String[] args) 
	{
		Cus b = new Cus();
		new Thread(b).start();
		new Thread(b).start();
	}
}
class BankT
{
	private int sum ;
	public synchronized void add(int n)
	{
		sum = sum+n;
		try{Thread.sleep(10);}catch(Exception e){}
		System.out.println(sum);
	}
}
class Cus implements Runnable
{
	private BankT b = new BankT();
	public void run(){	
		for(int i  = 0 ;i<3 ;i++){
			b.add(100);
		}
	}
}
相比同步代码块而言,同步方法,更简洁一些。持有的锁为 当前对象的引用。

锁 具有唯一性,那么 使用的时候,就使用当前引用,作为锁,避免出现问题

同步方法的锁是 this
同步代码块的锁 也用this ,

当同步方法和同步代码块组合应用的时候,才不会出现问题
出现问题肯定是2个前提。

当同步方法为静态的时候,锁不再是this的,   静态方法中是没有this的。
那么锁是谁呢?

因为有 static 进内存的时候是没有当前对象,所以没有this
但是一定有当前类对应的字节码文件对象
该对象的类型是  Class
class mair
{
	public static void main(String[] args) 
	{
		TicketDemo t = new TicketDemo();
		new Thread(t).start();
		try{Thread.sleep(10);}catch(Exception e){}
		t.flag = false;
		new Thread(t).start();
	}
}
class TicketDemo implements Runnable
{
	//private int tick = 100;  【A】对应非静态同步方法
	private static int tick = 100;
	boolean flag= true;
	public void run(){
		if(flag){
			//同步代码块
			while(true){
				//synchronized(this) //【C】对应A  
				synchronized(TicketDemo.class)
				{
					if(tick>0){
					try{Thread.sleep(10);}catch(Exception e){}
					System.out.println(Thread.currentThread().getName()+"...code"+tick--);
					}
				}
			}
		}else{
			//同步方法
			while(true){
				this.show();
			}
		}		
	}
	//public synchronized void show() //【B】对应A  [abc 为非静态同步方法组合方式]  
	public static synchronized void show()
	{
		if(tick>0){
			try{Thread.sleep(10);}catch(Exception e){}
			System.out.println(Thread.currentThread().getName()+"...show"+tick--);
		}
	}
}
【重点】总结:
当 静态同步方法和同步代码块组合使用的时候,锁 是 该类字节码对象
当 非静态同步方法和同步代码块组合使用时候,锁 是 当前对象引用 this


随便提一下 经常面试的【单列设计模式】题目

问;懒汉式和恶汉式有什么不同
懒汉式特点是 对象的延迟加载
问:懒汉式,有没有问题
有,多线程访问时容易出现安全问题。
问:怎么解决,
可以加同步来解决,
加同步代码块,或者同步方法 可以解决安全问题,但是效率比较低效
通过双重判断,结合同步代码块 ,可以解决效率问题

问:懒汉式的锁是那个
该类的字节码文件对象。

【重点】//懒汉式 常考  也称为 延迟加载的单例设计模式
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;
	}
}
假设123,线程,1进去,判断 null ,进入同步代码块,卧倒了。
2进去,同样 null ,但是 2没有锁,进不去,
1醒了创建实例对象 结束,
2醒了,进入锁,再判断, 不为 null ,出去了,
3进入,一判断,不为null */
恶汉式,常用方式
class Single{
	private static final Single s = new Single();
	private Single(){};
	public static Single getInstance(){
		return s;
	}
}
死锁
同步中嵌套同步
//实现一个死锁: 同步中嵌套锁
class ThreadS implements Runnable
{
	private boolean falg;
	ThreadS(boolean falg){
		this.falg = falg;
	}
	public  void run(){
		if(falg){
			synchronized(mySuo.A){
				System.out.println(" if......A");
				synchronized(mySuo.B){
					System.out.println(" if......BBB");
				}
			}
		}
		else{
			synchronized(mySuo.B){
				System.out.println(" else.....BBB");
				synchronized(mySuo.A){
					System.out.println(" else.....AAA");
				}
			}
		}
	}
}

class  mySuo // 锁
{
	static Object A = new Object();//静态修饰的目的是方便调用
	static Object B = new Object();
}
class sisuo
{
	public static void main(String [] args){
		new Thread(new ThreadS(true)).start();
		new Thread(new ThreadS(false)).start();
	}
}
//end






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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值