Java API —— 多线程(2)

1、JDK5中Lock锁的使用
  虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。
  · Lock
    void lock()
    void unlock()
  · ReentrantLock
自定义类:

package cn.itcast01;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * Created by gao on 16-1-6.
 */
public class SellTicket implements Runnable {
    private int tickets = 100;
    // 定义锁对象
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            //加锁
            try {
                lock.lock();
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "---" + (tickets--) + "张票");
                }
            } finally {
                //释放锁
                lock.unlock();
            }
        }
    }
}

 测试类:

package cn.itcast01;
/**
 * Created by gao on 16-1-6.
 */
/*
 * 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
 * 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。
 *
 * Lock:
 *         void lock(): 获取锁。
 *         void unlock():释放锁。
 * ReentrantLock是Lock的实现类.
 */
public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket s = new SellTicket();
        Thread t1 = new Thread(s,"窗口1");
        Thread t2 = new Thread(s,"窗口2");
        Thread t3 = new Thread(s,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

 

2、死锁问题
    1)同步弊端
        · 效率低
        · 如果出现了同步嵌套,就容易产生死锁问题
    2)死锁问题及其代码
        · 是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
        · 同步代码块的嵌套案例
自定义线程类:
package cn.itcast_02;
public class DieLock extends Thread {
    private boolean flag;
    public DieLock(boolean flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
        if (flag) {
            synchronized (MyLock.objA) {
                System.out.println("if objA");
                synchronized (MyLock.objB) {
                    System.out.println("if objB");
                }
            }
        } else {
            synchronized (MyLock.objB) {
                System.out.println("else objB");
                synchronized (MyLock.objA) {
                    System.out.println("else objA");
                }
            }
        }
    }
}

自定义锁:

package cn.itcast_02;
public class MyLock {
    // 创建两把锁对象
    public static final Object objA = new Object();
    public static final Object objB = new Object();
}

测试类:

package cn.itcast_02;
/*
 * 同步的弊端:
 *         A:效率低
 *         B:容易产生死锁
 * 
 * 死锁:
 *         两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象。
 * 
 * 举例:
 *         中国人,美国人吃饭案例。
 *         正常情况:
 *             中国人:筷子两支
 *             美国人:刀和叉
 *         现在:
 *             中国人:筷子1支,刀一把
 *             美国人:筷子1支,叉一把
 */
public class DieLockDemo {
    public static void main(String[] args) {
        DieLock dl1 = new DieLock(true);
        DieLock dl2 = new DieLock(false);
        dl1.start();
        dl2.start();
    }
}

 

3、线程间通信
    1)通过设置线程(生产者)和获取线程(消费者)针对同一个学生对象进行操作
例子1:
学生类:
package cn.itcast_04;
public class Student {
    String name;
    int age;
}

设置线程类:

package cn.itcast_04;
public class SetThread implements Runnable {
    private Student s;
    private int x = 0;
    public SetThread(Student s) {
        this.s = s;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                if (x % 2 == 0) {
                    s.name = "林青霞";//刚走到这里,就被别人抢到了执行权
                    s.age = 27;
                } else {
                    s.name = "刘意"; //刚走到这里,就被别人抢到了执行权
                    s.age = 30;
                }
                x++;
            }
        }
    }
}

获取线程类:

package cn.itcast_04;
public class GetThread implements Runnable {
    private Student s;
    public GetThread(Student s) {
        this.s = s;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                System.out.println(s.name + "---" + s.age);
            }
        }
    }
}

测试类:

package cn.itcast_04;
/*
 * 分析:
 *         资源类:Student    
 *         设置学生数据:SetThread(生产者)
 *         获取学生数据:GetThread(消费者)
 *         测试类:StudentDemo
 * 
 * 问题1:按照思路写代码,发现数据每次都是:null---0
 * 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
 * 如何实现呢?
 *         在外界把这个数据创建出来,通过构造方法传递给其他的类。
 * 
 * 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
 *         A:同一个数据出现多次
 *         B:姓名和年龄不匹配
 * 原因:
 *         A:同一个数据出现多次
 *             CPU的一点点时间片的执行权,就足够你执行很多次。
 *         B:姓名和年龄不匹配
 *             线程运行的随机性
 * 线程安全问题:
 *         A:是否是多线程环境        是
 *         B:是否有共享数据        是
 *         C:是否有多条语句操作共享数据    是
 * 解决方案:
 *         加锁。
 *         注意:
 *             A:不同种类的线程都要加锁。
 *             B:不同种类的线程加的锁必须是同一把。
 */
