实现多线程一般有三种方式:1. 继承Thread类 2.实现Runnable接口 3.实现Callable接口
我们简单模拟一下生活中的网上购票,先来看第一种继承Thread类
资源类
class W12306 {
private static int ticketSum = 1000;
/**
* 购票并扣减票数
*
* @param name
*/
public synchronized static void getTicket(String name) {
if (ticketSum == 0) {
System.out.println("Sold Out!");
return;
}
System.out.println(name + "by" + ticketSum);
ticketSum -= 1;
}
/**
* 获取票数
*
* @return
*/
public static int getSum() {
return ticketSum;
}
}
创建一个黄牛类
class Hn extends Thread {
public Hn(String name) {
super(name);
}
@Override
public void run() {
while (W12306.getSum() > 0) {
W12306.getTicket(this.currentThread().getName());
}
}
}
创建一个旅客类
class Lk extends Thread {
public Lk(String name) {
super(name);
}
@Override
public void run() {
while (W12306.getSum() > 0) {
W12306.getTicket(this.currentThread().getName());
}
}
}
应用:
public class ThreadTest {
public static void main(String[] args) {
Thread hn = new Hn("黄牛");
Thread lk = new Lk("旅客");
hn.start();
lk.start();
}
}
写多线程程序一定要注意并发及死锁等问题,资源是共享的,上述程序多个线程对资源(票)进行访问如果不做线程安全控制会出现一张票被重复购买及购买不合法票的情况。来分析一个线程不安全的情况,把W12306类的getTicket()方法的synchronized去除。
public static void getTicket(String name) {
if (ticketSum == 0) {
System.out.println("Sold Out!");
return;
}
//旅客线程执行到这
System.out.println(name + "by" + ticketSum);
//黄牛线程执行到这,购票了但未把这张票扣减
ticketSum -= 1;
}
这两个线程几乎同时进入该方法黄牛线程在买完票时准备去扣减时(还未扣减),旅客线程正好执行到购票代码中,这样就造成了一张票被重复购买的情况。加上synchronized可以让线程排队访问方法,也就是在某一时刻方法只有一个线程在执行,也可以使用synchronized(W12306.class){ }。这种方式的缺点是线程要排队访问方法,性能比较差。
来看一下第二种实现Runable接口:
资源类:
class W12306 {
private static int ticketSum = 1000;
/**
* 购票并扣减票数
*
* @param name
*/
public synchronized static void getTicket(String name) {
if (ticketSum == 0) {
System.out.println("Sold Out!");
return;
}
System.out.println(name + "by" + ticketSum);
ticketSum -= 1;
}
/**
* 获取票数
*
* @return
*/
public static int getSum() {
return ticketSum;
}
}
黄牛类:
class Hn implements Runnable {
@Override
public void run() {
while (W12306.getSum() > 0) {
W12306.getTicket(Thread.currentThread().getName());
}
}
}
旅客类:
class Lk implements Runnable {
@Override
public void run() {
while (W12306.getSum() > 0) {
W12306.getTicket(Thread.currentThread().getName());
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Thread proxy1= new Thread(new Hn(), "黄牛");
Thread proxy2= new Thread(new Lk(), "旅客");
proxy1.start();
proxy2.start();
}
}
想比第一种实现方式这种方式比较好扩展,第一种方式使用的是继承,这样就不可以再继承其它类了。第二种方式使用了一种设计模式叫静态代理(http://blog.youkuaiyun.com/u014534811/article/details/50401948),被代理对象黄牛和旅客及代理类Thread都实现了Runable接口。
</pre><pre name="code" class="java"> Thread proxy1= new Thread(new Hn(), "黄牛");
Thread proxy2= new Thread(new Lk(), "旅客");
相当于创建了代理对象再看一个Thread类的源码
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
this.name = name.toCharArray();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext = AccessController.getContext();
this.target = target; //这边把被代理的真实对象(黄牛、旅客)给target
setPriority(priority);
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
this.me = this;
}
public void run() {
if (target != null) {
target.run();
}
}
通过代理类Thread 构造器传入被代理的对象及名称,在init方法中把被代理对象赋值给target成员变更,当执行Thread run方法时其实是执行target的run方法,也就是被代理对象的run方法。
第二种方法 的run 方法返回是void 也不可以抛出check异常,第三种方法是实现Callable接口,重写call方法可以返回自定义类型及抛出check异常,但这种方法实现比较麻烦。
我们就看一下重写的call方法
@Override
public Object call() throws Exception {
return null;
}