黑马程序员---java基础之多线程

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

多线程的概念

进程:正在执行的程序。

线程:是进程中用于控制程序执行的控制单元(执行路径,执行情景)

一个进程中至少有一个线程。

对于JVM,启动时,只好有两个线程:jvm的主线程。jvm的垃圾回收线程。

线程的状态。

1,被创建。

2,运行。

3,冻结。

4,消亡。(也就是结束)

其实还有一种特殊的状态:临时状态。

临时状态的特点:具备了执行资格,但不具备执行权.

冻结状态的特点:放弃了执行资格。

自定义线程(线程的创建):

Java给我们提供了对象线程这类事物的描述。该类是Thread

该类中定义了,

创建线程对象的方法(构造函数).

提供了要被线程执行的代码存储的位置(run()

还定义了开启线程运行的方法(start()).

同时还有一些其他的方法用于操作线程:

static Thread currentThead():

String getName():

static void sleep(time)throws InterruptedException:

线程中要运行的代码都是后期定义的。

创建线程的第一种方式是:

继承Thread类。原因:要覆盖run方法,定义线程要运行的代码。

步骤:

1,继承Thread类。

2,覆盖run方法。将线程要运行的代码定义其中。

3,创建Thread类的子类对象,其实就是在创建线程,调用start方法。

public class Main {
	public static void main(String[] args) {
		MyThread thread = new MyThread();//创建自定义线程对象
		thread.start();//调用run方法
	}

}
class MyThread extends Thread{//继承Thread类
	@Override
	public void run() {//复写run方法
		for(int i=0;i<10;i++){
			System.out.println("i = "+i);
		}
	}
}

方式一的简写形式(匿名内部类)

new Thread(){
			@Override
			public void run() {
				for(int i=0;i<10;i++){
					System.out.println("i = "+i);
				}
			}
		}.start();

创建线程的第二种方式

实现Runnable接口。

步骤:

1,定义了实现Runnable接口。

2,覆盖接口的run方法。将多线程要运行的代码存入其中。

3,创建Thread类的对象(创建线程),并将Runnable接口的子类对象作为参数传递给Thread的构造函数。

为什么要传递?因为线程要运行的代码都在Runnable子类的run方法中存储。所以要将该run方法所属的对象传递给Thread。让Thread线程去使用该对象调用其run方法。

4,调用Thread对象的start方法。开启线程。

class MyThread implements Runnable{
	@Override
	public void run() {
		for(int i=0;i<10;i++){
			System.out.println("i = "+i);
		}
	}
}

public class Main {
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		Thread thread = new Thread(myThread);
		thread.start();
	}
}

方式二的简写形式(匿名内部类)

new Thread(new Runnable(){
			@Override
			public void run() {
				for(int i=0;i<10;i++){
					System.out.println("i = "+i);
				}
			}
		}).start();

两种方式的特点:

实现方式,因为避免了单继承的局限性,所以创建线程建议使用第二种方式。

综合示例:卖票小程序,

class Ticket implements Runnable{	
  private  int tick = 100;
	public void run(){
		while(tick>0){//只要还有票就继续卖
				System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
			}
		}
	}
}
class  Main{
	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();
	}
}

以上小程序多次运行后会出现0号票乃至-1号票,容易出现安全问题

线程间同步

因为多线程具备随机性。因为是由cpu不断的快速切换造成的。

就有可能会产生多线程的安全问题。

问题的产生的原因:

1,多线程代码中有操作共享数据。

2,多条语句操作该共享数据。

有一个线程对多条操作共享数据的代码执行的一部分。还没有执行完,另一个线程开始参与执行。就会发生数据错误。

解决方法:

当一个线程在执行多条操作共享数据代码时,其他线程即使获取了执行权,也不可以参与操作。

Java就对这种解决方式提供了专业的代码。

Synchronized:

同步的原理:就是将部分操作功能数据的代码进行加锁。

