【Java】多线程

多线程实现

继承Thread类实现多线程

class MyThread extends Thread{
    private String title;
    public MyThread(String title){
        this.title = title;
    }

    @Override
    public void run(){
        for (int i = 0;i < 10;i++){
            System.out.println(title + ",i = " + i);
        }
    }
}
public class Hello {
    public static void main(String[] args) throws Exception{
        MyThread my1 = new MyThread("thread1");
        MyThread my2 = new MyThread("thread2");
        my1.start();
        my2.start();
    }
}

多线程的启动必须通过调用start方法。

实现Runnable接口实现多线程

class MyThread2 implements Runnable{
    private String title;
    public MyThread2(String title){
        this.title = title;
    }

    @Override
    public void run(){
        for (int i = 0;i < 10;i++){
            System.out.println(title + ",i = " + i);
        }
    }
}

public class Hello {
    public static void main(String[] args) throws Exception{
        MyThread2 my3 = new MyThread2("thread3");
        MyThread2 my4 = new MyThread2("thread4");
        new Thread(my3).start();
        new Thread(my4).start();
    }
}

Runnable接口没有了继承的局限性,但是该接口并没有start函数,所以无法直接调用,就需要利用Thread提供的构造方法来调用。

public Thread(Runnable target);

同时这里可以使用内部类和lambda表达式。

Thread与Runnable关系

实际上Thread也是实现了Runnable的接口,并覆盖了run方法。这内部牵扯到代理模式。

Callable实现多线程

如果依靠Runnable接口,中的run方法没有返回值。如果需要返回值,就只能依靠Callable来实现多线程。

public interface Callable<V>{
    public V call() throws Exception;
}
class MyThread implements Callable<String> {
    private int ticket = 10;
    @Override
    public String call()  throws Exception {
        while(ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "还有" + ticket-- + "张票");
        }
        return "结束";
    }
}

public class Hello {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        FutureTask<String> task = new FutureTask<>(new MyThread());
        new Thread(task).start();
        System.out.println("返回数据:" + task.get());
    }
}

发现可以使用泛型,这个泛型就是返回值的类型。


线程状态转换图


多线程常用的操作方法

线程名命和获取

public Thread(Runnable terget,String name);//使用构造方法命名

public final synchronized void setName(String name);//使用一般方法命名

public final String getName();//获取线程名字

在Thread类里有个currentThread()函数可以夺取当前线程对象。

public static native Thread currentThread();
class MyThread2 implements Runnable{
    @Override
    public void run(){
        for (int i = 0;i < 10;i++){
            System.out.println(Thread.currentThread().getName()+",i="+i);
        }
    }
}

public class Hello {
    public static void main(String[] args) throws Exception{
        MyThread2 my = new MyThread2();
        new Thread(my).start();
        new Thread(my).start();
        new Thread(my,"hh").start();
    }
}

sleep方法

class MyThread implements Runnable{
    @Override 
    public void run(){
        for(int i=0;i<1000;i++){
            try {
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printstackTrace();
            }
        }
    }
}

该方法会让线程进入休眠。等时间过后才恢复执行。

线程休眠会交出CPU,让CPU执行其他任务。但sleep后不会释放锁。

yield方法

class MyThread implements Runnable {
    @Override
    public void run() {
        for(int i = 0;i < 3;i++){
            Thread.yield();
            System.out.println(Thread.currentThread().getName());
        }
    }
}

该方法会让线程让步,会暂停当前在执行的线程,并执行其他线程。

与sleep方法类似,同样不会释放锁。

yield不能控制交出CPU的时间。并且yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

yield不会让线程阻塞,而是让线程回到Runnable状态(就绪状态)

join方法

class MyThread implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("开始");
            Thread.sleep(3000);
        } catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

public class Hello {
    public static void main(String[] args) throws Exception{
        MyThread my = new MyThread();
        Thread thread = new Thread(my,"子线程");
        thread.start();
        thread.join();
        System.out.println("结束");
    }
}

等待该线程终止。

在主线程中调用后,会先让祖贤成进入休眠,直到调用join的线程run方法执行完毕,才会继续执行主线程。

线程停止

1:使用一个标记位,通过判断让线程运行完停止。

2:调用stop方法退出。(该方法不安全,一般不用)
为什么不安全:stop方法会在任意时刻完全停止,并解除该线程获得的锁。所以可能会导致线程同步的混乱问题。

3:使用Thread类中的一个interrupt()中断线程

线程优先级

优先级越高的线程越有可能执行。

public final void setPriority(int new Priority);//Thread类下的设置优先级

