黑马程序员__JAVA基础__多线程

-------android培训java培训、java学习型技术博客、期待与您交流! ------------

1.概述

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

线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行。

多线程存在的意义:充分利用cpu的空闲时间,提高进程的整体运行效率。

 

2.创建线程的两种方式

第一种:继承Thread类

步骤:

    1.子类覆盖父类中的run方法,将线程运行的代码存放在run中。

    2.建立子类对象的同时线程也被创建。

    3.通过调用start方法开启线程。

示例代码:

class Demo extends Thread
{
	public void run()
	{
		for(int i = 0;i<60;i++)
		{
			System.out.println(“demo run---+”i);
		}
	}
}

class ThreadDemo
{
	public static void main(String[] args)
	{
			Demo d = new Demo();//创建一个线程
			d.start();//调用start方法执行该线程的run方法
	}
}

第二种:实现Runnable接口

步骤:

    1.子类覆盖接口中的run方法

    2.通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。

    3.Thread类对象调用start方法开启线程。

代码示例:

class Test implement Runnable
{
	public void run()//子类覆盖接口中的run方法
	{
		for(int x=0; x<60; x++)
		{
			System.out.println(“run..."+x);
		}
	}

}


class ThreadTest 
{
	public static void main(String[] args) 
	{
		Test te = new Test();实现Runnable接口的对象
		Thread t1 = new Thread(te);将那个对象作为参数传递到Thread构造函数
		Thread t2 = new Thread(te);
		t1.start();//调用Thread类的start方法开启线程
		t2.start();
	}
}

两种线程创建方式的区别:

    继承Thread类创建对象:

        1.Thread子类无法再从其它类继承(java语言单继承)。

        2.编写简单,run()方法的当前对象就是线程对象,可直接操作。

    使用Runnable接口创建线程:

        1.可以将CPU,代码和数据分开,形成清晰的模型。

        2.线程体run()方法所在的类可以从其它类中继承一些有用的属性和方法。

        3.有利于保持程序的设计风格一致。

    继承Thread类线程代码存放于Thread子类run方法中;

    实现Runnable接口线程代码存在于接口的子类run方法中,而且这种方式避免了单继承的局限性,在实际应用中,几乎都采取第二种方式。

 

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

答:因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该run方法所属的对象。

 

3.线程的四种状态


sleep方法需要指定睡眠时间,单位是毫秒。

冻结与运行之间的状态:准备就绪。具备了执行资格,但是没有执行权。

 

4.线程的对象获取与名称的操作

线程都有自己的默认名称,格式为:Thread-编号 该编号从0开始。

操作方法:

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

getName(): 获取线程名称。

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

示例代码:

 

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 ThreadTest 
{
	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);
		}
	}
}

 

5.线程的安全

导致安全问题的出现的原因:

    1.多个线程访问出现延迟

    2.线程随机性

线程安全问题在理想状态下,不容易出现,但是一旦出现对软件的影响是非常大的

解决方法:同步(synchronized)

格式:

    synchronized(对象)

    {

        需要同步的代码;

    }

同步可以解决安全问题的根本原因就是在那个对象上,该对象如同锁的功能。

4个窗口售票示例:

class Ticket implements Runnable
{
	private  int tick = 1000;
	Object obj = new Object();//建立一个obj类对象,供synchronized作参数
	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();//创建Ticket对象供Thread对象作构造函数的参数用

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		//开始4个线程
		t1.start();
		t2.start();
		t3.start();
		t4.start();

	}
}


同步的前提:

|---->同步需要两个或者两个以上的线程

|---->多个线程使用的是同一个锁

未满足这两个条件,不能称其同步。

同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行的效率。

同步函数

    格式:在函数上加上synchronized修饰符即可。

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

   所以同步函数使用的锁是this。

关于死锁

    同步中嵌套同步,但是锁却不同,就会产生死锁

 

如:A线程持有A锁,B线程持有B锁,让A线程去抢夺B锁,B线程去抢夺A锁