public class StudentDemo {
    public static void main(String[] args) {
        //创建资源
        Student s = new Student();
        
        //设置和获取的类
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);
        //线程类
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);
        //启动线程
        t1.start();
        t2.start();
    }
}
  
  2)等待唤醒机制
例子1:
学生类:
package cn.itcast05;
/**
 * Created by gao on 16-1-6.
 */
public class Student {
    String name;
    int age;
    boolean flag; // 默认情况是没有数据,如果是true,说明有数据
}

设置线程类:

package cn.itcast05;
/**
 * Created by gao on 16-1-6.
 */
public class SetThread implements Runnable {
    private Student s;
    private int x = 0;
    public SetThread(Student s) {
        this.s = s;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                //判断有没有
                if (s.flag) {
                    try {
                        s.wait();  //t1等着,释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (x % 2 == 0){
                    s.name = "林青霞";
                    s.age = 27;
                }else{
                    s.name = "刘意";
                    s.age = 30;
                }
                x++;
                //修改标记
                s.flag = true;
                //唤醒线程
                s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
            }
            //t1有,或者t2有
        }
    }
}

获取线程类:

package cn.itcast05;
/**
 * Created by gao on 16-1-6.
 */
public class GetThread implements Runnable {
    private Student s;
    public GetThread(Student s) {
        this.s = s;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                if (!s.flag) {
                    try {
                        s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(s.name + "---" + s.age);
                //修改标记
                s.flag = false;
                //唤醒线程
                s.notify(); //唤醒t1
            }
        }
    }
}

测试类:

package cn.itcast05;
/**
 * Created by gao on 16-1-6.
 */
/* 等待唤醒:
* Object类中提供了三个方法:
* wait():等待
* notify():唤醒单个线程
* notifyAll():唤醒所有线程
* 为什么这些方法不定义在Thread类中呢?
* 这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
* 所以,这些方法必须定义在Object类中。
*/
public class StudentDemo {
    public static void main(String[] args) {
        //创建资源
        Student s = new Student();
        //设置和获取的类
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);
        //线程类
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);
        //启动线程
        t1.start();
        t2.start();
    }
}
例子2(优化):
学生类:
package cn.itcast_07;
public class Student {
    private String name;
    private int age;
    private boolean flag; // 默认情况是没有数据,如果是true,说明有数据
    public synchronized void set(String name, int age) {
        // 如果有数据,就等待
        if (this.flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                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) {
                e.printStackTrace();
            }
        }
        // 获取数据
        System.out.println(this.name + "---" + this.age);
        // 修改标记
        this.flag = false;
        this.notify();
    }
}

 设置线程类:

package cn.itcast_07;
public class SetThread implements Runnable {
    private Student s;
    private int x = 0;
    public SetThread(Student s) {
        this.s = s;
    }
    @Override
    public void run() {
        while (true) {
            if (x % 2 == 0) {
                s.set("林青霞", 27);
            } else {
                s.set("刘意", 30);
            }
            x++;
        }
    }
}

获取线程类:

package cn.itcast_07;
public class GetThread implements Runnable {
    private Student s;
    public GetThread(Student s) {
        this.s = s;
    }
    @Override
    public void run() {
        while (true) {
            s.get();
        }
    }
}

测试类:

package cn.itcast_07;
/*
 * 最终版代码中:
 *         把Student的成员变量给私有的了。
 *         把设置和获取的操作给封装成了功能,并加了同步。
 *         设置或者获取的线程里面只需要调用方法即可。
 */
public class StudentDemo {
    public static void main(String[] args) {
        //创建资源
        Student s = new Student();
        
        //设置和获取的类
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);
        //线程类
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);
        //启动线程
        t1.start();
        t2.start();
    }
}

 

4、线程的状态转换图

 

 

5、线程组
    · Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
    · 默认情况下,所有的线程都属于主线程组。
        public final ThreadGroup getThreadGroup()
    · 我们也可以给线程设置分组
        Thread(ThreadGroup group, Runnable target, String name) 