public final int getPriority();//获取优先级

Thread类提供了几个常量:

  1. 最高优先级:public final static int MAX_PRIORITY=10;
  2. 中等优先级:public final static int NORM_PRIORITY=5;
  3. 最低优先级:public final static int MIN_PRIORITY=1;

主方法只是一个中等优先级。

线程是有继承关系的,比如当A线程中启动B线程,那么B和A的优先级将是一样的。

守护线程

守护线程是一种特殊的线程,它属于是一种陪伴线程。

简单点说java中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。

典型的守护线程就是垃圾回收线程。只要当前VM进程中存在任何一个非守护线程没有结束,守护线程就在工作;只有当最后一个非守护线程结束时,守护线程才会随着JVM一同停止工作。

使用setDaemon(bool isDaemon);来设置当前线程是否为守护线程。

该方法必须在start之前执行


线程的同步和死锁

synchronized处理同步问题

同步代码块

synchronized (同步对象){
    同步代码操作;
}

一般进行同步推向处理的时候可以采用当前对象this进行同步。

class MyThread implements Runnable{
    private int ticket = 10;
    @Override
    public void run(){
        for (int i = 0;i < 100;i++) {
            System.out.println(i + "--");
            synchronized (this) {//表示程序逻辑上上锁
                if (this.ticket > 0) {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " " + ticket--);
                }
            }
        }
    }
}

同步方法

class MyThread implements Runnable{
    private int ticket = 10;
    @Override
    public void run(){
        for (int i = 0;i < 100;i++) {
            sale();
        }
    }

    public synchronized void sale(){
        synchronized (this) {//表示程序逻辑上上锁
            if (this.ticket > 0) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " " + ticket--);
            }
        }
    }
}

wait()方法和notfiy()方法

wait方法

wait方法使当前执行代码的线程停止运行,并释放对象锁。该方法是Object类的方法。

特点:

  • wait()使当前线程调用该方法后进行等待,并且将该线程置入锁对象的等待队列中,直到接到通知或被中断为止。
  • wait()方法只能在同步方法中或同步块中调用。
  • 如果调用wait()时,没有持有适当的锁,就会抛出异常。
  • wait()方法执行后,当前线程释放锁,线程与其它线程竞争重新获取锁。
  • wait()之后的线程继续执行有两种方法:
    • 调用该对象的notify()唤醒等待线程
    • 线程等待时调用interrupt()中断该线程

notify()方法

notify方法使停止的线程继续运行。

1.方法notify()也要在同步方法或同步块中调用。

用来通知可能在等待该对象的对象锁的其它线程,使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程规划器随机挑选出一个呈wait状态的线程。

2.在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

class MyThread implements Runnable{
    private boolean flag;
    private Object obj;

    public MyThread(boolean flag,Object obj){
        super();
        this.flag = flag;
        this.obj = obj;
    }

    public void waitMethod(){
        synchronized (obj){
            try{
                while(true){
                    System.out.println("wait-begin:" + Thread.currentThread().getName());
                    obj.wait();
                    System.out.println("wait-end:" + Thread.currentThread().getName());
                    return;
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    public void notifyMethod(){
        synchronized (obj){
            try {
                System.out.println("notify-begin:" + Thread.currentThread().getName());
                obj.notify();
                System.out.println("notify-end:" + Thread.currentThread().getName());

            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    public void run(){
        if(flag){
            waitMethod();
        }else {
            notifyMethod();
        }
    }
}

public class Hello {
    public static void main(String[] args) throws InterruptedException{
        Object object = new Object();
        MyThread waitThread = new MyThread(true,object);
        MyThread notifyThread = new MyThread(false,object);

        Thread thread1 = new Thread(waitThread,"wait线程");
        Thread thread2 = new Thread(notifyThread,"notify线程");

        thread1.start();
        Thread.sleep(1000);
        thread2.start();

        System.out.println("main-end");
    }
}

//结果:
//wait-begin:wait线程
//main-end
//notify-begin:notify线程
//notify-end:notify线程
//wait-end:wait线程

notifyAll()方法

notify只是唤醒一个等待的线程。使用notifyAll可以唤醒所有等待的线程。

小结

出现阻塞的情况大体分为如下5种:

  1. 线程调用sleep方法,主动放弃占用的处理器资源。
  2. 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。
  3. 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
  4. 线程等待某个通知。
  5. 程序调用了suspend方法将该线程挂起。此方法容易导致死锁尽量避免使用该方法。

每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。

posted @ 2020-02-01 19:47  LampsAsarum  阅读(103)  评论(0编辑  收藏  举报