多线程概述

多线程

要说线程,首先说一下我们比较熟悉的词语:进程,什么叫进程了?其实就是正在执行的程序。比如jvm在运行的时候会有一个进程叫java.exe那么,什么叫线程呢?在一个程序运行过程中,可能会有很多个线程组成,所以,线程其实就是进程中一个独立控制的单元,即线程在控制着进程的执行。(一个进程中至少有一个线程)
jvm运行的时候,我们看起来,可能觉得只有main函数在运行,这个存在于main方法中的线程叫做主线程,其实,如果更细节的取探讨,jvm至少还会再启动一个垃圾回收机制的线程,这样会让执行更高效。
那么,在我们的代码中,我们讲怎样去开启多线程呢?
在java的API文档中,我们会发现一个Thread(线程的意思)的类,这个类其实就是对线程这类事物的描述,然后被封装在了Thread这个类中,方便于我们的使用,所以,要创建线程,首要任务就是继承Thread类,首先你要变成它们的一员,你才能用人家里面的东西,具体步骤如下:
1:定义一个类,继承Thread类
2:复写Thread类中的run方法,将此线程要运行的代码放在里面
3:调动线程的start方法,启动此线程。(注意,一定要是调用start方法才能启动线程,调用run方法就只是在调用这个方法)
我们用一个简单的代码来继续接下的问题:
eg:
<span style="font-size:14px;">class Demo extends Thread
{
	public void run()//仅仅是封装线程要用的代码
	{
		for(int x=0;x<60;x++)
		{
			System.out.println("demo run--"+x);
		}
	}
}


class ThreadDemo
{
	public static void main(String []args)
	{
		Demo d=new Demo();//创建好一个线程
		d.start();开启线程并执行该线程的run方法
		//d.run();//仅仅是对象调用方法,而线程创建了,并没有运行
		
		
		for(int i=0;i<60;i++)
			System.out.println("hello---"+x);
	}
}</span>



每运行一次这段程序,我们会发现结果会不一样,这是为什么呢?是因为在多线程中,每个线程都有可能获取CPU的执行权,谁获得执行权,就该谁运行,所以每次运行的次数 和时间都是不一样的。所以,多线程其实就是每个线程在相互抢夺CPU的执行权,当然,这也体现了多线程的一个特性:随机性(谁抢到,谁执行。至于执行多长时间,是CPU说了算)



其实创建线程,除了上述的方法,还有一个方法,就是:实现Runnable接口,步骤如下:
1:定义类实现Runnable接口
2:覆盖Runnable接口中的run方法
3:通过Runnable类建立线程对象
4:将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
5:调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
 

既然继承Thread类就能创建线程,那么为什么还要有一个实现接口的方式去创建线程呢?那是因为java支持的是单继承的体系,如果是通过实现接口的方式去创建线程,那么就可以避免单继承的局限性,所以,在定义线程的时候,建议使用实现的方式

而他们唯一的区别就是线程代码存放的位置不一样,继承的代码存放在Thread子类的run方法中,实现的代码存放在接口子类的run方法中(哎,这个叫不能叫做区别的区别)
再用传智播客中的一个例子来帮助理解
eg:
class Ticket implements Runnable//extends Thread
{
	private static int tick=100;//3.但是送static的生命周期太长,所以一般不这么解决此问题
								//1.这个问题为:只有一百张票,但是四个线程同时进行却会卖出四百张票
								//2.如果把票定义为静态,让四个对象共享,那么就只卖出了一百张票
	public void run()
	{
		while(true)
		{
			if(tick>0)
			{
				System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
			}
		}
	}
}
class TicketDemo
{
	public static void main(String []args)
	{
		
		Ticket t=new Ticket();//此处没有创建线程,因为和Thread没有关系
		
		Ticket t1=new Ticket(t);//建立一个线程
		Ticket t2=new Ticket(t);//建立一个线程
		Ticket t3=new Ticket(t);//建立一个线程
		Ticket t4=new Ticket(t);//建立一个线程
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		
		
		/*
		Ticket t1=new Ticket();
		Ticket t2=new Ticket();
		Ticket t3=new Ticket();
		Ticket t4=new Ticket();
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		*/
		
	}
}



对于上述代码,我们运行发现,如果我们在run方法的if语句中,如果在第一行加入一个sleep语句,会打印出0,-1等错误的数据,这表明多线程出现了安全隐患。为什么会出现这样的问题呢?其实是因为当多条语句在操作同一个共享数据时,一个线程对多条语句只执行了一部分,还没有执行完的时候,另一个线程抢到了CPU的执行权,导致了共享数据的错误,所以就打印出了错误的数据。
那要怎么解决呢?其实只要让代码保证,对于多条操作共享数据的语句,只能让一个线程执行完后,才放另一个线程执行,那么问题就得到了解决。所以,此处要引入一个特别长,特别难写的关键字,synchronized(同步),即我们运用同步代码快来解决这个问题,基本格式如下:
synchronized(对象)
{
需要被同步的代码;
}
形象一点的说法,就是把操作共享数据的语句锁进一个空间里面,正在执行的线程拥有这个锁,只有他用完了,把这个空间和锁交给别的线程,别的线程才能用CPU的执行权,当那个线程还在那么空间的时候,其余的线程就算抢到了CPU的执行权也于事无补,这就为上面的问题提供了一个很好的解决方法。
当然,要运用同步代码块也有两个很基本的前提:首先,你必须要有两个及其以上的线程,如果只有一个线程,那么加个同步代码块不是浪费内存吗?其次,必须是多个线程使用同一个锁。
任何事物都有两面性,虽然它解决了多线程的安全问题,但是,这个确实很浪费资源的。。。
再借用传智播客的一个小例子吧。嘻嘻
eg:
<span style="font-size:14px;">class Ticket implements Runnable
{
	private int tick=100;
	Object obj=new Object();
	public void run()
	{
		while(true)
		{
			synchronized(obj)//只要是对象就行
			{
				if(tick>0)
				{
					try {Thread.sleep(10);}catch(Exception e){}
					System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
				}
			}
		}
	}
}
class TicketDemo
{
	public static void main(String []args)
	{
		
		Ticket t=new Ticket();
		
		Ticket t1=new Ticket(t);
		Ticket t2=new Ticket(t);
		Ticket t3=new Ticket(t);
		Ticket t4=new Ticket(t);
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}</span>




用同步来解决多线程的安全问题,除了静态代码块,还有一个就是把synchronized当做修饰符,把函数同步,但是这时候那个对象就不再是this了,而是该类所属的字节码文件对象。还有一点,如果同步代码块所在的函数被静态修饰后,锁也不再是this了,也变成了该类所属的字节码文件对象。其实需要注意的就是这两点,



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值