2.实现Runnable 接口

步骤:
        1、定义类实现Runnable接口
        2、覆盖Runnable接口中的run方法,运行的代码放入run方法中。
        3、通过Thread类建立线程对象。
        4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
             因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象
       5、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
    代码示例:卖票程序,多个窗口同时卖票
    class Ticket implements Runnable{
    private  int tick = 100;
    public void run(){
        while(true){
         if(tick>0) {
                System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
        }
        }
    }
}
class  TicketDemo {
    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();    
    }
}
这里使用线程类创建4个实例对象(窗口),共同卖100张票
注:这里需要注意线程同步问题

什么时候会出现线程安全问题?

1.在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的的资源
 2.当多个线程同时访问同一个资源的时候,就会存在一个问题:由于每个线程执行的过程是不可控的,所以很可能导致最终的结果
                                                    与实际上的愿望相违背或者直接导致程序出错
 举个简单的例子:以卖票举例
 现在有两个线程分别从窗口卖票,要求不能卖出重复的票。
 那么必然在卖票的过程中存在两个操作:
   (1)检查该票是否已经卖出;
   (2)如果卖出,则不卖;如果没有卖出,则卖出该票。
 假如两个线程分别用thread-1和thread-2表示,某一时刻,thread-1和thread-2都卖出票A,那么可能会发生这种情况;
  (1)thread-1去检查票A是否卖出,然后thread-2也接着去检查票A是否卖出;
  (2)结果两个线程检查的结果都是票A没有卖出,那么两个线程都分别将票A卖出。
  这个就是线程安全问题,即多个线程同时访问一个资源时,会导致程序运行结果并不是想看到的结果;
  这里面,这个资源被称为:临界资源(也有称为共享资源)。
  也就是说,当多个线程同时访问临界资源(一个对象,对象中的属性,一个文件,一个数据库等)时,就可能会产生线程安全问题。
  注:当多个线程执行一个方法,方法内部的局部变量并不是临界资源,因为方法是在栈上执行的,而Java栈是线程私有的,因此不会产生线程安全问题

解决多线程安全性问题:
通常来说,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。

1、同步代码块
public class Ticket implements Runnable {
	//共100票
	int ticket = 100;
	//定义锁对象
	Object lock = new Object();
	@Override
	public void run() {
		//模拟卖票
		while(true){
			//同步代码块
			synchronized (lock){
				if (ticket > 0) {
					System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
				}
			}
  2、同步函数:锁为this(推荐使用)
   class Ticket implements Runnable{
    private  int tick = 100;
    public void run(){
        while(true){
        //这里面的this就是一把锁,使用这个类创建的线程使用同一把锁
        synchronized (this) {
        if(tick>0) {
                System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
        }
        }
        }
  3、Lock对象(本身是一个接口,使用其子类,强烈推荐使用)
    public class Ticket implements Runnable {
	//共100票
	int ticket = 100;
	//创建Lock锁对象
	Lock ck = new ReentrantLock();
    @Override
    public void run() {
	//模拟卖票
	while(true){
		ck.lock();
			if (ticket > 0) {
				System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
			}
		//释放锁
		ck.unlock();
	}
}

同步synchronized与锁机制Lock区别:

(1) 线程睡眠一旦产生异常,去捕获异常,同步方法不能结束,同步锁就不能被释放。因此JDK1.5后,采用Lock接口可替代synchronized
 (2)使用Lock接口,以及其中的lock()方法和unlock()方法替代同步

死锁现象:

(1)当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步,这时容易引发死锁
(2)程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉
synchronzied(A锁){
       synchronized(B锁){  
            }
    }

扩展:
(1)sleep()和wait()方法的区别?
sleep(): 不释放锁对象, 释放CPU使用权,在休眠的时间内,不能唤醒
wait(): 释放锁对象, 释放CPU使用权,在等待的时间内,能唤醒
(2)为什么wait(),notify(),notifyAll()等方法都定义在Object类中?
锁对象可以是任意类型的对象!!!

使用Runnable接口创建线程的优缺点:

1.优点:
      (1) 适合多个相同的程序代码的线程去处理同一个资源
      (2)可以避免java中的单继承的限制
      (3)增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
      (4)线程池有很好的支持
 2.缺点:
    (1)在使用Runnable定义的子类中没有start()方法,只有Thread类中才有
    (2)通过Thread类来启动Runnable实现的多线程
    综上所述,Runnable接口创建线程优势远远大于劣势,所以Runnable比Thread应用更广!!