class Dead implements Runnable
{
	private booleanb = false;
	Dead(booleanb)
	{
		this.b = b;
	}
	public void run()
	{
		while(true)
		{
			if(b)
			{
				synchronized(Locks.locka)
				{//0线程,持有了A锁
					System.out.println(Thread.currentThread().getName()+"locka...+..if");
					//等待B锁
					synchronized(Locks.lockb)
					{
						System.out.println(Thread.currentThread().getName()+"lockb...+..if");
					}
				}
			}
			else
			{
				synchronized(Locks.lockb)
				{
					//1线程就进来了,持有了B锁
					System.out.println(Thread.currentThread().getName()+"lockb...+..else");
					synchronized(Locks.locka)//等待获得A锁
					{
						System.out.println(Thread.currentThread().getName()+"locka...+..else");

					}
				}
			}
		}
	}
}
//创造锁
class Locks
{
	public static Object locka = new Object();
	public static Object lockb = new Object();
}
class DeadLock
{
	public static void main(String[]args)
	{
		Dead d1 = new Dead(true);
		Dead d2 = new Dead(false);
		Thread t1 = new Thread(d1);
		Thread t2 = new Thread(d2);
		t1.start();
		t2.start();
	}
}

 

6.线程间通信

多个线程操作同一个资源,但是操作的动作不同

 

注意:wait();notify();notifyAll()都使用在同步中,因为要对持有监视器的线程操作,所以要使用在同步中,因为只有同步才具有锁。

问题:wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
    1.这些方法存在与同步中。
    2.使用这些方法时必须要标识所属的同步的锁。
    3.锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

注意:不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。


问题:wait(),sleep()有什么区别?
    wait():释放cpu执行权,释放锁。
    sleep():释放cpu执行权,不释放锁。

 

新特性:JDK1.5之后新增了java.until.concurrent.locks这个包为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。该框架允许更灵活地使用锁和条件,但以更难用的语法为代价。

生产和消费实例中升级:
JDK1.5 中提供了多线程升级解决方案。
    将同步Synchronized替换成现实Lock操作。
    将Object中的wait,notify notifyAll,替换了Condition对象。
    该对象可以Lock锁 进行获取。
    该示例中,实现了本方只唤醒对方操作。

代码如下:

import java.util.concurrent.locks.*;

class ProducerConsumerDemo 
{
	public static void main(String[] args) 
	{
		Resource r = new Resource();

		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);

		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);

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

	}
}

class Resource
{
	private String name;
	private int count = 1;
	private boolean flag = false;
	private Lock lock = new ReentrantLock();

	private Condition condition_pro = lock.newCondition();
	private Condition condition_con = lock.newCondition();


	public  void set(String name)throws InterruptedException
	{
		lock.lock();
		try
		{
			while(flag)
				condition_pro.await();
			this.name = name+"--"+count++;

			System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
			flag = true;
			condition_con.signal();
		}
		finally
		{
			lock.unlock();//释放锁的动作一定要执行。
		}
	}
	public  void out()throws InterruptedException
	{
		lock.lock();
		try
		{
			while(!flag)
				condition_con.await();
			System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
			flag = false;
			condition_pro.signal();
		}
		finally
		{
			lock.unlock();
		}
		
	}
}

class Producer implements Runnable
{
	private Resource res;

	Producer(Resource res)
	{
		this.res = res;
	}
	public void run()
	{
		while(true)
		{
			try
			{
				res.set("+商品+");
			}
			catch (InterruptedException e)
			{
			}
			
		}
	}
}

class Consumer implements Runnable
{
	private Resource res;

	Consumer(Resource res)
	{
		this.res = res;
	}
	public void run()
	{
		while(true)
		{
			try
			{
				res.out();
			}
			catch (InterruptedException e)
			{
			}
		}
	}
}

 

7.停止线程

1.定义循环结束标记(run方法结束)
    因为线程运行代码一般都是循环,只要控制了循环即可。
