多线程

2019-06-30  本文已影响0人  LeoFranz

基本概念

进程:一个程序在运行过程中的动态指令集和系统内存、资源的集合。
线程:线程是进程中的一条执行路径。

创建线程

java是没有办法自己创建线程的,是调用了C/C++接口
1、三种方法

Tip:比较骚的语法

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("heheda");
            }
        }){
            @Override
            public void run() {
                super.run();//影响的关键在于这句,自己去研究下踏马的
                System.out.println("hahaha");
            }
        }.start();
//输出heheda
//hahaha

2、给线程起名
两种方法

3、设置线程的优先级

4、线程的调度

void join():当前线程等该加入该线程后面,等待该线程终止。
void join(long millis):当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度。
void join(long millis,int nanos):等待该线程终止的时间最长为 millis 毫秒 + nanos纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.
SecurityException - if the current thread cannot modify this thread

5、线程的生命周期


实战演练
三个窗口售卖总共100张票

public class SellingTickets implements Runnable{
    private int mTicketNum = 100;

    @Override
    public void run() {
        while(mTicketNum>0) {
            System.out.println(Thread.currentThread().getName()+" selling the ticket NO."+mTicketNum);
            mTicketNum--;
        }
    }
}

public class SellingWindow extends Thread {
    public SellingWindow() {
        super();
    }

    public SellingWindow(Runnable runnable, String s) {
        super(runnable, s);
    }
}

public static void main(String[] args) {
        SellingTickets sellingTask = new SellingTickets();
        SellingWindow window1 = new SellingWindow(sellingTask,"window1");
        SellingWindow window2 = new SellingWindow(sellingTask,"window2");
        SellingWindow window3 = new SellingWindow(sellingTask,"window3");
        window1.start();
        window2.start();
        window3.start();
    }
//结果中虽然能够实现基本卖票效果,但是票的顺序和总票数有一点微小偏差

所以需要做线程同步问题

6、线程同步
多线程代码中不会等到一个方法运行结束后再跑另一个线程的方法,可能线程A的方法走到一半,线程B方法就开始执行。
以下是一个关于线程同步的例子:

public class TestClass {

    private class Window implements Runnable {
        private int mTicket = 1;

        @Override
        public void run() {
            while (mTicket <= 100) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第 " + (mTicket++) + " 张票");
            }
        }
    }

    public static void main(String[] args) {
        Window window = new TestClass().new Window();
        Thread th1 = new Thread(window, "窗口1");
        Thread th2 = new Thread(window, "窗口2");
        Thread th3 = new Thread(window, "窗口3");
        th1.start();
        th2.start();
        th3.start();
    }

    /**
     * 问题1.相同的票卖了多次,原因是CPU的操作是原子性的,mTicket在输出后才被++,然后将新值赋值给mTicket,
     * 所以当不同线程抢占CPU的时候,会出现该变量卡在被赋值前,多个线程运行了输出语句。
     *
     * 问题2.和出现超过100的票,原因是线程切换随机性和代码延迟造成的,
     * 不同线程都成功通过了mTicket <= 100的判断后引发的并发异常
     */
}

多线程同步问题有两个出现条件

解决问题方式:
Synchronized关键字

使用synchronized关键字时还需要注意
1.该关键字是锁住了包含的代码,并非锁住了CPU的使用权,当同步代码块被一个线程抢占时,其他线程在同一时间不能访问同步代码块,但可以访问非同步代码块。
2.synchronized会封锁住所有影响的模块,如持有同一个对象锁的几个不同方法。当一个方法中的对象锁被某个线程占用,其他持锁的方法也不能被别的线程执行。
3.synchronized关键字不能继承。
对于父类中的 synchronized 修饰方法,子类在覆盖该方法时,默认情况下不是同步的,必须显示的使用 synchronized 关键字修饰才行。
4.在定义接口方法时不能使用synchronized关键字。
5.构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
更详细知识可参照这篇文章:
Java中Synchronized的用法

Lock方法
有几个实现类,在需要同步的代码中使用,比较常用的是reentrantLock类,方法lock(),unlock();建议采用try finally结构。

try {
                    mLock.lock();
                    try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(mTicketNum > 0) {
                    System.out.println(Thread.currentThread().getName() + " selling the ticket NO." + mTicketNum);
                    mTicketNum--;
                }
                }finally {
                    mLock.unlock();
                }

7、线程安全的工具类
线程安全是指多个Thread访问同一个对象的相同方法时不会出现同步的问题,比如多个线程不会同时调用一个集合的add()方法。但如果我们要使用线程非原子方法的组合,就不是线程安全了,比如

