黑马程序员 java多线程笔记(一)

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

多线程概述


进程是一个正在执行的程序,每一个进程都有一个执行顺序,该顺序是一个执行路径或者叫一个控制单元,线程是进程中一个独立的控制单元。

一个进程中至少有一个线程, jvm进程中至少有一个线程负责java程序执行,而且这个线程运行的代码存在于main方法中,该线程称为主线程。

其实更细节说明jvm jvm启动不止一个线程,有负责垃圾回收的线程。

如何在自定义代码中自定义一个线程?

 通过api查找java已经提供了对线程这类实物的描述,就是Thread类,创建线程的第一种方法继承Thread

1 创建一个类继承Thread

2复写Thread中的run方法 (目的是将自定义代码存储在run方法中,让run方法执行)

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

class Demo extends Thread
{
	public void run()//复写run方法,将自己的自定义代码存储在run中
	{
		for(int x=0;x<60;x++)
			System.out.println("demo run -----"+x);
	}
}
class  thd
{
	public static void main(String[] args) 
	{
		Demo d = new Demo();//创建一个线程
		d.start();//开启线程并执行里面的run方法

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

这个程序的每一次运行的结果是不一样的。这是因为多个线程都在获取cpu的执行权 cpu执行到谁,谁就运行,明确一点单核系统在某一时刻只能有一个程序在运行。cpu在做快速切换,看上去是同事运行的结果,cpu执行哪个线程是根据相应的线程调度算法。

为什么要覆盖run方法呢

目的是将自定义的代码存贮在run方法中让线程执行。 Thread类用于描述线程,该类定义了一个功能,用于存数线程要运行的代码。该存储功能就是run方法,也就是说Thread类中的run方法用于存储线程要运行的代码。

start和run的区别

对象.start();开启线程,执行该线程的run方法。

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

每一个线程都有自己的编号,格式是 Thread——编号,这个编号是从0开始的,static Thread currentThread():获取当前线程对象。 getName();获取线程名称。

例子 创建两个线程和主线程交替运行。

class Test extends Thread
{
	Test(String name)
	{
		super(name);
	}
	public void run()
	{
		for(int x=0;x<60;x++)
		{
			System.out.println((Thread.currentThread()==this)+"......"+this.getName()+"run...."+x);
		}
	}
}

class  thd2
{
	public static void main(String[] args) 
	{
		Test t1 = new Test("one----");//创建两个线程并开启 
		Test t2 = new Test("two++++");
		t1.start();
		t2.start();
		for(int x=0;x<60;x++)
		{
			System.out.println("main----"+x);
		}
	}
}

多线程售票的例子

需求 简单的卖票系统,实现多个窗口同事卖票

第一种创建方式

此时会出现不同线程对同一张票进行操作,结局这个问题有一个办法就是 把票静态了 
private static int tick = 100; 这样就可以防止这种情况发生,但是我们在实际编程的时候
不会用静态,因为静态的生命周期较长。
那么第一种创建方式就不靠谱了。

class Ticket extends Thread
{
	private int tick = 100;
	public void run(){
		while(true)//while在这是用于测试,ctrl+c 结束运行
		{
			if(tick>0)
			{
			   System.out.println(Thread.currentThread().getName()+"sale : "+tick--);
			}
		}
	}
}

class  TicketDemo
{
	public static void main(String[] args) 
	{
		Ticket t1 = new Ticket();
		Ticket t2 = new Ticket();
		Ticket t3 = new Ticket();
		Ticket t4 = new Ticket();

		t1.start();
		t2.start();
		t3.start();
		t4.start();

		System.out.println("Hello World!");
	}
}


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

步骤 

1定义类实现Runnable接口

2覆盖Runnable接口中的run方法,

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

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

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

    为什么讲runnable接口的子类对象传递给Thread的构造函数,因为

自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程

去指定 知道那个对象的run方法。就必须明确该run方法所属的对象。

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

采用第二种方法来创建该例子

class Ticket implements Runnable //创建了一个实现Runnable接口的类
{
	private  int tick = 100;

	public void run() //在该类中复写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();//创建一个实现了Runnable接口的类的对象
		
		//通过Thread类建立线程对象 这就是建立了一个线程
		// 通过Runnable接口的子类对象作为实际参数传递给Thread类的构造函数	
			Thread t1 = new Thread(t); 
			Thread t2 = new Thread(t); 
			Thread t3 = new Thread(t);
			Thread t4 = new Thread(t);


		// 调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
		t1.start();
		t2.start();
		t3.start();
		t4.start();

	
	}
}

这就成了。

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

实现方式好处是避免了单继承的局限性。(java里面一个类继承了一个父类会后就不能继承别的父类了)在定义线程时,建议使用实现方式。

两种方式的区别:

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

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

这个Runnable究竟啥用呢?

在 java里面有这样的规则,子类继承了父类才能执行父类的方法,子类继承了一个父类之后就不允许继承其他的类了,但是还要用到其他的类的方法怎么办?伟大的java工程师们找出了一个方法就是即便这个类不是我儿子也能替你执行你的方法 但是你要遵循我的规则。Runnable接口就可以解决这问题。


多线程的安全问题

想上面这个例子,他会出现负数票的情况,在干运行的时候很难测出来 ,这个时候我们只需要在if(tick>0) 

下面添加try{Thread.sleep(10);}catch(Exception e){} 就能看到负数票,sleep函数的意思是在程序执行到这的时候暂停参数毫秒.

这问题是怎么产生的呢? 

当多条语句在操作同一个线程共享数据时 一个线程对多条语句只能执行一个部分,还没有执行完,另一个线程参与进来,造成错误。

遇到这个问题怎么解决?

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

怎么实现?

java对于多线程的安全问题提供了专业的解决办法。用同步代码块 格式

synchronized(对象)

{

需要被同步的代码

}

对象如同锁,持有锁的线程可以再同步中执行,没有持有锁的线程即使获取CPU的执行权也进不去,因为没有获取锁。打个比喻,在火车上用厕所,每次只能进入一个人,该人进入后 咔 门一锁,此时只能这一个人用,用完后放开锁,下一个人进去,咔 又一锁。对象就是那锁。、

那么解决上面的问题就这么实现,将Ticket类中的代码换成这样

private  int tick = 1000;
	Object obj = new Object();
	public void run()
	{
		while(true)
		{
			synchronized(obj)// 注意这里面这个obj对象是任意对象就行,一般这时候就用上帝的。
			{
				if(tick>0)
				{
					
					System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
				}
			}
		}
	}

我们在使用同步的时候是由前提的

有俩 第一 必须要有两个或者两个以上的线程。第二 必须是多个线程使用同一个锁。

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

好处是 解决了线程的安全问题 , 弊端呢 就是 多个线程需要判断锁,教为耗费资源。

程序总是否有安全问题,如果有 如何解决?

如何找问题 

1 明确哪些代码是多线程运行代码

2明确共享数据。

3明确多线程运行代码中哪些语句是操作共享数据的。


同步代码块可以扩展到同步函数,首先这个函数要具备同步性,然后才能扩展,看下面一个例子就了然了

class Bank
{
	private int sum;
	//Object obj = new Object();
	public synchronized void add(int n)
	{
		//synchronized(obj)
		//{
			sum = sum + n;
			try{Thread.sleep(10);}catch(Exception e){}
			System.out.println("sum="+sum);
		//}
	}
}
这个是在add函数里面构造同步代码块拓展为add同步函数。
同步函数调用的是哪一个锁?

函数需要被对象调用,那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this 。通过一个例子来验证。 

需求  使用两个线程进行卖票,一个线程在同步代码块中,一个线程在同步函数中,都在执行卖票动作。

class Ticket implements Runnable
{
	private int tick = 100;
	Object obj = new Object();
	boolean flag = true;
	public void run()
	{
		if (flag)
		{
			while (true)
			{
				synchronized(obj) //这里面放的是obj则会产生0好票,要是放的this 则不会产生。
				{
					if (tick>0)
					{
						try
						{
							Thread.sleep(10);
						}
						catch (Exception e)
						{
						}
						System.out.println(Thread.currentThread().getName()+"....code----- : "+tick--);
					}
				}
			}
		}
		else
			while(true)
			 show();
	}
	public synchronized void show ()
	{
		if (tick>0)
		{
			try
			{
			Thread.sleep(10);
			}
			 catch (Exception e)
			{
			}
			System.out.println(Thread.currentThread().getName()+"....show : "+tick--);
		 }
	}


}

class ThisLockDemo
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();
		
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
        try
        {
			Thread.sleep(3);
        }
        catch (Exception e)
        {
        }
		t.flag = false;
		t2.start();
	}
}

