黑马程序员_多线程

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

多线程

多线程的运行出现了安全问题的原因:

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

 

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

Java对于多线程的安全问题,提供了专业的解决方式。

就是同步代码块。

Synchronized(对象)
{
   //需要被同步的代码。
}

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

 

线程同步的书写前提:(也就是当用了同步之后依然出现线程安全问题)

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

2.   必须是多个线程使用同一个锁。(十分的重要)

 

如何找一个程序中是否会发生多线程安全的问题。

1.   明确哪些代码是多线程运行的代码。(run方法中的都是)

2.   明确哪些数据是共享数据。(公共资源的数据)

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

同步函数的锁是this。

静态进内存时候,内存中没有本类的对象,但是一定有该类对应的字节码文件对象。

所以静态同步函数的锁是这个函数所对应的类的字节码文件对象(类名.class对象)。该对象的类型是Class。

 

单例模式:使一个类只能有一个实例对象。

有懒汉式和饿汉式两种方式:

懒汉式:实例的延迟加载。

在多线程中有一定的问题,可以加同步来解决,而同步的方式,用同步代码块和同步函数都可以,但是稍微有些低效。使用双重否定的方式可以避免每次得到对象的时候都去验证锁,解决这个效率问题。加同步的时候,使用的锁是该类所属的字节码文件对象

示例如下:

//懒汉式

//懒汉式
class SingleClass
{
    private SingleClass()
    {
	System.out.println("create class");
    }

    private static SingleClass singleClass;

    public static SingleClass getSingle()
    {
	if (singleClass == null)
	{
	    synchronized (SingleTest.class)
	    {
		if (singleClass == null)
		{
		    singleClass = new SingleClass();
		}
	    }
	}
	return singleClass;
    }
}
//饿汉式
 class SingleClass
 {
 private SingleClass()
 {
	
 }
 private static final SingleClass singleClass = new SingleClass();
    
    
 public static SingleClass getSingle()
 {
 return singleClass;
 }
    
 }

线程中通信的等待和唤醒机制。

 

系统中等待的线程在哪呢?

线程在运行的时候内存中会建立一个线程池,等待的线程(调用wait()方法)都存放在这个线程池中,当使用notify()唤醒的都是线程池中的线程,通常唤醒第一个被等待的线程,使用notifyAll()唤醒线程池中所有等待的线程

 

十分的注意:在使用wait,notify,notifyAll的时候,必须用在同步中,因为调用它们的前提是获得锁。而且必须要标志这些方法在哪个锁上调用。

为什么这些操作线程的方法要定义在Object类中呢?

因为这些方法在操作同步线程时候,都必须要标识它们所操作线程持有的锁,

只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,也就是等待和唤醒是同一个锁。

而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。

 

实例代码:

package day23;

public class ThreadConnection
{
    public static void main(String[] args)
    {
	Res res = new Res();
	
	Thread inputThread = new Thread(new InputRun(res));
	Thread outputThread = new Thread(new OutputRun(res));
	inputThread.start();
	outputThread.start();
    }

}
class Res
{
    private String name;
    private String sex;
    boolean flagType = true;
    private boolean flagCondiction = true;
    public synchronized void show()
    {
	if(flagCondiction)
	{
	    try
	    {
		this.wait();
	    } 
	    catch (InterruptedException e)
	    {
		e.printStackTrace();
	    }
	}
	
	System.out.println(name+".."+sex);
	flagCondiction = true;
	this.notifyAll();
	
	
    }
    public synchronized void set(String name,String sex) 
    {
	if(!flagCondiction)
	{
	    try
		{
		    this.wait();  
		} 
		catch (InterruptedException e)
		{
		    e.printStackTrace();
		}
	}
	
	this.name = name;
	this.sex = sex;
	flagCondiction = false;
	this.notifyAll();

    }
    
}
class InputRun implements Runnable
{
    public InputRun(Res res)
    {
	super();
	this.res = res;
    }
    private Res res;
    public void run()
    {
	while(true)
	{
	    if(res.flagType)
	    {
		res.set("wangdabin","nan");
	    }
	    else
	    {
		  res.set("丽丽","女");
	    }
	    res.flagType = !res.flagType;
	}
    }
}
class OutputRun implements Runnable
{
    private Res res;
    public OutputRun(Res res)
    {
	super();
	this.res = res;
    }
    public void run()
    {
	while(true)
	{
	   res.show();   
	}
    }
}


当出现多个生产者和消费者时候,上述的代码会出现错误。

原因:

在一个对象调用wait()方法进入阻塞状态的时候,当另一个线程使用notify唤醒的时候,是从wait()方法之后继续执行,而不是去继续验证if中的条件。

所以必须使用while(条件),而这样又会导致所有的线程全部阻塞等待。必须使用notifyAll将所有的线程全部唤醒才能避免。

 

在jdk1.5中提供了多线程升级的解决方案:

将同步Synchronized替换成了Lock操作。

将Object中的wait,notify,notifyAll,替换成了Condition对象。

该对象用来管理那些已经获得了一个锁但是却不能做有用工作的线程,或者完成线程之间的通讯。它用来实现了因为某些条件而导致某些线程进入阻塞状态。在另一个线程中可以使用这个条件唤醒因为某一特定条件被阻塞的线程,而不是所有的线程(像Object)。

特别注意,在使用Lock锁的时候,释放锁这个动作必须要执行,无论是否抛出异常,所以有了以下的代码格式。

  Lock l = ...; 
     l.lock();
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock();//释放锁的动作一定要执行。
     }


停止线程:怎么让一个处于while(true)中的线程停止呢?

只有一种,run方法结束。

开启多线程运行,运行代码通常都是循环结构。

只要控制住循环,就可以让run方法结束,也就是线程结束。

