1. Runnable接口

 1. 创建线程的另一种方法是声明一个实现Runnable接口的类,之后重写run()方法,然后可以分配类的实例,在创建Thread时作为参数传递,最后启动。

 2. 具体实现Runnable接口:(1) 定义一个MyRunnable实现Runnable接口。 (2) 在MyRunnable中重写run()方法。 (3) 创建MyRunnable类对象。 (4) 创建Thread类的对象,把MyRunnable对象作为构造方法的参数。 (5) 启动线程。

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

public class MyRunnableDemo {
    public static void main(String[] args) {
        //创建MyRunnable类对象
        MyRunnable my=new MyRunnable();

        //创建Thread类对象,把MyRunnable对象作为参数传进来
        //Thread(Runnable target)
        //Thread t1=new Thread(my);
        //Thread t2=new Thread(my);

        //Thread(Runnable target, String name)
        Thread t1=new Thread(my,"线程1");
        Thread t2=new Thread(my,"线程2");

        //启动线程
        t1.start();
        t2.start();
    }
}

Java---线程讲解(二)-LMLPHP
 3. 多线程的实现方案有两种:(1) 继承Thread类。 (2) 实现Runnable接口。

  • 实现Runnable接口好处:避免了Java单继承的局限性;适合多个相同程序的代码去处理同一资源的情况,把线程和程序的代码、数据有效分离,较好地体现了面向对象的设计思想。

2. 卖票案例

 1. 需求和思路:

Java---线程讲解(二)-LMLPHP

 2. 代码块:

public class SellTicket implements Runnable{
    //表示有100张票
    private int tickets=100;
    @Override
    public void run() {
        while (true){
            if(tickets>0){
                System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
                tickets--;
            }
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st=new SellTicket();
        Thread t1=new Thread(st,"窗口1");
        Thread t2=new Thread(st,"窗口2");
        Thread t3=new Thread(st,"窗口3");

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

Java---线程讲解(二)-LMLPHP
 3. 问题分析:看上面结果相同的票会出现三次,为什么呢?请参照下图理解,虽然加上了休眠时间,但是原理是一样的,都是线程抢占CPU执行权导致的。

Java---线程讲解(二)-LMLPHP

3. 同步代码块解决数据安全问题

 1. 如何解决多线程安全问题:(1) 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。 (2) Java提供了同步代码块的方式来解决。

 2. 同步代码块格式:

Java---线程讲解(二)-LMLPHP

 3. 代码块举例:

public class SellTicket implements Runnable{
    //表示有100张票
    private int tickets=100;
    private Object obj= new Object();
    @Override
    public void run() {
        while (true){
            synchronized (obj) {
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st=new SellTicket();
        Thread t1=new Thread(st,"窗口1");
        Thread t2=new Thread(st,"窗口2");
        Thread t3=new Thread(st,"窗口3");

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

Java---线程讲解(二)-LMLPHP

 4. 同步的好处和弊端:(1) 好处:解决了多线程的数据安全问题。 (2) 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

4. 同步方法解决数据安全问题

 1. 同步方法:就是把synchronized关键字加到方法上。

 2. 格式:修饰符 synchronized 返回值类型 方法名(方法参数){ }

 3. 同步方法的锁对象:this

 4. 代码块举例:

public class SellTicket implements Runnable{
    //表示有100张票
    private int tickets=100;
    private Object obj= new Object();
    private int x=0;

    @Override
    public void run() {
        while (true){
            if(x%2==0) {  //这里得是this,否则会出问题
                synchronized (this) {
                    if (tickets > 0) {
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                        tickets--;
                    }
                }
            }else{
                sellticket();
            }
            x++;
        }
    }

    private synchronized void sellticket(){
        if (tickets > 0) {
            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
            tickets--;
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st=new SellTicket();
        Thread t1=new Thread(st,"窗口1");
        Thread t2=new Thread(st,"窗口2");
        Thread t3=new Thread(st,"窗口3");

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

5. 线程安全的类

Java---线程讲解(二)-LMLPHP

6. Lock锁

 1. 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

 2. Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。

 3. Lock中获得锁和释放锁的方法:void lock():获得锁。void unlock():释放锁。

 4. Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。

 5. 代码块举例:

public class SellTicket implements Runnable{
    //表示有100张票
    private int tickets=100;
    private Lock lock=new ReentrantLock();


    @Override
    public void run(){
        while (true) {
            lock.lock();
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
            }
            lock.unlock();
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st=new SellTicket();
        Thread t1=new Thread(st,"窗口1");
        Thread t2=new Thread(st,"窗口2");
        Thread t3=new Thread(st,"窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
12-08 15:17