Java 多线程总结

java多线程总结


  • 进程与线程
  • 实现方法
  • run()和start()
  • 线程运行状态
  • 多线程的安全问题
  • 死锁
  • 等待唤醒机制
  • 停止线程
  • 守护线程join()方法 线程优先级和yield方法

1.进程与线程

进程就是操作系统上运行的一个任务,拿windows系统来讲,每个运行的程序就是一个进程。线程就是在进程中多个并发执行的任务。比如说一个播放器在播放电影的时候,可以一边播放一边下载。那播放与下载就属于进程中两个并发执行的线程。

2.实现方法

线程实现方法有两种,一种是直接继承Thread类,一种是实现Runnable接口。这两种实现方式的区别在与设计模式上。Java只支持单一的继承,因此当一个类如果已经有了父类,那在想实现多线程的话就要用到Runnable接口了。

继承Thread类实现如下:

public class ThreadTest extends Thread{
	
	private int count = 0;
	public void run()
	{
		while(true)
		{
			System.out.println(count++);
			if( count > 100)
				return;
		}
		
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ThreadTest ts = new ThreadTest();
		ts.start();

	}
用Runnable接口实现如下:

package com.zs.day1;

public class ThreadTest2
{

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MyThread t1 = new MyThread();
		Thread th1 = new Thread(t1);
		th1.start();

	}

}
class MyThread implements Runnable
{

	private int count = 0;
	@Override
	public void run() 
	{
		// TODO Auto-generated method stub
		while(true)
		{
			System.out.println(count++);
			if( count > 100)
				return;
		}
		
	}
	}
3.run()和 start()方法

在线程启动的时候应该使用线程的start()方法,因为这个方法会让该线程与别的线程一起被cpu交替执行,如果调用run()方法,那么这只是一个普通方法的调用,并不涉及到线程的并发执行。也就是说调用run()方法,就是单线程,那么run()方法后面的代码在run()方法执行完之前是得不到执行的,而调用start()方法会并发执行主线程与该线程中的代码。

4.线程的运行状态

运行状态分为:

a.新建状态:创建一个线程对象。

b.就绪状态:该线程对象调用了start()方法。在这个状态下,该线程拥有了执行的权利和其他线程共同争夺cpu执行权。

c.执行状态:线程正在被cpu所执行。

d.阻塞状态:由于一些原因,线程被挂起,也就是放弃了cpu的执行权,一直等待直到再次回到就绪状态。阻塞的原因有三种:

第一,锁对象调用了wait()方法,导致了该线程阻塞。第二,在访问共享资源的时候,得不到锁,导致线程阻塞。第三,其他阻塞,比如调用sleep()方法。

e.死亡状态:执行完毕,或者是由于异常退出了执行run()方法。也就是说run()方法不在执行的状态。

5.多线程的安全问题

多线程会出现安全问题,一般是在多线程要访问同一资源的时候出现问题。下面有一个火车站卖票的案例,假设火车站有100张票,而要有多个窗口同时卖票。代码如下:

class Tikie implements Runnable
{
	private int  tik = 100;
	public void run()
	{

		while(true)
		{
			if(tik<0)
			{
				System.out.println(Thread.currentThread().getName()+"票已卖完");
				Thread.currentThread().stop();//已废弃
			}
			else
				System.out.println(Thread.currentThread().getName()+"----"+tik--);
		}

	}
}
class Myclass 
{
	public static void main(String[] args) 
	{
		System.out.println("Hello World!");
		
		Tikie tik1 = new Tikie();
		Thread thread1 = new Thread(tik1);
		Thread thread2 = new Thread(tik1);
		Thread thread3 = new Thread(tik1);
		Thread thread4 = new Thread(tik1);
		thread1.start();
		thread2.start();
		thread3.start();
		thread4.start();
	}
}
打印就会发现有可能会卖出负的票,为什么会出现问题?原因是因为开启了四个线程同时执行run()中的代码。而所谓的同时执行就是cpu在个个线程中快速的做切换,在一个时间片当中,cpu其实还只是处理了一个线程,只不过它在做快速的切换会让我们误以为是在同时执行。

那么既然是cpu切换执行,就有可能出现一种情况,就是当 判断完tik是大于零的时候就要执行tik--的时候,别的线程抢到了cpu的执行权,而该线程被迫处于等待下一次执行的状态。这时候另外的线程执行了tik--的操作,而后该线程获得了执行的权利,从tik--开始执行。如果该线程上一次执行判断的时候是0即将执行tik--,这时候被别的线程抢去了执行权,那么别的线程又完成了tik--的操作之后该线程又获得了cpu的执行权,则该线程要从tik--开始执行,那么就会又执行一次tik--,则就会出现负数的情况。

那么产生这种安全问题的原因就是因为多个线程在对共享资源进行操作的时候,线程并没有执行一次完整的操作就被切换了,从而导致了数据的错乱。那么怎么问题的方法是什么?java引入了同步来解决这个问题,代码如下:

