黑马程序员----java中的多线程基础

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

一、概述

1.进程与线程

谈多线程,首先要知道什么是进程。

进程是一个正在执行的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。线程就是进程中的一个独立的控制单元,线程在控制着进程的执行。java虚拟机启动的时候以多线程运行,代码存在于main中的线程称为主线程,还有负责垃圾回收的线程等。

线程运行流程图:





2.多线程存在的好处和弊端:

多线程的优点在于:程序运行的时候,如果只有一个线程在运行,那么资源的利用效率会比较低,如果把任务分摊给多个线程,让程序内部代码并发进行,那么可以有效利用cpu和资源的空余,从而提高效率。

多线程的不利方面:

线程也是程序,所以线程需要占用内存,线程越多占用内存也越多; 

多线程需要协调和管理,所以需要CPU时间跟踪线程; 

线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;

线程太多会导致控制太复杂,最终可能造成很多Bug。

二、创建线程

1.创建线程的方法一:继承Thread类

步骤:

(1),定义类继承Thread。

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

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

(3),调用线程的start方法,

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

<span style="font-family:Microsoft YaHei;">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 x=0; x<60; x++)
			System.out.println("Hello World!--"+x);
	}
}</span>

发现:运行结果每一次都不同。因为多个线程轮流获得cpu执行权,在某一个时刻,只能有一个程序在运行。(多核除外)这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长是不确定的。

为什么要覆盖run方法呢?

Thread类用于描述线程,该类就定义了一个功能,用于存储其他线程(非主线程)要运行的代码。该存储功能就是run方法。也就是说Thread类中的run方法,用于存储线程要运行的代码。

2.线程的标识

线程都有自己默认的名称。形式为:

Thread-编号(该编号从0开始)

相关方法:

static Thread currentThread():获取当前线程对象。

getName(): 获取线程名称。

设置线程名称:setName或者构造函数。

3.创建线程的方法二:实现Runable接口

步骤:

(1),定义类实现Runnable接口

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

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

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

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

将Runnable接口的子类对象传递给Thread的构造函数,是因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。

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

实现方式和继承方式有什么区别呢?
实现方式好处:避免了单继承的局限性。
在定义线程时,建立使用实现方式。
两种方式区别:
继承Thread:线程代码存放Thread子类run方法中。

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

<span style="font-family:Microsoft YaHei;">class Ticket implements Runnable//extends Thread
{
	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);//创建了一个线程;
		Thread t2 = new Thread(t);//创建了一个线程;
		Thread t3 = new Thread(t);//创建了一个线程;
		Thread t4 = new Thread(t);//创建了一个线程;
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}</span>

三.实现线程的同步

线程在交替执行的过程中,有这样一种情况,一个线程正在执行一部分不可分割的代码(有两句或两句以上),若中途有其他进线程介入,会造成代码执行顺序的错误,这个时候就必须将这一部分代码进行同步,即加锁,如果有线程正在执行某一块同步代码,那其他线程执行到这里时必须等待,这就是线程的同步。

同步的前提是要有两个或两个以上的线程,并且多个线程使用同一把锁,如果不满足这两个条件,就不能称其为同步。同步时,同步的代码只有一个线程在执行。同步保证了程序的安全性。

同步也有弊端,当线程较多时,每个线程都会去判断锁,这样就降低了程序的执行效率。

同步的实现:

1.同步代码块代码块:

synchronized(对象)//任意对象都行

{

需要被同步的代码

}

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

卖票的例子:

现有四个窗口,同时卖票,票共有100张,用进程模拟卖票窗口

<span style="font-family:Microsoft YaHei;">class Ticket implements Runnable
{
	private  int tick = 1000;
	Object obj = new Object();//为synchronized提供对象
	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  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();
	}
}</span>

2.函数的同步

当同步代码块较多时,可以封装成同步函数,即在声明函数时加上synchronized修饰,且不需要指明作为锁的对象。

需求:

银行有一个金库。

有两个储户分别存300员,每次存100,存3次。

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

如何找程序的安全问题呢?

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

(2)明确共享数据。

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

<span style="font-family:Microsoft YaHei;">class Bank
{
	private int sum;
	//Object obj = new Object();//同步代码块可用Object的对象作为锁
	public synchronized void add(int n)//若在函数上加上synchronized关键字,则函数内代码都被同步,效果等同于同步代码块
	{
		//synchronized(obj)
		//{
			sum = sum + n;
			try{Thread.sleep(10);}catch(Exception e){}
			System.out.println("sum="+sum);
		//}
	}
}

class Cus implements Runnable
{
	private Bank b = new Bank();
	public void run()
	{		
		for(int x=0; x<3; x++)
		{
			b.add(100);
		}
	}
}

class  BankDemo
{
	public static void main(String[] args) 
	{
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}
}</span>

同步函数用的是哪一个锁:

函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。

如果同步函数被静态修饰后,使用的锁是什么呢?

是该类对应的字节码文件对象(类名.class  该对象的类型是Class),为什么不是this呢,因为静态方法中不可以定义this


四、线程的死锁

当几个线程之间都不释放资源,而等待别的线程释放自己需要的资源的时候,造成所有的线程都处于等待状态,程序无法继续运行下去,这就是死锁。

死锁的代码示例:

