Java基础总结九之多线程(二)

虽然我们可以在多线程总结一中理解同步代码块和同步方法中的锁对象问题,但是我们并没有看到他在哪里上了锁,哪里释放了锁,为了更清晰的表达如何加锁是释放锁,JDK5以后提供了一个新的锁对象Lock,下面是Lock锁对象一个简单使用实例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicketLockDemo implements Runnable {

	private int ticket = 100;

	// 定义锁对象
	private Lock lock = new ReentrantLock();

	@Override
	public void run() {
		while (true) {
			try {
				// 加锁
				lock.lock();
				if (ticket > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在出售第:" + ticket-- + "张票");
				}
			} finally {
				lock.unlock();//释放锁
			}
		}
	}

	public static void main(String[] args) {
		SellTicketLockDemo stld = new SellTicketLockDemo();
		Thread t1 = new Thread(stld, "窗口一");
		Thread t2 = new Thread(stld, "窗口二");
		t1.start();
		t2.start();

	}
}

总结完了同步的方法之后,那么接下来再总结一下因为同步而产生的死锁问题:

(1)死锁

什么是死锁?所谓死锁是指两个或两个以上的线程在执行的过程中,因争夺资源产生的一种相互等待的现象。

public class DieLockDemo extends Thread {

	// 定义两把锁
	private static final Object obj = new Object();
	private static final Object obj2 = new Object();

	private boolean flag;

	public DieLockDemo(boolean flag) {
		this.flag = flag;
	}

	@Override
	public void run() {
		if (flag) {
			synchronized (obj) {
				System.out.println("if obj");
				synchronized (obj2) {
					System.out.println("if obj2");
				}
			}
		} else {
			synchronized (obj2) {
				System.out.println("else obj2");
				synchronized (obj) {
					System.out.println("else obj");
				}
			}
		}

	}

	public static void main(String[] args) {
		DieLockDemo dld = new DieLockDemo(true);
		DieLockDemo dld2 = new DieLockDemo(false);
		dld.setName("线程一");
		dld2.setName("线程二");
		dld.start();
		dld2.start();
	}
}
//执行结果:
if obj
else obj2
然后程序卡死

上面的代码是一个简单会发生的死锁的程序,对于两个线程产生的死锁可以通俗的可以理解为A和B都需要对方手中的资源,但是又因为得不到对方手中的资源,导致自己的资源无法关闭,也就无法贡献出来让别人使用,这就产生了死锁的问题。

(2)生产者消费者模式的三种实现:https://www.cnblogs.com/Ming8006/p/7243858.html。除了该博文的讲解,以下再列出一个实例,体验下生产者和消费者模式:该实例实现的是生产了一个就消费一个,不生产不消费。


public class Student {//资源类

	private String name;
	private int age;
	private boolean flag;// 假设仓库大小为1,判断仓库是否有数据

	public synchronized void set(String name, int age) {
		if (this.flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		// 设置数据
		this.name = name;
		this.age = age;
		// 修改标记
		this.flag = true;
		this.notify();
	}

	public synchronized void get() {
		if (!this.flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		// 获取数据
		System.out.println(this.name + "-----" + this.age);
		// 修改标记
		this.flag = false;
		this.notify();
	}
}


public class Produce implements Runnable {//生产者

	private Student s;

	public Produce(Student s) {
		this.s = s;
	}

	@Override
	public void run() {

		int x = 0;
		while (true) {
			synchronized (s) {
				if (x % 2 == 0) {
					s.set("Is-Me-HL", 22);
				} else {
					s.set("Is-Me-JJJ", 21);
				}
				x++;

			}

		}
	}

}

public class Consume implements Runnable {//消费者

	private Student s;

	public Consume(Student s) {
		this.s = s;
	}

	@Override
	public void run() {
		while (true) {
			synchronized (s) {
				s.get();
			}
		}

	}

}

public class StudentTest {//测试类

	public static void main(String[] args) {
		Student s = new Student();
		Produce p = new Produce(s);
		Consume c = new Consume(s);

		Thread t1 = new Thread(p);
		Thread t2 = new Thread(c);

		t1.start();
		t2.start();
	}
}
//测试结果:
Is-Me-HL-----22
Is-Me-JJJ-----21
Is-Me-HL-----22
Is-Me-JJJ-----21
Is-Me-HL-----22
Is-Me-JJJ-----21交替出现

(3)线程组:ThreadGroup:把多个线程组合到一起,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。且创建线程后如果线程没有设置属于哪个线程组,那么Java默认该线程属于main线程组。

(4)线程池:JDK5新增了一个Executors工厂类来产生线程池。线程池的好处是每一个线程代码结束后,并不会死亡,二十再次回到线程池中成为空闲状态,等待下一个对象来使用。下面是简单的线程池使用案例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorsDemo implements Runnable {

	@Override
	public void run() {
		for(int x=0;x<100;x++) {
			System.out.println(Thread.currentThread().getName()+":"+x);
		}

	}

	public static void main(String[] args) {
		//创建线程池对象,控制要几个线程
		ExecutorService pool = Executors.newFixedThreadPool(2);
		//可以执行Runnable对象或者Callable对象代表的线程
		pool.submit(new ExecutorsDemo());
		pool.submit(new ExecutorsDemo());
		//关闭线程池
		pool.shutdown();
	}

}

创建线程的第三种方式;实现Callable接口(注意:该接口是带返回值的<泛型>,默认返回值类型时Object):

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorsDemo implements Callable {
	@Override
	public Object call() throws Exception {
		for (int x = 0; x < 100; x++) {
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
		return null;
	}

	public static void main(String[] args) {
		// 创建线程池对象,控制要几个线程
		ExecutorService pool = Executors.newFixedThreadPool(2);
		// 可以执行Runnable对象或者Callable对象代表的线程
		pool.submit(new ExecutorsDemo());
		pool.submit(new ExecutorsDemo());
		// 关闭线程池
		pool.shutdown();
	}

}

Callable接口使用方式几乎和Runnable接口一样,但是由于其带返回值,还是相对于Runnable有其存在的意义:

//计算数的和
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorsDemo implements Callable<Integer> {
	private int number;

	public ExecutorsDemo(int number) {
		this.number = number;
	}

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int x = 1; x <= number; x++) {
			sum += x;
		}
		return sum;
	}

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 创建线程池对象,控制要几个线程
		ExecutorService pool = Executors.newFixedThreadPool(2);
		// 可以执行Runnable对象或者Callable对象代表的线程
		Future<Integer> a = pool.submit(new ExecutorsDemo(100));
		Future<Integer> b = pool.submit(new ExecutorsDemo(200));
		// 关闭线程池
		pool.shutdown();
		System.out.println(a.get() + "-----" + b.get());
	}

}

