黑马程序员 java_多线程(一)

本文详细介绍了Java中多线程的基本概念、定义方法、线程的状态及同步机制等内容。通过实例讲解了如何创建线程、解决多线程安全问题以及避免死锁。

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

                                     

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

 

多线程

 

 

 

 基本理解

 

进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。一个进程中至少有一个线程。
 
Java VM  启动的时候会有一个进程java.exe.该进程中至少一个线程负责java程序的执行。而且
这个线程运行的代码存在于main方法中。该线程称之为主线程。此外还会启动负责垃圾回收机制的线程。
 
可以简单理解为:进程是需要执行的任务,而线程是cpu能够同时执行的最大任务数量。在一台电脑中线程数量有cpu决定,是固定的。而进程是由需要执行的任务决定,数量是变动的。在个人电脑中,一般会有几十个进程,只有几个线程(有的甚至只有一个线程),为了保证所有任务得到执行,cpu就需要在这些任务上不到切换,切换速度相当快,就好像几十个任务一起执行的。
 
java中的多线程技术与cpu上的多线程是不同的,首先cpu的多线程是从物理上来说,有几个运行核,就可以同时运行几个任务。而java中的多线程技术是相对于逻辑上来说的,我有几个线程需要独立运行,看上去还要"同时"运行,实际上它们并没有同时运行,它们在不停争夺cpu执行资格,这也是为什java多线程中往往线程需要等待以便获取cpu执行权,然后才能执行代码。它们只是在相对独立的运行,并且运行顺序上不分先后。这其实就有点像windows操作系统上多个进程“同时”执行一样。
 
java多线程技术是非常有用的,首先可以提供代码运行速度。此外一些程序中需要两段甚至更多代码的交互运行,多线程技术将能很好的解决这个问题。

 

 

 

    定义线程

 

有两种方法,第一种为:继承Thread类

通过对api的查找,java已经提供了对线程这类事物的描述。就Thread类。

 

 步骤:  1,定义类继承Thread。

               2,复写Thread类中的run方法。

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

               3,调用线程的start方法。

                     该方法两个作用:启动线程,调用run方法。

 

 


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) 
	{
		//for(int x=0; x<4000; x++)
		//System.out.println("Hello World!");

		Demo d = new Demo();//创建好一个线程。
		//d.start();//开启线程并执行该线程的run方法。
		d.run();//仅仅是对象调用方法。而线程创建了,并没有运行。

		
		for(int x=0; x<60; x++)
			System.out.println("Hello World!--"+x);
		
	}
}

可以发现运行结果每一次都不同。这是因为多个线程都获取cpu的执行权。cpu执行到谁,谁就

运行。

  

在定义线程是需要复写run()方法,这是因为:

Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就

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

 

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

 

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

                       2,覆盖Runnable接口中的run方法。将线程要运行的代码存放在该run方法中。

                       3,通过Thread类建立线程对象。

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

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

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

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

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

 

package itcast.heima;

class Ticket implements Runnable
{
	private  int tick = 100;
	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 t1 = new Thread(t);//创建了一个线程;
		
		t1.start();
		
	}
}

 

       

 

这部分也可以这么理解,Runnable接口提供了定义线程的内容格式,这些内容都定义在了run()方法

中,Thread类提供了创建线程的方式。

 

 首先Runnable接口提供了定义线程的内容格式

因为Thread类已经实现了Runnable接口,所以当我们通过Thread子类创建对象时,Thread子类

的run方法会复写Runnable接口中的run()方法。

 

 Thread类提供了创建线程的方式

从上面两个例子可以看出无论通过Thread子类的方式定义线程,还是通过实现Runnable接口的方式,最终都需要Thread来创建线程。

 

由于用继承方式创建代码避免了单继承的局限性,所以在定义线程时,建议使用实现Runnable的方法。

 

 

 

   线程的四种状态

 




 
 

 

 

      同步

 

 

    多线程的运行时,往往会出现我们不想看到的结果。


class Ticket1 implements Runnable
{
	private  int tick = 100;
	public void run()
	{
		while(true)
		{
			if(tick>0)
			{
				System.out.println(Thread.currentThread().getName()+"....sale : "+ tick);
				tick--;
			
			}
		}
	}
}


class  TicketDemo
{
	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();

	}
}

 



 

从结果中可以看到,打出来四张编号为100的票,出现的原因是,当线程t1刚打印了标号为100的票,还没有执行tick--,线程t2和t3还要t4都接着执行打印编号的语句。所以都打印了编号为100的票,这显然不是设计程序时想要的结果。这就是多线程安全问题。

 

问题的原因:

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行

完,另一个线程参与进来执行。导致共享数据的错误。归根到底还是因为是由cpu不断的快速切

换造成的。

 

解决办法:

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

执行。

 

 

同步代码块

 

Java对于多线程的安全问题提供了专业的解决方式,就是同步代码块。

            synchronized(对象)

                   {

                            需要被同步的代码

                    }

对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进

不去,因为没有获取锁。

 

         同步的前提:

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

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

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

 

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

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


class Ticket implements Runnable
{
	private  int tick = 1000;
	Object obj = new Object();
	public void run()
	{
		while(true)
		{
			synchronized(obj)
			{
				if(tick>0)
				{
					System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
				}
			}
		}
	}
}


class  TicketDemo2
{
	public static void main(String[] args) 
	{

		Ticket t = new Ticket();

		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();


	}
}

同步以后,同步代码块中只能同时运行一个线程,就不会出现安全问题,就不会买出现,零和

负数的票。

 

 

同步函数

 

将关键字sychronized修饰函数,即可创建同步函数。

格式为:

             public synchronized void show(){}

 

同步函数用使用的锁     

              同步函数使用的锁是this。

              静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class

 

 

同步的好处:

       解决了线程的安全问题。

 

弊端:

较为消耗资源。

同步嵌套后,容易死锁。

 

 

 

 

死锁

 

 

简单的就是说,同步中嵌套同步。 

class Test implements Runnable
{
	private boolean flag;
	Test(boolean flag)
	{
		this.flag = flag;
	}

	public void run()
	{
		if(flag)
		{
			while(true)
			{
				synchronized(MyLock.locka)
				{
					System.out.println(Thread.currentThread().getName()+"...if locka ");
					synchronized(MyLock.lockb)
					{
						System.out.println(Thread.currentThread().getName()+"..if lockb");					
					}
				}
			}
		}
		else
		{
			while(true)
			{
				synchronized(MyLock.lockb)
				{
					System.out.println(Thread.currentThread().getName()+"..else lockb");
					synchronized(MyLock.locka)
					{
						System.out.println(Thread.currentThread().getName()+".....else locka");
					}
				}
			}
		}
	}
}


class MyLock
{
	static Object locka = new Object();
	static Object lockb = new Object();
}

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

 



可以看出程序并未结束,而是处于不能继续往下执行就状态。从上面代码可以看出,线程t1将先拿锁 locka,然后拿lockb。而线程t2会先拿锁lockb,然后拿locka。

从结果中看出,线程t2拿锁lockb,正在等待锁locka被释放,而此时线程t1也拿到锁locka,正在等待锁lockb被释放,这时两个线程都处于临时阻塞状态,都在等待对方释放锁。但是两边都不释放自己的锁。所以程序处于死锁状态。

死锁是设计代码时,应当极力避免的。

 

 

 

                        

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值