例如:将前面的买票程序加同步

class Ticket implements Runnable{	
  private int tick = 100;
	public synchronized void run(){//同步函数的形式
		while(tick>0){//只要还有票就继续卖
  //synchronized(this){//同步代码块的形式
  If(tick>0){
					System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
  }
  //}
			}
		}
	}
}
class  Main{
	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();
	}
}

同步的表现形式:

1,同步代码块。

2,同步函数。

两者有什么不同:

同步代码块使用的锁是任意对象。

同步函数使用的锁是this

注意:对于static的同步函数,使用的锁不是this。是 类名.class 是该类的字节码文件对象。

单例设计模式的懒汉式也要用到同步,不然会有安全问题

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

同步的弊端:

较为消耗资源。

同步嵌套后,容易死锁。

同步使用的前提:

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

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

这是才可以称为这些线程被同步了。

特别注意:同步中要避免嵌套同步,容易死锁

例如:

class DeadLock{
	public static void main(String[] args)	{
		Dead d = new Dead();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();
		t2.start();
	}
}
class Dead implements Runnable{
	boolean flag = true;
	public void run(){
		if (flag){
			flag = false;
			while(true){
				synchronized(MyLock.locka)//这里用的是A对象锁
				{
					System.out.println(Thread.currentThread().getName()+":if locka");
					synchronized(MyLock.lockb)这里用的是B对象锁
					{
						System.out.println(Thread.currentThread().getName()+":if lockb");
					}
				}
			}
		}
		else
		{
			while (true){
				synchronized(MyLock.lockb)//这里用的是B对象锁
				{
					System.out.println(Thread.currentThread().getName()+":else lockb");
					synchronized(MyLock.locka)这里用的是A对象锁
					{
						System.out.println(Thread.currentThread().getName()+":else locka");
					}
				}
			}
		}
	}

}
class MyLock//自定义锁对象
{
	static MyLock locka = new MyLock();//锁A
	static MyLock lockb = new MyLock();//锁B
}

由于A锁中嵌套了B,B锁中嵌套了A,很容易出现两个线程拿着各自的锁要对方的锁,造成死锁

JDK1.5 中提供了多线程升级解决方案。

将同步Synchronized替换成现实Lock操作。

Lock:替代了Synchronized

lock 上锁

Unlock 释放锁

newCondition()

释放锁的动作一定要执行.

线程间的通信:

等待/唤醒机制。

也就是常见的生产者消费者问题。

1.当多个生产者消费者出现时,

需要让获取执行权的线程判断标记。

通过while完成。

2.需要将对方的线程唤醒。

仅仅用notify,是不可以的。因为有可能出现只唤醒本方。

有可能会导致,所有线程都等待。

所以可以通过notifyAll的形式来完成 。

wait:进入等待状态

notify();唤醒线程池中等待的第一个线程

notifyAll();唤醒线程池中所有等待线程

都使用在同步中,因为要对持有监视器()的线程操作。

所以要使用在同步中,因为只有同步才具有锁。

class Res{//定义一个缓冲区,同时也有锁对象的作用
	String name;
	String sex;
	boolean flag = false;
}
class Input implements Runnable{
	private Res r ;
	Input(Res r){
		this.r = r;
	}
	public void run(){
		int x = 0;
		while(true){
			synchronized(r){
				if(r.flag)//如果缓冲区有数据,就等待
					try{r.wait();}catch(Exception e){}
				if(x==0){
					r.name="mike";
					r.sex="man";
				}
				else{
					r.name="张三";
					r.sex = "男男男男男";
				}
				x = (x+1)%2;//写入值转换标记
				r.flag = true;
				r.notify();//唤醒消费线程
			}
		}
	}
}
class Output implements Runnable{//消费者线程
	private Res r ;	
	Output(Res r){//将传入的缓冲区对象赋值
		this.r = r;
	}
	public void run(){
		while(true){
			synchronized(r){
				if(!r.flag)/如果缓冲区没数据,就等待,否则就打印,同时改变标记
					try{r.wait();}catch(Exception e){}
				System.out.println(r.name+"...."+r.sex);
				r.flag = false;
				r.notify();//唤醒生成线程
			}
		}
	}
}
class  Main{
	public static void main(String[] args) 	{
		Res r = new Res();//定义一个统一的锁对象,同时也是缓冲区的作用
		Input in = new Input(r);//创建生产者
		Output out = new Output(r);//创建消费者
		Thread t1 = new Thread(in);创建生成线程
		Thread t2 = new Thread(out);/创建消费线程
		t1.start();
		t2.start();
	}
}