class Tikie implements Runnable
{
	private int  tik = 100;
	Object obj = new Object();
	public void run()
	{
		synchronized(obj)
		{
			while(true)
			{
				if(tik<0)
				{
					System.out.println(Thread.currentThread().getName()+"票已卖完");
					Thread.currentThread().stop();//已废弃
				}
				else
					System.out.println(Thread.currentThread().getName()+"----"+tik--);
			}
		}
	}
}
使用synchronized关键字可以使代码同步,也就是说被synchronized关键字所修饰的代码块当中所有的代码必须一起执行完之后,该线程才有可能被切换。

synchronized中的参数就是锁对象,他可以是任何的对象。所谓锁就是一个对象,只有线程拥有锁之后才有权利去执行被锁所锁住的代码,否则不能执行。也就是说如果在同步的情况下,多个线程只有获得锁的那个才能执行同步代码中的内容。

同步也可以修饰方法,当修饰方法的时候那么它的锁对象就是该方法所在类的本身这个对象,也就是this。如果修饰的是static方法的话,那么锁对象就是该类的.class对象,也就是Class的实例。

一般有一个共享资源的时候,我们的锁对象要是同一个,不然也会出现数据错乱的情况。而假如有两个共享资源A和B,对这两个资源的操作互不干涉,那么就要两把锁。当有多把锁并且这些锁在相互嵌套的时候就会出现死锁的情况。

6.死锁

死锁出现的情况就是:同步代码块出现嵌套,线程1持有A锁需要B锁,而线程2持有B锁需要A锁,这样两个线程由于其中一个处在同步代码块中得不到锁B而不能完成A锁同步代码块中的代码释放A锁,而导致别的线程得不到A锁不能执行A锁代码块中的B锁的代码而释放B锁,从而导致了线程之间的僵持引起程序不能往下运行的情况。代码如下:

class LockDemo implements Runnable
{
	private int i = 0;
	public void run()
	{
		
		while(true)
		{
			synchronized (Lock.locka)
			{
				System.out.println("locka-----"+i);
				synchronized(Lock.lockb)
				{
					System.out.println("lockb-------"+i);
					i++;
					if(i > 10000)
						Thread.currentThread().stop();
				}
			}
			synchronized (Lock.lockb)
			{
				System.out.println("locka-----"+i);
				synchronized(Lock.locka)
				{
					System.out.println("lockb-------"+i);
					i++;
					if(i > 10000)
						Thread.currentThread().stop();
				}
			}
			
		}
		
	}

}

class Lock
{
	public static Object locka = new Object();
	public static Object lockb = new Object();

}
class LockTest
{
	public static void  main(String[] arg)
	{
		LockDemo demo = new LockDemo();
		
		Thread thread1 = new Thread(demo);
		Thread thread2 = new Thread(demo);
		thread1.start();
		thread2.start();

	}
}
/*造成死锁的原因是因为当有多个共有的资源的时候 需要多把锁
	而多把锁在不同的访问顺序去访问这些资源的时候就会造成死锁
	解决办法就是 有多个共有的资源的时候就要把这些 线程在访问这些资源的顺序排成一致这样就可以避免死锁
*/

避免死锁的方法就是访问这些不同资源的时候使线程之间的访问顺序保持一致就行了。

7.等待唤醒机制

生产者消费者的例子:当需要生产者生产一个商品而消费者消费一个的时候需要用到等待唤醒机制。代码如下:

/*
等待唤醒机制
如果是真 就等 在最后的时候唤醒 
等待与唤醒都是在有锁的情况下才可以使用的
也就是说等待与唤醒只能作用于一个锁上的线程

*/
class Test 
{
	public static void main(String[] args) 
	{
		Res s = new Res();
		Input input = new Input(s);
		Output output = new Output(s);

		Thread t1 = new Thread(input);
		Thread t2 = new Thread(output);

		t1.start();
		t2.start();
	}
}

class Res
{
	String name;
	String sex;
	Boolean flag = false;
}

class Input implements Runnable
{
	private Res s;
	public Input(Res s)
	{
		this.s = s;
	}
	public void run()
	{
		int i = 0;
		while(true)
		{
			synchronized(s)
			{
				if(s.flag)
					try{s.wait();}catch(Exception e){}

				if(i == 0)
				{
					s.name = "lisi";
					s.sex = "man";
				}
				else
				{
					s.name = "张女";
					s.sex = "女";
				}
				s.flag = true;
				i = (i+1)%2;
				s.notify();
				
			}
		}
	}
}
class Output implements Runnable
{
	private Res s;
	public Output(Res s)
	{
		this.s = s;
	}
	public void run()
	{
		while(true)
		{
			
			synchronized(s)
			{
				if(!s.flag)
					try{s.wait();}catch(Exception e){}
				System.out.print(s.name+"______");
				System.out.println(s.sex);
				s.flag = false;
				
				s.notify();
			}
			
		}
	}
}

这是只有一个生产者消费者的情况,当有多个生产者消费者的时候就会出现安全问题。原因就是nofify()唤醒的是线程池中的一个最先wait()的线程,这时候有可能唤醒一个生产者,从而导致上个生产的商品没有被消费。