public void method() {
boolean absent = !list.contains(x);
      if(absent)
          list.add(x);
}

StringBuffer, Vector, Hashtable,通过源码可以看到他们的方法都加锁了。
Vector目前不推荐使用了,但可以通过Collections.synchronizedList()将线程不安全的集合转变为线程安全的集合。如

List<String> list = Collections.synchronizedList(new ArrayList<String>());

其内部实现也是将基本操作加上synchronized关键字,类似于Vector,所以也不推荐使用
说说线程安全包装:Collections.synchronizedList

不推荐的原因

8、同步的弊端
同步的弊端,一是效率低,因为每次运行到锁代码都要判断锁;二是可能产生死锁问题。
死锁:两个或以上的线程在争夺资源的时候,产生的相互等待的现象

//举个例子
class DeadLockTask implements Runnable {

    public static Object myLock1 = new Object();
    public static Object myLock2 = new Object();
    private boolean mFlag;

    public DeadLockTask(boolean mFlag) {
        this.mFlag = mFlag;
    }

    @Override
    public void run() {
        if (mFlag) {
            synchronized (myLock1) {
                System.out.println("if MyLock1");
                synchronized (myLock2) {
                    System.out.println("if MyLock2");
                }
            }
        } else {
            synchronized (myLock2) {
                System.out.println("else MyLock2");
                synchronized (myLock1) {
                    System.out.println("else MyLock1");
                }
            }
        }
    }
}

public class TestClass {

    public static void main(String[] args) {

        DeadLockTask task1 = new DeadLockTask(true);
        DeadLockTask task2 = new DeadLockTask(false);
        Thread th1 = new Thread(task1);
        Thread th2 = new Thread(task2);
        th1.start();
        th2.start();
    }
}

输出的是

else MyLock2
if MyLock1

9、线程间的通信
指的是不同线程间针对同一资源进行操作
不同线程之间都要加锁,且加的锁必须是一样的,原理参照上述synchronize关键字注意点。
经典例子生产者消费者实例代码

class SetTask implements Runnable {

    private boolean mFlag;
    private Student mStudent;

    public SetTask(Student student) {
        this.mStudent = student;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (mStudent) {
                if (mFlag) {
                    mStudent.name = "Leo";
                    mStudent.age = 24;
                } else {
                    mStudent.name = "Franz";
                    mStudent.age = 25;
                }
                mFlag = !mFlag;
            }
        }
    }
}

class GetTask implements Runnable {
    private Student mStudent;

    public GetTask(Student mStudent) {
        this.mStudent = mStudent;
    }

    @Override
    public void run() {
        while (true) {
            synchronized(mStudent) {
                System.out.println(mStudent.name + "  " + mStudent.age);
            }
        }
    }

}

class Student {
    String name;
    int age;
}

public class TestClass {

    public static void main(String[] args) {
        Student student = new Student();
        SetTask task1 = new SetTask(student);
        GetTask task2 = new GetTask(student);
        Thread th1 = new Thread(task1);
        Thread th2 = new Thread(task2);
        th1.start();
        th2.start();
    }

上述代码解决了线程安全问题,但没有处理好线程之间有序地通信(同一个线程仍然可能连续执行很多次),这就需要等待唤醒机制。

class SetTask implements Runnable {

    private boolean mFlag;
    private Student mStudent;

    public SetTask(Student student) {
        this.mStudent = student;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (mStudent) {
                if(mStudent.flag) {
                    try{
                        mStudent.wait();//释放锁,下次被唤醒的时候就从这里醒过来
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
                if (mFlag) {
                    mStudent.name = "Leo";
                    mStudent.age = 24;
                } else {
                    mStudent.name = "Franz";
                    mStudent.age = 25;
                }
                mFlag = !mFlag;

                mStudent.flag = true;
                mStudent.notify();//唤醒之后并不表示被唤醒线程立马拥有执行权,而是进入等待队列争夺执行权。
            }
        }
    }
}

class GetTask implements Runnable {
    private Student mStudent;

    public GetTask(Student mStudent) {
        this.mStudent = mStudent;
    }

