Java之多线程(二)

一、线程的同步与死锁

1.如:多个线程同时卖票

class MyThread implements Runnable{
	private int ticket = 10;
	@Override
	public void run() {
		while(this.ticket > 0) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + ",还有" + this.ticket-- + "张票");
		}
	}
}
public class Test{
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		new Thread(myThread,"A").start();
		new Thread(myThread,"B").start();
		new Thread(myThread,"C").start();
	}
}
经过多次运行,票数有可能会出现负数,这就是不同步。不同步的唯一好处就是处理速度快(多个线程并发执行)。
2.同步处理
指的是所有的线程不是一起进入到方法中执行,而是按照顺序一个一个进来。
(1)synchronized处理同步问题
使用synchronized可以实现这把“锁”,它有两种模式:同步代码块、同步方法。
·使用同步代码块:

如果要使用同步代码块必须设置一个要锁定的对象,所以一般可以锁定当前对象:this

class MyThread implements Runnable{
	private int ticket = 100;
	@Override
	public void run() {
		for(int i = 0;i < 1000;i++) {
			synchronized (this) {
				if(this.ticket > 0) {
					try {
						Thread.sleep(20);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + ",还有" + this.ticket-- + "张票");
				}
			}
		}
	}
}
public class Test{
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		new Thread(myThread,"A").start();
		new Thread(myThread,"B").start();
		new Thread(myThread,"C").start();
	}
}

·同步方法

class MyThread implements Runnable{
	private int ticket = 1000;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i = 0;i < 1000;i++) {
			this.sale();
		}
	}
	public synchronized void sale() {
		if(this.ticket > 0) {
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + ",还有" + this.ticket-- + "张票");
		}
	}
}
public class Test{
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		new Thread(myThread,"A").start();
		new Thread(myThread,"B").start();
		new Thread(myThread,"C").start();
	}
}
同步可以保证数据的完整性(线程安全操作),但是其执行的速度会很慢。
实际上,synchronized(this)以及非static的synchronized方法,只能防止多个线程同时执行同一个对象的代码段。即synchronized锁住的是括号里的对象,而不是代码。对于非static的synchronized方法,锁的就是对象本身也就是this。
当synchronized锁住一个对象后,别的线程如果也想拿到这个对象的锁,就必须等待这个线程执行完成释放锁,才能再次给对象加锁,这样才能达到线程同步的目的。即使两个不同的代码段,都要锁同一个对象,那么这两个代码段也不能在多线程环境下同时运行。
锁住这段代码,有两种方法:
·锁住同一个对象

如:

class Sync {
	public void test() {
		synchronized (this) {
			System.out.println("test方法开始,当前线程为 " + Thread.currentThread().getName());
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("test方法结束,当前线程为 " + Thread.currentThread().getName());
		}
	}
}
class MyThread extends Thread{
	private Sync sync;
	public MyThread(Sync sync) {
		this.sync = sync;
	}
	@Override
	public void run() {
		this.sync.test();
	}
}
public class Test{
	public static void main(String[] args) {
		Sync sync = new Sync();
		for(int i = 0;i < 3;i++) {
			MyThread myThread = new MyThread(sync);
			myThread.start();
		}
	}
}
·让synchronized锁这个类对应的class对象

如:

class Sync {
	public void test() {
		synchronized (Sync.class) {
			System.out.println("test方法开始,当前线程为 " + Thread.currentThread().getName());
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("test方法结束,当前线程为 " + Thread.currentThread().getName());
		}
	}
}
class MyThread extends Thread{
	@Override
	public void run() {
		Sync sync = new Sync();
		sync.test();
	}
}
public class Test{
	public static void main(String[] args) {
		for (int i = 0; i < 3; i++) {
			MyThread myThread = new MyThread();
			myThread.start();
		}
	}
}
上面的synchronized(Sync.class)实现了全局锁的效果。因此,如果想要锁的是代码段,锁住多个对象的同一方法,使用这种全局锁,锁的是类而不是this。
static synchronized方法,static方法可以直接类名加方法名调用,方法中无法使用this,所以它锁的不是this,而是Class对象,所以,static synchronized方法也相当于全局锁,相当于锁住了代码段。
3.Lock锁