2.使用interrupt(中断)方法。
    该方法是结束线程的冻结状态,使线程回到运行状态中来。
注:stop方法已经过时不再使用。

 

特殊情况:
    当线程处于了冻结状态,就不会读取到标记。那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。

    Thread类提供该方法 interrupt();

测试代码:

class StopThread implements Runnable
{
	private boolean flag =true;
	public synchornized void run()
	{
		while(flag)
		{
			try
			{
				wait();//调用线程等待
			}
			catch(InterruptedException e)
			{
				System.out.println(Thread.currentThread().getName()+"...Exception");
				flag = false;//在异常中控制线程的运行
			}
			System.out.println(Thread.currentThread().getName()+"....run");
		}
	}
	public void changeFlag()
	{
		flag = false;
	}
}

class  StopThreadDemo
{
	public static void main(String[] args) 
	{
		StopThread st = new StopThread();
		
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);


		t1.setDaemon(true);
		t2.setDaemon(true);
		t1.start();
		t2.start();

		int num = 0;

		while(true)
		{
			if(num++ == 60)
			{
				//st.changeFlag();
				t1.interrupt();//调用interrupt方法来中断线程,抛出异常
				t2.interrupt();
				break;
			}
			System.out.println(Thread.currentThread().getName()+"......."+num);
		}
		System.out.println("over");
	}
}


8.线程类的其他方法

守护线程

    setDaemon():将该线程标记为守护线程或用户线程,守护线程就相当于后台线程,当前台线程结束时,后台线程跟着也结束。

    注意:该方法必须在启动线程前调用。
          该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
用法示例代码如下:

class StopThread implements Runnable
{
	public  void run()//原本run方法是一个死循环,但是将线程定义为守护线程后,主线程结束,访问run方法的线程立即也结束了
	{
		while(true)
		{		
			System.out.println(Thread.currentThread().getName()+"....run");
		}
	}
}
class  StopThreadDemo
{
	public static void main(String[] args) 
	{
		StopThread st = new StopThread();	
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);
		//将t1,t2定义为守护线程
		t1.setDaemon(true);
		t2.setDaemon(true);
		t1.start();//开始线程
		t2.start();
		int num = 0;
		while(true)
		{
			if(num++ == 60)
			{
				break;
			}
			System.out.println(Thread.currentThread().getName()+"......."+num);
		}
		System.out.println("over");//主线程结束,t1,t2也结束
	}
}


join()

抢夺cpu执行权,适用于临时加入线程用,先执行完,其他线程才执行。

测试代码如下:

class Demo implements Runnable
{
	public void run()
	{
		for(int x=0; x<70; x++)
		{
			System.out.println(Thread.currentThread().getName()+"....."+x);
		}
	}
}
class  JoinDemo
{
	public static void main(String[] args) throws Exception
	{
		Demo d = new Demo();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();
		t1.join();//t1获取cpu的执行权,主线程处于冻结状态,只有t1结束主线程才能恢复运行状态
		t2.start();
		//t1.join();主线程冻结,t1,t2交替运行,t1结束,主线程才继续
		for(int x=0; x<80; x++)
		{		}
		System.out.println("over");
	}
}


setPriority()

更改线程的优先级。三个优先级分别为:

    MAX_PRIORITY(最高优先级,10)

    MIN_PRIORITY(最低优先级,1)

    NORM_PRIORITY(默认优先级,5)

首先调用线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException。在其他情况下,线程优先级被设定为指定的newPriority 和该线程的线程组的最大允许优先级相比较小的一个。

 

yeild()

暂停当前正在执行的线程对象,并执行其他线程。(使线程交替执行)

 

个人总结:

    线程的运行状态与线程之间的通信个人很难理解,可能还得多多写代码操练才行,线程的操作给我的感觉就是你必须要考虑得很全面,每个线程的执行状态,每个线程读取的数据什么的都必须要考虑清楚,这要才能避免线程的安全问题。还是觉得线程这一块比较难,得多下点工夫。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值