自定义线程类:
package cn.itcast06;
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(Thread.currentThread().getName() + ":" + x);
        }
    }
}

 测试类:

package cn.itcast06;
/*
 * 线程组: 把多个线程组合到一起。
 * 它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
 */
public class ThreadGroupDemo {
    public static void main(String[] args) {
        // method1();
        // 我们如何修改线程所在的组呢?
        // 创建一个线程组
        // 创建其他线程的时候,把其他线程的组指定为我们自己新建线程组
        method2();
        // t1.start();
        // t2.start();
    }
    private static void method2() {
        // ThreadGroup(String name)
        ThreadGroup tg = new ThreadGroup("这是一个新的组");
        MyRunnable my = new MyRunnable();
        // Thread(ThreadGroup group, Runnable target, String name)
        Thread t1 = new Thread(tg, my, "林青霞");
        Thread t2 = new Thread(tg, my, "刘意");
        
        System.out.println(t1.getThreadGroup().getName());
        System.out.println(t2.getThreadGroup().getName());
        
        //通过组名称设置后台线程,表示该组的线程都是后台线程
        tg.setDaemon(true);
    }
    private static void method1() {
        MyRunnable my = new MyRunnable();
        Thread t1 = new Thread(my, "林青霞");
        Thread t2 = new Thread(my, "刘意");
        // 我不知道他们属于那个线程组,我想知道,怎么办
        // 线程类里面的方法:public final ThreadGroup getThreadGroup()
        ThreadGroup tg1 = t1.getThreadGroup();
        ThreadGroup tg2 = t2.getThreadGroup();
        // 线程组里面的方法:public final String getName()
        String name1 = tg1.getName();
        String name2 = tg2.getName();
        System.out.println(name1);
        System.out.println(name2);
        // 通过结果我们知道了:线程默认情况下属于main线程组
        // 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
        System.out.println(Thread.currentThread().getThreadGroup().getName());
    }
}
 
6、线程池
    1)
    2)在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。
    3)JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:
        · public static ExecutorService newCachedThreadPool()
        · public static ExecutorService newFixedThreadPool(int nThreads)
        · public static ExecutorService newSingleThreadExecutor()
        · 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法:
            · Future<?> submit(Runnable task)
            · <T> Future<T> submit(Callable<T> task)
    4)案例演示
        · 创建线程池对象
        · 创建Runnable实例
        · 提交Runnable实例
        · 关闭线程池
例子1:
自定义线程类:
package cn.itcast_08;
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(Thread.currentThread().getName() + ":" + x);
        }
    }
}

测试类:

package cn.itcast_08;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorsDemo {
    public static void main(String[] args) {
        // 创建一个线程池对象,控制要创建几个线程对象。
        // public static ExecutorService newFixedThreadPool(int nThreads)
        ExecutorService pool = Executors.newFixedThreadPool(2);
        // 可以执行Runnable对象或者Callable对象代表的线程
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        //结束线程池
        pool.shutdown();
    }
}
例子2:
自定义线程类:
package cn.itcast_09;
import java.util.concurrent.Callable;
//Callable:是带泛型的接口。
//这里指定的泛型其实是call()方法的返回值类型。
public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        for (int x = 0; x < 100; x++) {
            System.out.println(Thread.currentThread().getName() + ":" + x);
        }
        return null;
    }
}

 测试类:

package cn.itcast_09;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CallableDemo {
    public static void main(String[] args) {
        //创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(2);
        
        //可以执行Runnable对象或者Callable对象代表的线程
        pool.submit(new MyCallable());
        pool.submit(new MyCallable());
        
        //结束
        pool.shutdown();
    }
}
例子3:线程求和
自定义线程类:
package cn.itcast10;
import java.util.concurrent.Callable;
/**
 * Created by gao on 16-1-7.
 */
public class MyCallable implements Callable<Integer> {
    private int number;
    public MyCallable(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;
    }
}

 测试类:

package cn.itcast10;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(2);
        // 可以执行Runnable对象或者Callable对象代表的线程
        Future<Integer> f1 = pool.submit(new MyCallable(100));
        Future<Integer> f2 = pool.submit(new MyCallable(200));
        // V get()
        Integer i1 = f1.get();
        Integer i2 = f2.get();
        System.out.println(i1);
        System.out.println(i2);
        // 结束
        pool.shutdown();
    }
}
 