实例:

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

	StopThread st = new StopThread();
	Thread thread = new Thread(st);
	thread.start();
	
	int num = 0;
	while(true)
	{
	    try
	    {
		Thread.sleep(1);
	    }
	    catch (InterruptedException e)
	    {
		// TODO Auto-generated catch block
		e.printStackTrace();
	    }
	    if(num == 60)
	    {
		
		st.changeFlag();
		break;
	    }
	    num++;
	    System.out.println(num);
	   
	    
	}
    }

}
class StopThread implements Runnable
{
    private boolean flag = true;
    public void changeFlag()
    {
	flag = false;
    }
    public void run()
    {
	while(flag)
	{
	    System.out.println("132");
	}
    
    }
}


但是上边这种方式仅仅只能用来对于没有在代码块中出现等待使一个线程进入阻塞状态时候,就会使之不能结束,例如将上述代码块改为

public  void run()
    {
	synchronized(this)
	{
	    while(flag)
	    {
		try
		{
		   this.wait();   
		}
		catch(Exception e)
		{
		}
		System.out.println("132");
	    }
	}
    }


就会使程序不能结束,必须采取其他方法使线程结束,而不仅仅改变标记。

在Thread中有一个方法叫做interrupt(),如果在调用形如wait方法使线程进入等待状态。使用interrupt可以将其等待状态清除,重新恢复到就绪态。但是会抛出InterrupetedException.  也就是在处理这个异常的时候,将while的判断位改为false即可。

实例如下:

public class StopThreadDemo
{

    
    public static void main(String[] args)
    {

	StopThread st = new StopThread();
	Thread thread = new Thread(st);
	thread.start();
	
	int num = 0;
	while(true)
	{
	    try
	    {
		Thread.sleep(1);
	    }
	    catch (InterruptedException e)
	    {
		// TODO Auto-generated catch block
		e.printStackTrace();
	    }
	    if(num == 60)
	    {
		
//		st.changeFlag();
		thread.interrupt();
		break;
		
	    }
	    num++;
	    System.out.println(num);
	   
	    
	}
    }
    
    

}
class StopThread implements Runnable
{
    private boolean flag = true;
    public void changeFlag()
    {
	flag = false;
    }
    public  void run()
    {
	synchronized(this)
	{
	    while(flag)
	    {
		try
		{
		   this.wait();   
		}
		catch(Exception e)
		{
		    flag = false;
		}
		
		System.out.println("132");
	    }
	}
    
    }
}


Thread类中的常见方法。

void   setDaemon(boolean on)

          将该线程标记为守护线程或用户线程。

当正在运行的线程都是守护线程的时候,jvm退出执行。

该方法必须在启动线程前调用。

也就是当一个线程是守护线程的时候,当用户线程没有结束时候,守护线程和它一起抢夺cpu的执行权,一旦用户线程结束,守护线程也就结束。

 

public final    void join()

                throws InterruptedException等待该线程终止。

也就是当在一个线程中调用了另一个线程的join方法时候,这个线程必须在另一个线程运行完成后才能执行。

 

static void    yield()

          暂停当前正在执行的线程对象,并执行其他线程。

 也就是临时释放这个线程的cpu执行权,使其它线程有机会执行。避免了其它线程抢不到资源而被饿死。


### 黑马程序员多线程学习笔记与资料 在Java中,多线程是实现并发编程的重要工具。以下是一些关于黑马程序员多线程学习笔记和相关资料的内容总结[^1]。 #### 线程池的工作机制 线程池是一种用于管理和复用线程的机制,能够有效减少线程创建和销毁的开销,提高系统性能。当通过`submit`方法向线程池提交任务时,其工作流程如下: - 客户端每次提交一个任务,线程池会在核心线程池中创建一个工作线程来执行这个任务。 - 如果核心线程池中的线程已满,则尝试将任务存储到工作队列中。 - 如果工作队列也满了,线程池会再次在非核心线程池区域创建新线程来执行任务,直到当前线程池总线程数量达到最大值。 - 当线程池中的线程数量超过最大值时,多余的任务将按照指定的拒绝策略进行处理。 #### 创建线程的方式 在Java中,可以通过以下几种方式创建线程: 1. **继承Thread类**:通过重写`Thread`类的`run`方法实现线程逻辑,并调用`start`方法启动线程。 2. **实现Runnable接口**:定义一个实现了`Runnable`接口的类,并在`run`方法中编写线程逻辑,然后将其传递给`Thread`类的构造函数。 3. **使用Callable和FutureTask**:`Callable`接口类似于`Runnable`,但可以返回结果并抛出异常。结合`FutureTask`可以实现更复杂的线程功能。 4. **使用线程池**:通过`ExecutorService`接口提供的线程池管理功能,简化线程的创建和管理过程。 以下是使用线程池的一个简单示例: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(5); // 创建一个固定大小的线程池 for (int i = 0; i < 10; i++) { Runnable task = () -> { System.out.println("Task executed by: " + Thread.currentThread().getName()); }; executorService.submit(task); // 提交任务到线程池 } executorService.shutdown(); // 关闭线程池 } } ``` #### 多线程同步与锁 在多线程环境中,多个线程可能同时访问共享资源,导致数据不一致的问题。为了解决这个问题,可以使用同步机制: - **synchronized关键字**:用于修饰方法或代码块,确保同一时间只有一个线程可以访问该方法或代码块。 - **Lock接口**:提供了比`synchronized`更灵活的锁机制,例如可重入锁、读写锁等。 #### 常见问题与解决方案 在多线程编程中,可能会遇到死锁、线程安全等问题。解决这些问题的关键在于合理设计线程间的协作机制,避免竞争条件的发生。 --- ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值