【黑马程序员】多线程

 

 

 

 

          

   一.多线程概述     

一、进程和线程

 进程 正在进行的程序,一个进程可以有多条执行路径,这每一个路径,就称为线程,一个进程至少有一个线程

线程,程序中的控制单元或者执行路径,线程控制着进程的执行

创建线程的目的?为了让线程去执行一些代码。

1. 主线程----vm执行的线程 main线程由jvm开启。一句话总结:主线程负责开启其他线程!

 

2. 垃圾回收线程---更细节说明jvm,jvm启动的 不止一个主线程,还有垃圾回收线程【一般做题慎重考虑】

 

二、多线程存在的意义,

【多个程序同时执行】---Cpu在某一时刻只能执行一个程序,程序的同时执行 是cpu在快速切换,

 

三、线程的创建方式以及注意点总结

1. 有两种方法

方式一:继承Thread类,复写Thread类中的run方法--创建子类对象【就是创建好了一个线程】--对象.start()启动线程,

方法二:继承Runnable接口,实现run方法,通过Thread创建线程,接收Runnble子类对象,,再通过线程对象start方法启动线程

 

2. 为什么要覆盖run方法?

Thread类和Runnable接口中的run方法,用于存储线程运行的代码。子类复写run()

方法就是为了封装自定义线程要执行的代码

【科普:Thread类具有调用底层的方法,控制windows系统创建线程】

3. start()和run()的区别?

【start方法:开启线程并执行该线程的run方法】

【对象.run只是对象调用该方法,线程创建了,并没有执行,run方法只是封装了自定义线程要执行的代码】

 4.Runnable接口和Thread类的区别

① Thread类【线程类】也是继承了Runnable的run方法, run方法的作用,只是存放需要通过线程执行的语句,即只是一个存放功能

② 如果要创建线程,必须通过Thread类或者Thread子类对象启动

 

四、多线程的特性---随机

a. 现象:程序运行结果每一次都不同

是因为多个线程都获取cpu的执行权,cpuu执行到谁,就运行谁,【单核中,某一时刻,只有一个程序在运行】

b. 随机性:谁抢到cpu的执行权,就执行谁,至于执行多长时间,是cpu说了算,

 

五.获取线程对象和名称

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

setName() 设置线程名称。

getName():获取线程名称   Thread.currentThread.getName();

 

.线程的状态

 

 

 

二.多线程示例

 

多线程卖票:

 

 

class Demo2 implements Runnable
{
	private int ticket =30;
	public void run()
	{
		for(;ticket>0;)
		{
			try
			{
				Thread.sleep(100);//定义在for循环之内!
			} 
			catch (InterruptedException e) 
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread()+"  run "+ticket--);
		}
	}
}
public class Ticket2 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Demo2 d=new Demo2();
		
//4个线程 只关联接收一个 run方法和一个成员 ticket,表示共享同一个对象的成员数据
		Thread t1=new Thread(d);
		Thread t2=new Thread(d);
		Thread t3=new Thread(d);
		Thread t4=new Thread(d);
//启动线程	
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

}


 出现了多线程的安全问题

同票:没关联对象,没有共享ticket的值

错票:  0和负数原因

当多条语句在操作同一个线程共享数据ticket时,一个线程对共享的【多条语句】只执行了一部分,就被另一个线程抢走了运行权,导致共享数据错误。

 如何解决?

对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

Java对于多线程的安全问题提供了专业的解决方式,这就引入了线程的同步

 

多线程存钱

 

/*
需求:
银行有一个金库。
有两个储户分别存300员,每次存100,存3次。

目的:该程序是否有安全问题,如果有,如何解决?


如何找问题:
1,明确哪些代码是多线程运行代码。
2,明确共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。
*/
class Banke
{
	//累加存储功能
	private int sum;//多线程共享数据
	public synchronized void add(int i)//多线程共同执行的语句,避免多线程安全
	{
		sum=sum+i;
		System.out.println(Thread.currentThread()+"sum"+sum);
	}
}
class Cus implements Runnable
{
	private Banke b = new Banke();//定义对象操作Banke类的add方法
	public void run()
	{	
		for(int i=0;i<3;i++)
		{
			b.add(100);
		}
	}
}
public class Bank1 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Cus c=new Cus();
		
		Thread t1=new Thread(c);
		Thread t2=new Thread(c);
		t1.start();
		t2.start();
	}
}


 

 

 三、线程的同步

 

1、同步代码块

(1)格式:

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

}

对象:如同锁
持有锁的线程可以在同步中执行。
没有锁的线程即使获取CPU的执行权,也进不去,因为没有获取锁。

(2)同步的前提:
1:必须要有两个或者两个以上的线程。
2:必须是多个线程使用同一个锁。
3:必须保证同步中只能有一个线程在运行。
(3)好处:解决了多线程的安全问题。

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

例如火车上的卫生间经典示例!

卫生间的门关上并锁死,表示里面有人使用,外面的人进不来,只能等里面的人使用完出来才能进入;

卫生间的门开着,表示里面没有人,外面的人可以进来使用,进来一个人把门关上锁死后,外面的人就又进不来了。

 