    @Override
    public void run() {
        while (true) {
            synchronized(mStudent) {
                if(!mStudent.flag) {
                    try{
                        mStudent.wait();
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(mStudent.name + "  " + mStudent.age);

                mStudent.flag = false;
                mStudent.notify();
            }
        }
    }

}

class Student {
    String name;
    int age;
    boolean flag;
}

public class TestClass {

    public static void main(String[] args) {
        Student student = new Student();
        SetTask task1 = new SetTask(student);
        GetTask task2 = new GetTask(student);
        Thread th1 = new Thread(task1);
        Thread th2 = new Thread(task2);
        th1.start();
        th2.start();
    }
}//其实上述代码如果把Student的成员设置为私有,
//将Runnable中的方run()方法封装到Student中,并用synchronized修饰,更加规范.
//注意线程被notify的地方就是当初wait的后一行语句;

改进版本的代码如下:
public synchronized void changeName(String name,int age) {
        if (mTimes < 300) {
            if (flag) {
                setName(name);
                setAge(age);
                mTimes++;
                flag = false;
                notify();
            } else {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public synchronized void showName() {
        if (!flag) {
            System.out.println("Get student name: " + getAge() + getName());
            flag = true;
            notify();
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

public void run() {
        while(true) {
                if(mStudent.mTimes % 2 == 0) {
                    mStudent.changeName("Leonardo",24);
                }else {
                    mStudent.changeName("Hugo",25);
                }
            }
        }

public void run() {
        while(true) {
            mStudent.showName();
        }
    }

至此,线程生命周期就可以再次深化,以下附上图解:


10.线程组
Java中使用ThreadGroup来管理一组线程,并允许直接对线程组进行控制。默认情况下所有线程属于主线程组public final ThreadGroup getThreadGroup()

   Task task = new Task();
   Thread thread1 = new Thread(task);
   Thread thread2 = new Thread(task);
   System.out.println(thread1.getThreadGroup().getName());
   System.out.println(thread2.getThreadGroup().getName());
//默认都属于main线程

可以将新建的线程归类到特定线程组中

        ThreadGroup threadGroup = new ThreadGroup("Leonado Hugo up");
        Task task = new Task();
        Thread th1 = new Thread(threadGroup, task,"线程1");
        Thread th2 = new Thread(threadGroup, task,"线程2");
        System.out.println(th1.getThreadGroup().getName());
        //输出我会有二猫

线程组可以对线程进行批处理,比如threadGroup.setMaxPriority(7);不过开发中使用较少。

11.线程池
程序启动一个线程成本比较高,因为涉及到与操作系统交互,使用线程池能够很好地提高性能,因为线程池中的每一个线程代码执行完后并不会死亡,而是回到池中变成空闲状态,等待其他对象来调用。当程序中使用大量生存期很短的线程时,更应该使用线程池。
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)

//示例代码
ExecutorService pool = Executors.newFixedThreadPool(3);
// is a ThreadPoolExecutor exactly, implement of ExecutorService
        pool.submit(new OutTask());
        pool.submit(new OutTask());
//ThreadPoolExecutor还有execute方法提交一个Runnable任务
        pool.shutdown();
        //Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.
        //若没有这个方法,线程就会保留而不结束。

shutdown和shutDownNow方法的区别

线程池中独有的创建新线程的方法

public class TestClass {

    static class CallableTask implements Callable<Integer> {

        private Integer number;

        public CallableTask(Integer number) {
            this.number = number;
        }

        @Override
        public Integer call() throws Exception {
            return number * 2;
        }
    }
//Callable是有返回值的,其泛型就是call方法的返回值类型。
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        Future<Integer> result1 = pool.submit(new CallableTask(200));
        Future<Integer> result2 = pool.submit(new CallableTask(100));
        System.out.println(result1.get());//该方法用于获取线程执行结果
        System.out.println(result2.get());
        pool.shutdown();
    }
}

12、定时器
定时器用于调度单个或多个定时任务单次或多次地执行,由Timer和TimerTask结合使用,不过开发中一般不用他们,而是使用Quartz这个开源框架。
每一个Timer对应单个后台线程。当计时器所有任务执行完毕后,会被当做垃圾回收,但这可能花掉较长的时间——

After the last live reference to a Timer object goes away and all outstanding tasks have completed execution, the timer's task execution thread terminates gracefully (and becomes subject to garbage collection). However, this can take arbitrarily long to occur.

须知Timer对应的线程又不是守护线程,当用户想尽快结束该线程,应调用其cancel方法。
Timer是线程安全的,多个线程可以共享单个Timer而无需进行外部同步。
此类不提供实时保护,它使用Object.wait()来安排任务。
Timer有其固有的缺陷,

Timer tasks should complete quickly. If a timer task takes excessive time to complete, it "hogs" the timer's task execution thread. This can, in turn, delay the execution of subsequent tasks, which may "bunch up" and execute in rapid succession when (and if) the offending task finally completes.

Timer有很多构造器,简单用法如下:

Timer timer = new Timer();
timer.schedule(new TaskPackage(),2000);
//你会发现即使不是循环的任务,该线程也很可能不会再执行完后结束,
//需要手动调用cancel方法或者System.gc()。
上一篇 下一篇

猜你喜欢

热点阅读