(5)匿名内部类实现多线程方式:


public class Demo {

	public static void main(String[] args) {
		// 继承Thread类来实现多线程
		new Thread() {
			public void run() {
				for (int x = 0; x < 100; x++) {
					System.out.println(Thread.currentThread().getName() + ":" + x);
				}
			}
		}.start();

		// 实现Runnable接口来实现多线程
		new Thread(new Runnable() {

			@Override
			public void run() {
				for (int x = 0; x < 100; x++) {
					System.out.println(Thread.currentThread().getName() + ":" + x);
				}
			}
		}) {
		}.start();
	}
}

要注意的是继承Thead类和实现Runnable接口run方法重写的位置:如果出现下面这个情况:


public class Demo {

	public static void main(String[] args) {

		new Thread(new Runnable() {

			@Override
			public void run() {
				for (int x = 0; x < 100; x++) {
					System.out.println("hello" + ":" + x);
				}
			}
		}) {
			public void run() {
				for (int x = 0; x < 100; x++) {
					System.out.println("world" + ":" + x);
				}
			}
		}.start();
	}
}

则线程执行的run方法是输出为world的这个方法。

(6)多线程(定时器):可以让我们在指定的时间做某件事,还可以重复做某件事。依赖的是Timer和TimerTask这两个类。Timer:定时,TimerTask:任务。简单的使用如下:(具体其他方法使用查看API)

import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo {

	public static void main(String[] args) {
		Timer t = new Timer();
		t.schedule(new MyTask(t), 3000);

	}
}

class MyTask extends TimerTask {

	private Timer t;

	public MyTask(Timer t) {
		this.t = t;
	}

	@Override
	public void run() {
		System.out.println("定时任务启动了!");
		t.cancel();// 结束任务
	}

}

(7)多线程的几种实现方案:

A:继承Thread类   B:实现Runnable接口   C:实现Callable接口(ps:该接口依赖于线程池)

(8)同步有几种方式:两种,同步代码块和同步方法。

(9)启动一个线程是run()还是start()方法?两者有什么区别?

start方法启动,run()方法封装了被线程执行的代码,直接调用仅仅是普通方法的调用。start()方法启动线程,并通知JVM自动调用run方法。

(10)sleep()和wai()方法的区别?

sleep():必须指定时间;不释放锁。

wait():可以指定时间也可以不指定时间;释放锁

(11)为什么wait()、notify()、notifyAll()等方法都定义在Object类中?

因为这些方法的调用都是依赖于锁对象的,而同步代码块的锁对象是任意锁。而Object代表是任意对象。

(12)线程的生命周期?

新建--就绪--运行--死亡

新建--就绪--运行--阻塞--就绪--运行--死亡

建议是自己画图理解。


注:以上文章仅是个人学习过程总结,若有不当之处,望不吝赐教。

猜你喜欢

转载自blog.csdn.net/m0_37265215/article/details/81807155