2、同步函数

同步函数就是在方法前面修饰synchronized关键字的方法。

当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能执行,必须将每个能访问共享资源的方法修饰为同步方法,否则会出错。

 

3.同步函数和同步代码块的区别?

 

      同步函数简化了代码书写,减少了代码的缩进,但是同步代码块更灵活,可以嵌套在方法体中

 

 

4.小知识点:

若在run方法前加同步关键字,则为单线程

同步函数的锁是this

静态同步函数的锁,使用的是Class对象作为锁  类名.class

 

5.单例设计模式----懒汉式

懒汉式 ---延迟加载

懒汉式 ---延迟加载
  class  Single
  {
 		private static Single s=null;
  		private Single(){}
  		public static void getInstance()
 		{
 			if(s==null)
 				{	
 					synchronized(Single.class)
 					{
 						if(s==null)
 						s=new Single();
 					}
 				}
 			return s;//这一步经常忘记
 		}


 

记住懒汉式常问的问题!还有为什么懒汉式叫延迟加载?

1.懒汉式和饿汉式有什么不同:懒汉式 是实例的延迟加载

2.懒汉式有安全隐患吗?多线程访问的时,会出现多个实例的安全问题

3.安全隐患解决办法:加同步代码块或者同步函数,但是比较低效,可以使用双重判断+内部同步代码块的方式解决低效的问题

4.懒汉式的锁是:该类所属的字节码对象

 

6.死锁示例

 

死锁是指发生在线程之间相互阻塞的现象,这种现象导致同步线程相互等待,以致每个线程都不能往下执行。发生在同步的嵌套中。

 

下面写一个简单的死锁

<span style="font-size:18px;">class Test implements Runnable
{
	boolean flag;
	public Test(boolean b) 
	{
		this.flag=b;
	}
	public void run()
	{
		
			if(flag)
			{
				while(true)
				{
					synchronized (Lock.locka) 
					{  
						System.out.println(Thread.currentThread()+"locka if!");
						synchronized(Lock.lockb)
						{
							System.out.println(Thread.currentThread()+"lockb if!");
						}
					}
				}
			}
			else
			{
				while(true)
				{
					synchronized (Lock.lockb) 
					{  
						System.out.println(Thread.currentThread()+"locka else!");
						synchronized(Lock.locka)
						{
							System.out.println(Thread.currentThread()+"lockb else!");
						}
					}
				}
			}
		}
	}
	


//定义锁</span>
<span style="font-size:18px;">class Lock
</span>
<span style="font-size:18px;">{
	static Object locka=new Object();
	static Object lockb=new Object();
}
public class DeadLock {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
//定义线程并且启动</span>
<span style="font-size:18px;">		 Thread s1=new Thread(new Test(true));
		 Thread s2=new Thread(new Test(false));
		 s1.start();
		 s2.start();
	}

}</span>


死锁问题发生:

线程1有a锁,线程2有b锁;线程1想要访问b锁,线程2想要访问a锁,双方还不放自己的锁,于是产生死锁。

 

 

 

易引起 是因为两个锁引用的不是同一个锁,这样就会引起死锁现在 

          还有在多现在中出现安全性问题的时候,一般要考虑的问题: 

       1.   是不是两个线程,并且两个线程有没有同步,也就是有没有 synchronized 块或函数 

       2.   如果有多个同步代码块或同步函数的话,那看看所有 的同步它们用的是不是同一个锁,如果不是,会引起死锁和安全性问题 

 

 四.多线程的操作【难不理解】

 

1、线程通信

(1)线程通信

多线程一个重要特点就是它们之间可以相互通信,线程通信是线程之间可以相互交流和等待,可以通过经常共享的数据使线程相互交流,也可以通过线程控制方法使线程之间相互等待。

线程间通信:其实就是多个线程在操作同一个资源,但是操作的动作不同。

(2)等待唤醒机制

线程通信可以通过线程控制方法使线程互相等待。

Object类提供了3个方法:wait()、notify()和notifyAll()。

都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。


为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。


wait()和sleep()的区别:wait() 释放资源,释放锁;sleep()  释放资源,不释放锁。

 

五.线程小知识点

1.停止线程,守护线程【守护线程就是后台线程】,join方法,yield()方法

Interrupt()

setDaemon(boolean on)该方法必须在启动前调用

后台线程的特点:开启后,和前台线程共同抢夺cpu执行权,当所有的前提线程都结束后,后台线程会自动结束

Join方法;等待该线程停止,即,cpu的执行权在调用join方法的线程手里,而等这个线程执行完毕之后,线程.join之后的线程交互执行,

 2.线程池

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。

 

 

3.Java线程中sleep和wait的区别,

java 线程中的sleep和wait有一个共同作用,停止当前线程任务运行,但他们存在一定的不同,首先我们先看sleep中的构造函数。

  1、这两个方法来自不同的类分别是Thread和Object

  2、最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

  3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在

  任何地方使用(使用范围)

  synchronized(x){

  x.notify()

  //或者wait()

  }

  4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

 

 

 

 

 

 

 

 

 

 

 

    

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值