如:使用ReentrantLock进行同步处理

class Mythread implements Runnable {
	private int ticket = 500;
	private Lock ticketLock = new ReentrantLock();

	@Override
	public void run() {
		for (int i = 0; i < 500; i++) {
			ticketLock.lock();
			if (this.ticket > 0) {
				try {
					Thread.sleep(20);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + ",还有" + this.ticket-- + "张票");
			}
			ticketLock.unlock();
		}
	}
}
public class Test {
	public static void main(String[] args) {
		Mythread mythread = new Mythread();
		new Thread(mythread, "A").start();
		new Thread(mythread, "B").start();
		new Thread(mythread, "c").start();
	}
}
在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。到了JDK1.6,对synchronized加入了很多优化措施,导致在JDK1.6中synchronized的性能并不比Lock差,所以,优先考虑使用synchronized。
4.死锁
同步的本质在于,一个线程等待另一个线程执行完毕后才可以继续执行。但如果现在相关的几个线程都在等待着,那么就会造成死锁。死锁一旦出现,整个程序就将中断执行,所以死锁属于严重性问题。过多的同步会造成死锁,对于资源的上锁一定注意不要成“环”。
二、wait()方法和notify()方法
1.wait()方法
wait()方法就是使线程停止运行。
(1)方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法是用来将线程置入“预执行队列”中,并且在wait()所在的代码处停止执行,直到接到通知或被中断为止。
(2)wait()方法只能在同步方法中或同步块中调用。如果调用wait()时,没有持有适当的锁,会抛出异常。
(3)wait()方法执行后,当前线程释放锁,线程与其他线程竞争重新获取锁。

如:观察wait()方法使用

public class Test{
	public static void main(String[] args) throws InterruptedException {
		Object object = new Object();
		synchronized (object) {
			System.out.println("等待中");
			object.wait();
			System.out.println("等待已过");
		}
		System.out.println("main方法结束");
	}
}
这样在执行到object.wait();之后就一直等待下去。
2.notify()方法
notify()方法就是使停止的线程继续运行。
(1)方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其他线程,对其发出通知notify,并使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程规划器随机挑选出一个呈wait状态的线程。
(2)在notify方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

如:使用notify()方法唤醒线程

class MyThread implements Runnable {
	private boolean flag;
	private Object object;

	public MyThread(Boolean flag, Object object) {
		this.flag = flag;
		this.object = object;
	}