<span style="font-family:Microsoft YaHei;">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();//t1会进入这里
				}
			}
		}
		else
			while(true)
				show();//t2会进入这里
	}
	
	public synchronized void show()//用的锁是this,即这个方法所属的对象
	{
		synchronized(obj)//用的锁是obj
		{
			if(tick>0)
			{
				try{Thread.sleep(10);}catch(Exception e){}
				System.out.println(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();//t1开启
		try{Thread.sleep(10);}catch(Exception e){}//让主线程先休眠一下,则t1开始运行
		t.flag = false;//设置flag为false
		t2.start();//t2开启
	}
}</span>

当线程t1持有obj锁,需要this锁以便运行同步函数,但是t2持有同步函数的this锁,需要obj锁以便运行同步代码块,双方都不放手,这时就会产生死锁。

如何避免死锁呢?一般造成死锁必须同时满足如下4个条件:

1,互斥条件:线程使用的资源必须至少有一个是不能共享的;

2,请求与保持条件:至少有一个线程必须持有一个资源并且正在等待获取一个当前被其它线程持有的资源;

3,非剥夺条件:分配资源不能从相应的线程中被强制剥夺;

4,循环等待条件:第一个线程等待其它线程,后者又在等待第一个线程。

因为要产生死锁,这4个条件必须同时满足,所以要防止死锁的话,只需要破坏其中一个条件即可。


更多方法请参考java的API文档
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 您好!如果我理解正确的话,您的问题是关于JMU Java 07多线程同步访问的。以下是我的回答: 多线程编程是指在一个程序同时执行多个线程的编程技术。多线程编程在提高程序性能、增强程序交互性等方面有很大的优势,但也会引入一些问题,比如线程安全问题。 当多个线程同时访问共享资源时,可能会出现线程不安全的问题,比如数据竞争、死锁等。为了避免这些问题,我们需要使用同步机制来确保线程安全。 Java提供了多种同步机制,比如synchronized关键字、ReentrantLock类、Semaphore类等。这些同步机制都可以用来保证多线程访问共享资源的安全性。 其,synchronized是最常用的同步机制之一。在Java,synchronized关键字可以用来修饰方法或代码块,以实现同步访问共享资源。synchronized可以保证在同一时间只有一个线程可以访问共享资源,从而避免了数据竞争等线程安全问题。 除了synchronized之外,Java还提供了一些其他的同步机制,比如ReentrantLock类和Semaphore类。ReentrantLock类提供了比synchronized更灵活的锁机制,可以实现可重入锁、公平锁等功能。Semaphore类可以控制同时访问共享资源的线程数量,从而避免资源被过度占用的问题。 总的来说,多线程编程是一项复杂而重要的技术,需要仔细研究和实践。在实际开发,我们需要根据具体的需求选择合适的同步机制来确保多线程访问共享资源的安全性。 ### 回答2: 多线程编程是在当前计算机领域最为常见的技术之一,它可以利用计算机的多核处理器来使程序运行更加高效。但是,多线程编程可能会出现的最大问题就是线程安全,因为线程之间可能会访问相同的资源,从而导致竞态条件。 在Java,可以通过使用synchronized关键字来实现同步访问,从而避免线程安全问题。synchronized关键字可以用于两种不同的情形:同步方法和同步块。在同步方法,方法是同步的,即每个线程在执行该方法时都需要获取该对象的锁,如果该锁已经被其他线程获取,则需要等待直到此锁被释放。在同步,需要手动指定锁,即每个线程在执行同步块时需要获取该指定锁,其他线程如果需要访问该代码块的共享资源也需要获取该指定锁,这样就保证了该代码块的所有共享资源的同步访问。 除了synchronized关键字外,Java还提供了其他一些同步机制来实现线程安全,如ReentrantLock类和CountDownLatch类等。ReentrantLock类可以实现更为灵活的同步访问控制,但需要手动释放锁;而CountDownLatch类则用于同步一个或多个线程,使这些线程在某个条件满足之前一直处于等待状态。 在进行多线程编程时,应该尽量避免对同步访问造成瓶颈,应该通过减小同步代码块的范围等方式来提高程序的效率。此外,多线程编程时还应该进行线程安全性的测试,以确保程序能够正确地运行。 ### 回答3: 在Java多线程是一种非常常见的编程方式。由于多线程的特点,对共享资源的访问会出现竞争的情况,这种竞争可能会导致数据不一致或程序异常等问题。因此,在多线程编程,我们需要采取一些措施来保证共享资源的访问能够正确、有序地进行,这就是同步机制。 同步机制包括两种方式:锁和信号量。锁是最基本的同步机制。锁有两种类型:互斥锁(Mutex)和读写锁(ReadWriteLock)。互斥锁用于保护共享资源,保证同一时间只有一个线程可以访问它,其他线程需要等待锁释放后才能继续访问。读写锁用于读写分离场景,提高了多线程访问共享资源的并发性。读写锁支持多个线程同时读取共享资源,但只允许一个线程写入共享资源。 信号量是一种更加高级的同步机制。信号量可以用来控制并发线程数和限制访问共享资源的最大数量。在Java,Semaphore类提供了信号量的实现。Semaphore可以控制的线程数量可以是任意的,线程可以一起执行,也可以分批执行。 除了锁和信号量,Java还提供了一些其他同步机制,比如阻塞队列、Condition等。阻塞队列是一种特殊的队列,它支持线程在插入或者删除元素时阻塞等待。Condition是一种锁的增强,它可以让线程在某个特定条件下等待或者唤醒。 在多线程编程,使用同步机制需要注意以下几点。首先,同步机制要尽可能的保证资源访问的公平性,避免因为某些线程执行时间过长导致其他线程等待时间过长。其次,同步机制要尽可能的避免死锁的发生,尤其要注意线程之间的依赖关系。最后,同步机制的实现要尽可能地简单,避免过于复杂的代码实现带来的维护成本。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值