对于多个生产者和消费者。

为什么要定义while判断标记。

原因:让被唤醒的线程再一次判断标记。

为什么定义notifyAll

因为需要唤醒对方线程。

因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待

JDK1.5版本提供了一些新的对象,优化了等待唤醒机制。

1,将synchronized 替换成了Lock接口。

将隐式锁,升级成了显示锁。

Lock

获取锁:lock();

释放锁:unlock();注意:释放的动作一定要执行,所以通常定义在finally中。

获取Condition对象:newCondition();

2,将Object中的waitnotifynotifyAll方法都替换成了ConditionawaitsignalsignalAll

和以前不同是:一个同步代码块具备一个锁,该所以具备自己的独立waitnotify方法。

现在是将waitnotify等方法,封装进一个特有的对象Condition,而一个Lock锁上可以有多个Condition对象。

例如:生产者消费者

class Main{
	public static void main(String[] args){
		final Demo d = new Demo();
		new Thread(){//消费者线程
			public void run() {
				while(true)
				d.get();
			}
		}.start();
		new Thread(){//生产者线程
			@Override
			public void run() {
				while(true)
				d.set();
			}
		}.start();
	}
}

class Demo{
	private boolean flag = true;//转换标记
	private int num = 0;
	private Lock lock = new ReentrantLock();//定义一个锁lock
	Condition con_get = lock.newCondition();//定义一个condition对象con_get
	Condition con_set = lock.newCondition();//定义一个condition对象con_set
	public void get(){
		lock.lock();
		try {
			while(flag)//如果是空就等待
				con_get.await();
			System.out.println("消费者"+num+"******");
			flag = true;//改变标记
			con_set.signal();//唤醒con_set
		} catch(Exception e){
			
		}finally {
			lock.unlock();
		}
	}
	public void set(){
		lock.lock();
		try {
			while(!flag)//如果不空就等待
				con_set.await();
			System.out.println("生产者"+(num++)+"***");
			flag = false;//改变标记
			con_get.signal();//唤醒con_get
		} catch(Exception e){
		}finally {
			lock.unlock();
		}
	}
}

停止线程

stop过时。

原理:run方法结束。run方法中通常定义循环,指定控制住循环线程即可结束。

1,定义结束标记。

2,当线程处于了冻结状态,没有执行标记,程序一样无法结束。

这时可以循环,正常退出冻结状态,或者强制结束冻结状态。

强制结束冻结状态:interrupt();目的是线程强制从冻结状态恢复到运行状态。

但是会发生InterruptedException异常。

线程中一些常见方法:

yield():临时暂停,可以让线程是释放执行权。

setDaemon(boolean):将线程标记为后台线程,后台线程和前台线程一样,开启,一样抢执行权运行,

只有在结束时,有区别,当前台线程都运行结束后,后台线程会自动结束。

join():什么意思?等待该线程结束。当A线程执行到了B.join方法时,A就会处于冻结状态。

A什么时候运行呢?当B运行结束后,A就会具备运行资格,继续运行。

加入线程,可以完成对某个线程的临时加入执行。

waitsleep的区别:

wait:释放cpu执行权,释放同步中锁。

sleep:释放cpu执行权,不释放同步中锁。


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值