7、匿名内部类方式使用多线程

  · new Thread(){代码…}.start();
  · new Thread(new Runnable(){代码…}).start();

package cn.itcast10;
/**
 * Created by gao on 16-1-7.
 */
/*
 * 匿名内部类的格式:
 *         new 类名或者接口名() {
 *             重写方法;
 *         };
 *         本质:是该类或者接口的子类对象。
 */
public class ThreadDemo01 {
    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();
        // 更有难度的
        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();
    }
}

 

8、定时器
  1)定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能
  2)Timer
    · public Timer()
    · public void schedule(TimerTask task, long delay)
    · public void schedule(TimerTask task,long delay,long period)
  3)TimerTask
    · public abstract void run()
    · public boolean cancel()
  4)开发中
    · Quartz是一个完全由java编写的开源调度框架。
例子1:

package cn.itcast_12;
import java.util.Timer;
import java.util.TimerTask;
/*
 * 定时器:可以让我们在指定的时间做某件事情,还可以重复的做某件事情。
 * 依赖Timer和TimerTask这两个类:
 * Timer:定时
 *         public Timer()
 *         public void schedule(TimerTask task,long delay)
 *         public void schedule(TimerTask task,long delay,long period)
 *         public void cancel()
 * TimerTask:任务
 */
public class TimerDemo {
    public static void main(String[] args) {
        // 创建定时器对象
        Timer t = new Timer();
        // 3秒后执行爆炸任务
        // t.schedule(new MyTask(), 3000);
        //结束任务
        t.schedule(new MyTask(t), 3000);
    }
}
// 做一个任务
class MyTask extends TimerTask {
    private Timer t;
    
    public MyTask(){}
    
    public MyTask(Timer t){
        this.t = t;
    }
    
    @Override
    public void run() {
        System.out.println("beng,爆炸了");
        t.cancel();
    }
}

例子2:

package cn.itcast_12;
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo2 {
    public static void main(String[] args) {
        // 创建定时器对象
        Timer t = new Timer();
        // 3秒后执行爆炸任务第一次,如果不成功,每隔2秒再继续炸
        t.schedule(new MyTask2(), 3000, 2000);
    }
}
// 做一个任务
class MyTask2 extends TimerTask {
    @Override
    public void run() {
        System.out.println("beng,爆炸了");
    }
}

例子3:

package cn.itcast_12;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/*
 * 需求:在指定的时间删除我们的指定目录(你可以指定c盘,但是我不建议,我使用项目路径下的demo)
 */
class DeleteFolder extends TimerTask {
    @Override
    public void run() {
        File srcFolder = new File("demo");
        deleteFolder(srcFolder);
    }
    // 递归删除目录
    public void deleteFolder(File srcFolder) {
        File[] fileArray = srcFolder.listFiles();
        if (fileArray != null) {
            for (File file : fileArray) {
                if (file.isDirectory()) {
                    deleteFolder(file);
                } else {
                    System.out.println(file.getName() + ":" + file.delete());
                }
            }
            System.out.println(srcFolder.getName() + ":" + srcFolder.delete());
        }
    }
}
public class TimerTest {
    public static void main(String[] args) throws ParseException {
        Timer t = new Timer();
        String s = "2014-11-27 15:45:00";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = sdf.parse(s);
        t.schedule(new DeleteFolder(), d);
    }
}

 

多线程常见面试题
1)多线程有几种实现方案,分别是哪几种?
  两种。继承Thread类实现Runnable接口扩展一种:实现Callable接口。这个得和线程池结合。
2)同步有几种方式,分别是什么?
  两种。同步代码块同步方法
3)启动一个线程是run()还是start()?它们的区别?
  启动一个线程是start()run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用start():启动线程,并由JVM自动调用run()方法
4)sleep()和wait()方法的区别
  sleep():必须指时间;不释放锁。wait():可以不指定时间,也可以指定时间;释放锁。
5)为什么wait(),notify(),notifyAll()等方法都定义在Object类中
  因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。而Object代码任意的对象,所以,定义在这里面。
6)线程的生命周期图
  新建 -- 就绪 -- 运行 -- 死亡
  新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡

 

posted @ 2016-01-07 17:25  我是一名老菜鸟  阅读(394)  评论(0编辑  收藏  举报