解决办法如下:

/*
有多个生产者和消费者的时候要记得 在循环中一直判断wait()  然后唤醒的时候唤醒所有
另外唤醒的唤醒顺序是跟根据在 线程池中的队列顺序唤醒的 , 也就是说 先唤醒最先等待的线程
*/
class Test 
{
	public static void main(String[] args) 
	{
		Resource s = new Resource();
		Produce p = new Produce(s);
		Consumer c = new Consumer(s);
		
		Thread t1 = new Thread(p);
		Thread t2 = new Thread(p);
		Thread t3 = new Thread(c);
		Thread t4 = new Thread(c);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

class Resource
{
	private String name;
	private int count = 1;
	private boolean flag = false;

	public synchronized void set(String name)
	{
		while(flag)
		{
			try
			{
				this.wait();
			}
			catch (Exception e)
			{
			}
		}

		this.name = name + "------" + count++;
		System.out.println(Thread.currentThread().getName()+"生产者生产----" + this.name);
		flag = true;
		this.notifyAll();
	}
	public synchronized void get()
	{
		while(!flag)
		{
			try
			{
				this.wait();
			}
			catch (Exception e)
			{
			}
		}

		System.out.println(Thread.currentThread().getName()+"---------消费者消费"+ name);
		flag = false;
		this.notifyAll();
	}
}

class Produce implements Runnable
{
	private Resource s ;
	public Produce(Resource s)
	{
		this.s = s;
	}
	public void run()
	{
		while(true)
		{
			s.set("zzz");
		}

	}
}
class Consumer implements Runnable
{
	private Resource s;
	public Consumer (Resource s)
	{
		this.s = s;
	}
	public void run()
	{
		while(true)
		{
			s.get();
		}

	}
	
}
这时候唤醒的是所有线程。5.0版本新增加了Lock接口,可以唤醒在同一个锁下面的属于不同的Condition对象的线程,代码如下:
/*
这个列子
 在一个资源中 创建一个锁对象
 在对这些资源对象进行多线程操作的时候都要把对资源的操作上锁,以防止数据的错误。
 创建对象的方法是  ReentrantLock()  lock.lock() 是锁 lock.unlock()是释放锁。
 而这个锁对象可以讲持有它的线程分类  也就是用 lock.newCondition()方法获取该锁的condition条件
 而用该对象进行的await()方法 和 signal()方法都将会唤醒拥有该对象的线程 而并非用synchronized中的notifyall唤醒线程池中的所有线程了 这样可以提升了多线程下 消费者与生产者的效率问题
*/
import java.util.concurrent.locks.*;
class Test 
{
	public static void main(String[] args) 
	{
		Resource s = new Resource();
		Produce p = new Produce(s);
		Consumer c = new Consumer(s);
		
		Thread t1 = new Thread(p);
		Thread t2 = new Thread(p);
		Thread t3 = new Thread(c);
		Thread t4 = new Thread(c);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
class Resource
{
	private String name;
	private int count = 1;
	private boolean flag = false;
	private ReentrantLock lock = new ReentrantLock();
	private Condition con = lock.newCondition();
	private Condition pro = lock.newCondition();



	public void set(String name)
	{
		try
		{
			lock.lock();
			while(flag)
			{
				con.await();
			}
			this.name = name + "------" + count++;
			System.out.println(Thread.currentThread().getName()+"生产者生产----" + this.name);
			flag = true;
			pro.signalAll();
		}
		catch (Exception e)
		{
		}
		finally
		{
			lock.unlock();
		}
		
	}
	public void get()
	{
		try
		{
			lock.lock();
			while(!flag)
			{
				pro.await();
				
			}

			System.out.println(Thread.currentThread().getName()+"---------消费者消费"+ name);
			flag = false;
			con.signalAll();
		}
		catch (Exception e)
		{
		}
		finally
		{
			lock.unlock();

		}
		
	}
}

class Produce implements Runnable
{
	private Resource s ;
	public Produce(Resource s)
	{
		this.s = s;
	}
	public void run()
	{
		while(true)
		{
			s.set("zzz");
		}

	}
}
class Consumer implements Runnable
{
	private Resource s;
	public Consumer (Resource s)
	{
		this.s = s;
	}
	public void run()
	{
		while(true)
		{
			s.get();
		}

	}
	
}
 当有多个lock的时候也会有死锁现象。

8.停止线程

停止线程一般是结束run()方法,一个方法就是改变while()中flag的值。interrupt()方法是打断正在阻塞的线程让其恢复到运行状态,但是会抛出InterrupttedException,当挂起的时候,可以调用interrupt方法在catch中对flag进行值改变。

9.守护线程join()方法 线程优先级和yield方法

守护线程,就是setDaemon(true)方法设置线程是否为守护线程,所谓守护线程就是依赖于别的线程的线程,如果程序只有守护线程,那么程序将结束。join()方法就是等待该线程执行完之后主线程才会活过来。就是临时加入一个运算,运算完之后再执行别的线程。setPriority()设置线程的优先级。yield()临时释放执行权。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值