当同步函数被静态修饰之后,所用的锁不是this 因为静态方法中也不可以定义this。静态进内存是,内存中没有本类对象,但是一定有该类对应的字节码文件对象。 记住 是 【类名.class】

多线程单例设计模式

这里主要是将懒汉式, 怎么考呢 会出现 延迟加载的单例设计模式示例,这个时候就写懒汉式那个。

懒汉式用于对象的延迟加载,多线程访问时会出现问题,可以用同步来解决,用同步代码块或者同步函数都恩能够解决,不过有点低效,用双重判断的形式解决效率问题,加同步的时候使用锁是该类字节码文件。(单例设计模式必须要会)

下面将该单例设计模式实现。

// 单例设计模式
// 俄汉式
/*
俄汉 上去就实例化对象
class Single
{
	private static final Single s = new Single();
	private Single(){}
	public Static Single getInstance()
	{
		return s ;
	}
}
*/
//懒汉式  懒汉 到执行的时候才实例化对象

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 ;
	}
}

class SingleDemo 
{
	public static void main(String[] args) 
	{
		System.out.println("Hello World!");
	}
}
多线程同步问题 还会遇到死锁,一般死锁的发生都是在嵌套中,通俗的说 我等待你手中握着的资源去执行,你等待着我执行后的结果去运行,这俩谁也不撒手 这就造成了死锁。

下面是一个常见的死锁程序, 面试的时候获取会考

 

class Ticket implements Runnable
{
	private int tick = 1000;
	Object obj = new Object();
	boolean flag = true;
	public void run()
	{
		if (flag)
		{
			while (true)
			{
				synchronized(obj)
				{
					show();
				}
			}
		}
		else
			while(true)
			  show();
	}
	public synchronized void show()
	{
		synchronized(obj)
		{
			if(tick>0)
			{
				try
				{
					Thread.sleep(10);
				}
				catch (Exception e)
				{

				}
				System.out.ptrinln(Thread.currentThread().getName()+"...code :"+tick--);
			}
		}
	}
}

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

		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		try
		{
			Thread.sleep(10);
		}
		catch (Exception e)
		{
		}
		t.flag = false;
		t2.start();
		
	}
}


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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值