	public void waitMethod() {
		synchronized (object) {
			try {
				while (true) {
					System.out.println("wait()方法开始:" + Thread.currentThread().getName());
					object.wait();
					System.out.println("wait()方法结束:" + Thread.currentThread().getName());
					return;
				}
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	public void notifyMethod() {
		synchronized (object) {
			System.out.println("notify()方法开始:" + Thread.currentThread().getName());
			object.notify();
			System.out.println("notify()方法结束:" + Thread.currentThread().getName());
		}
	}

	@Override
	public void run() {
		if (flag) {
			this.waitMethod();
		} else {
			this.notifyMethod();
		}
	}
}
public class Test{
	public static void main(String[] args) throws InterruptedException {
		Object object = new Object();
		MyThread waitThread = new MyThread(true, object);
		MyThread notifyThread = new MyThread(false, object);
		Thread thread1 = new Thread(waitThread,"wait线程");
		Thread thread2 = new Thread(notifyThread,"notify线程");
		thread1.start();
		Thread.sleep(1000);
		thread2.start();
		System.out.println("main方法结束");
	}
}
第一个线程执行的是waitMethod方法,该方法里面有个死循环并且使用了wait方法进入等待状态将释放锁,如果这个线程不被唤醒的话将会一直等待下去,这个时候第二个线程执行的是notifyMethod方法,该方法里面执行了一个唤醒线程的操作,并且一直将notify的同步代码块执行完毕之后才会释放锁然后继续执行wait结束打印语句。
注意:wait、notify必须使用在synchronized同步方法或者代码块内。
3.notifyAll()方法
notify()方法只是唤醒一个线程,notifyAll()方法可以一次唤醒所有的等待线程。

如:使用notifyAll()方法唤醒所有线程

class MyThead implements Runnable{
	private boolean flag;
	private Object object;
	public MyThead(boolean flag,Object object) {
		this.flag = flag;
		this.object = object;
	}
	public void waitMethod() {
		synchronized (object) {
				try {
					while(true) {
						System.out.println("wait()方法开始:" + Thread.currentThread().getName());
						object.wait();
						System.out.println("wait()方法结束:" + Thread.currentThread().getName());
						return;
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
		}
	}
	public void notifyAllMethod() {
		synchronized (object) {
			System.out.println("notifyAll()方法开始:" + Thread.currentThread().getName());
			object.notifyAll();
			System.out.println("notifyAll()方法结束:" + Thread.currentThread().getName());
		}
	}
	@Override
	public void run() {
		if(flag) {
			this.waitMethod();
		}else {
			this.notifyAllMethod();
		}
	}
}
public class Test{
	public static void main(String[] args) throws InterruptedException {
		Object object = new Object();
		MyThead waitThread1 = new MyThead(true, object);
		MyThead waitThread2 = new MyThead(true, object);
		MyThead waitThread3 = new MyThead(true, object);
		MyThead notifyAllThread = new MyThead(false, object);
		Thread thread1 = new Thread(waitThread1,"wait线程A");
		Thread thread2 = new Thread(waitThread2,"wait线程B");
		Thread thread3 = new Thread(waitThread3,"wait线程C");
		Thread thread = new Thread(notifyAllThread,"notifyAll线程");
		thread1.start();
		thread2.start();
		thread3.start();
		Thread.sleep(1000);
		thread.start();
		System.out.println("main方法结束");
	}
}
注意:唤醒线程不能过早,如果还没有线程在等待中时,过早的唤醒线程,这个时候就会出现先唤醒再等待的效果了。
三、线程池
1.
对于线程池操作的核心类和接口就定义在java.util.conurrent包中,这里面有两个核心接口:
(1)普通线程池:public interface ExecutorService extends Executor
(2)调度线程池:public interface ScheduledExecutorService extends ExecutorService
如果要进行线程池的创建,一般可以使用Executors类完成
(1)创建无大小的线程池:public static ExecutorService newCachedThreadPool()
(2)创建固定的大小的线程池:public static ExecutorService newFixedThreadPool(int nThreads)
(3)单线程池:public static ExecutorService newSingleThreadExecutor()
(4)创建定时调度池:public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

2.如:创建无大小限制的线程池

public class Test{
	public static void main(String[] args) {
		ExecutorService newCachedThreadPool =Executors.newCachedThreadPool();
		for(int i = 0;i < 10;i++) {
			int index = i;
			newCachedThreadPool.submit(new Runnable() {
				
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName() + ",i = " + index);
				}
			});
		}
		newCachedThreadPool.shutdown();
	}
}

如:创建单线程的线程池

public class Test{
	public static void main(String[] args) {
		ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 10; i++) {
			int index = i;
			newSingleThreadExecutor.submit(new Runnable() {
				
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName() + ",i = " + index);
				}
			});
		}
		newSingleThreadExecutor.shutdown();
	}
}

如:创建固定大小线程池

public class Test{
	public static void main(String[] args) {
		ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
		for(int i = 0;i < 10;i++) {
			int index = i;
			newFixedThreadPool.submit(new Runnable() {
				
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName() + ",i = " + index);
				}
			});
		}
		newFixedThreadPool.shutdown();
	}
}

如:创建定时调度池

public class Test{
	public static void main(String[] args) {
		ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(2);
		for(int i = 0;i < 10;i++) {
			int index = i;
			newScheduledThreadPool.scheduleAtFixedRate(new Runnable() {
				
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName() + ",i = " + index);
				}
			}, 3, 2, TimeUnit.SECONDS);	//3s后开始执行,每间隔2s执行一次,单位为秒(s)
		}
	}
}
线程池带来的好处是多个线程按照组的模式进行程序的处理。这样在某些复杂环境下,性能就会得到很好的提升。

猜你喜欢

转载自blog.csdn.net/wzh_123_123/article/details/80257750