并发编程1-多线程基础

30 篇文章 0 订阅

1.概述

1.1 并发编程进阶

熟练掌握API,能够完成并发编程->熟读API源码,掌握其原理->理解Java虚拟机的内存模型->操作系统对并发的支持

1.2 并发编程优缺点

  • 优点
    发挥多处理的强大能力, 建模的简单性, 异步事件的简化处理, 响应更加灵敏的用户界面
  • 缺点
    安全性问题,活跃性问题(抢占CPU,饥饿问题), 性能问题

1.3 线程安全性问题

创建线程 多种方式
多线程运行 卖票
线程安全性问题引入
提出解决方案 synchronized
解决线程安全性问题
Synchronized 原理

1.4 多线程与并发联系及区别

1个线程1个顺序执行流.
多线程就是多个线程切换执行,并行是多个线程同时执行.

1.5 线程与进程

  • 进程:运行中的程序,进程是资源分配的基本单位,每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。
    • 进程是资源分配的最小单位
    • 多进程是指操作系统能同时运行多个任务(程序)。
    • 进程中包含多个线程,线程共享进程的资源
  • 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
    • 线程是cpu调度的最小单位
    • 多线程是指在同一程序中有多个顺序流在执行。
    • 线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

2.创建线程方式

2.1 继承Thread

public class Thread1 extends  Thread{

    public  Thread1(String name){
        super(name);
    }

    @Override
    public void run() {
        //super.run();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "运行  :  " + i);
            try {
                sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread1 t1 = new Thread1("t1");
        Thread1 t2 = new Thread1("t2");
        t1.start();
        t2.start();

    }
}

2.2 实现Runnable

/**
 * 多线程实现-实现Runnable
 */
public class Thread2 implements Runnable {


    @Override
    public void run() {
        System.out.println("Thread1-current->" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Thread2 runnable1 = new Thread2();
        Thread t1 = new Thread(runnable1);
        Thread t2 = new Thread(runnable1);
        t1.start();
        t2.start();

    }
}

2.3 匿名内部类的方式

public class AnonymousInner {

    /**
     anonymous1 extend thread start ..
     anonymous2 Runnable thread start ..
     anonymous1and2 extend thread start ..
     */
    public static void main(String[] args) {
        anonymous1();
        anonymous2();
        anonymous1and2();
    }

    //继承Thread覆盖方式
    public static void anonymous1() {
        new Thread() {
            public void run() {
                System.out.println("anonymous1 extend thread start ..");
            }

            ;
        }.start();
    }

    //实现Runnable覆盖方式
    public static void anonymous2() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("anonymous2 Runnable thread start ..");
            }
        }).start();
    }

    //继承Thread覆盖并且实现Runnable覆盖方式
    //runnable的run也是传给thread,最后被thread的run覆盖
    public static void anonymous1and2() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("anonymous1and2 Runnable thread start ..");
            }
        }) {
            public void run() {
                System.out.println("anonymous1and2 extend thread start .. ");
            };
        }.start();
    }
}

//运行结果
anonymous1 extend thread start ..
anonymous2 Runnable thread start ..
anonymous1and2 extend thread start ..

2.4 Callable

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

//传入的是Integer类型,返回的就是Integer类型
public class NewThreadCallable implements Callable {

public static void main(String[] args) throws Exception {
	NewThreadCallable d = new NewThreadCallable();

	FutureTask<Integer> task = new FutureTask<>(d);
	Thread t = new Thread(task);
	t.start();//启动的时候则call()开始执行了

	System.out.println("我先干点别的。。。");

	Integer result = task.get();
	System.out.println("线程执行的结果为:" + result);
}

//类似run方法
@Override
public Integer call() throws Exception {
	System.out.println("正在进行紧张的计算....");
	Thread.sleep(3000);
	return 1;
}

}

3.线程状态

StatusThread1.java
StatusThread2.java
StatusThread3.java.


3.1 状态概述

线程基础-线程状态-1

线程基础-线程状态-2

线程状态

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)

(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)

5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

3.2 注意

  • start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
  • Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
  • 实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。

4.线程调度

4.1 概述

  • 1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。

Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
static int MAX_PRIORITY
线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
分配给线程的默认优先级,取值为5。

Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。

    • 每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
    • 线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
    • JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
  • 2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。

  • 3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。

  • 4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。

  • 5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。

  • 6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
    注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。

4.2 Thread常用函数说明

4.2.1 start()

它的作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。

4.2.2 run()

run()就和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程!

4.2.3 start和ruan的比较

mythread.start()会启动一个新线程,并在新线程中运行run()方法。
而mythread.run()则会直接在当前线程中运行run()方法,并不会启动一个新线程来运行run()。

package bob.bob1;

public class RunAndStart {
    public static void main(String[] args) {
        Thread mythread = new MyThread("mythread");

        System.out.println(Thread.currentThread().getName() + " call mythread.run()");
        mythread.run();
        System.out.println("----------------");
        System.out.println(Thread.currentThread().getName() + " call mythread.start()");
        mythread.start();
    }
}


class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }

    public void run() {
        System.out.println(Thread.currentThread().getName() + " is running");
    }
}

运行结果如下:

main call mythread.run()
main is running
----------------
main call mythread.start()
mythread is running
  • mythread.run()是在“主线程main”中调用的,该run()方法直接运行在“主线程main”上。
  • mythread.start()会启动“线程mythread”,“线程mythread”启动之后,会调用run()方法;此时的run()方法是运行在“线程mythread”上。

Java多线程系列–“基础篇”03之 Thread中start()和run()的区别

4.2.4 setPriority()

setPriority(): 更改线程的优先级。
优先级:只能反映 线程 的 中或者是 紧急程度,不能决定是否一定先执行.

 1~10   1最低  10最高    5是默认值
  MIN_PRIORITY = 1
   NORM_PRIORITY = 5
    MAX_PRIORITY = 10

用法:

Thread4 t1 = new Thread4("t1");
Thread4 t2 = new Thread4("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);

4.2.5 sleep()

sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

4.2.6 wait()、notify()、notifyAll()

4.2.6.1 概述
  • Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作
    • 从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。
    • 并且synchronized的还要是当前类的实例
  • 从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。
  • 但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。
  • Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。
4.2.6.2 举例
  • 无synchroinzed
package t1;

public class StatusThread3 implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("自定义的线程执行了....");
            try {
                //wait方法之后一直处于等待状态,直到调用了notify或notifyall
                //如果没有synchronized则不能wait或notify
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        StatusThread3 n = new StatusThread3();

        // 初始化状态
        Thread thread = new Thread(n); // 创建线程,并指定线程任务
        thread.start(); // 启动线程

        while (true) {
            System.out.println("主线程执行了...");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            n.notifyAll();
        }
    }
}

执行报错信息如下:

Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
 at java.lang.Object.wait(Native Method)
 at java.lang.Object.wait(Object.java:502)
 at course.threadstu.t1.StatusThread3.run(StatusThread3.java:11)
 at java.lang.Thread.run(Thread.java:745)
 主线程执行了...
 自定义的线程执行了....
 Exception in thread "main" java.lang.IllegalMonitorStateException
 at java.lang.Object.notifyAll(Native Method)
 at course.threadstu.t1.StatusThread3.main(StatusThread3.java:33)

 Process finished with exit code 1
  • 有synchroinzed
package t1;

public class StatusThread4 implements Runnable {

    @Override
    public synchronized void run() {
        while(true) {
            System.out.println("自定义的线程执行了....");
            try {
                //wait方法之后一直处于等待状态,直到调用了notify或notifyall
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        StatusThread4 n = new StatusThread4();

        // 初始化状态
        Thread thread = new Thread(n); // 创建线程,并指定线程任务
        thread.start(); // 启动线程

        while(true) {
            synchronized (n) {
                System.out.println("主线程执行了...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                n.notifyAll();
            }
        }
    }
}

4.2.6.3 wait和notify源码分析
  • Object.java
public class Object {

        private static native void registerNatives();
        static {
            registerNatives();
        }

        public final void wait() throws InterruptedException {
            wait(0);
        }    
        
        public final native void wait(long timeout) throws InterruptedException;
        
        
        public final void wait(long timeout, int nanos) throws InterruptedException {
            if (timeout < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
    
            if (nanos < 0 || nanos > 999999) {
                throw new IllegalArgumentException(
                                    "nanosecond timeout value out of range");
            }
    
            if (nanos > 0) {
                timeout++;
            }
    
            wait(timeout);
        }
        
        public final native void notify();
        
        public final native void notifyAll();
    
}

4.2.7 join()

  • join():指等待t线程终止

  • 使用方式。
    join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
    Thread t = new AThread();
    t.start();
    t.join();

  • 为什么要用join()方法
    在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,
    但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,
    这个时候就要用到join()方法了。

  • 示例

class Thread1 extends Thread {
    private String name;

    public Thread1(String name) {
        super(name);
        this.name = name;
    }

    public void run() {
        System.out.println(Thread.currentThread().getName() + " 线程运行开始!");
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程" + name + "运行 : " + i);
            try {
                sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " 线程运行结束!");
    }
}



public class Thread1Test1 {
    /**
     * main主线程运行开始!
     * main主线程运行结束!
     * B 线程运行开始!
     * 子线程B运行 : 0
     * A 线程运行开始!
     * 子线程A运行 : 0
     * 子线程B运行 : 1
     * 子线程A运行 : 1
     * 子线程B运行 : 2
     * 子线程A运行 : 2
     * 子线程B运行 : 3
     * 子线程A运行 : 3
     * 子线程B运行 : 4
     * 子线程A运行 : 4
     * B 线程运行结束!
     * A 线程运行结束!
     * <p>
     * 主线程结束的比子线程早
     */
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "主线程运行开始!");
        Thread1 mTh1 = new Thread1("A");
        Thread1 mTh2 = new Thread1("B");
        mTh1.start();
        mTh2.start();
        System.out.println(Thread.currentThread().getName() + "主线程运行结束!");

    }
}


public class Thread1Test2 {
    /**
     main主线程运行开始!
     A 线程运行开始!
     B 线程运行开始!
     子线程A运行 : 0
     子线程B运行 : 0
     子线程A运行 : 1
     子线程B运行 : 1
     子线程A运行 : 2
     子线程B运行 : 2
     子线程A运行 : 3
     子线程B运行 : 3
     子线程B运行 : 4
     子线程A运行 : 4
     B 线程运行结束!
     A 线程运行结束!
     main主线程运行结束!

     * 主线程结束的比子线程早
     */
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "主线程运行开始!");
        Thread1 mTh1 = new Thread1("A");
        Thread1 mTh2 = new Thread1("B");
        mTh1.start();
        mTh2.start();

        //wait();

        try {
            System.out.println("mTh1=->"+mTh1.isAlive());
            mTh1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            mTh2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "主线程运行结束!");

    }
}

Thread1Test1中的主线程,两个子线程是各自运行的,
Thread1Test2中的两个子线程join到主线程后,是两个子线程完成之后主线程才完成的.

4.2.8 setDemon 守护线程

  • 守护线程–也称“服务线程”,在没有用户线程可服务时会自动离开

   只要当前JVM实例中尚存任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束是,守护线程随着JVM一同结束工作,Daemon作用是为其他线程提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),他就是一个很称职的守护者。

  • 优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务

  • 生命周期:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。那Java的守护线程是什么样子的呢。当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则JVM不会退出

  • 线程类型:用户线程和守护线程。Thread.setDaemon(false)用户线程[默认];setDaemon(true)设置为守护线程。

  • 用户线程和守护线程的关系

  1.主线程结束,如果还有用户线程运行,JVM存活;

  2.主线程结束,如果没有用户线程运行,则守护线程结束,JVM也结束

  • example: 垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

  • 示例


public class NewThreadExtendAndDaemon extends Thread {

    public NewThreadExtendAndDaemon(String name) {
        super(name);
    }

    @Override
    public void run() {
        while(true) {
            System.out.println(getName() + "线程执行了 .. ");
        }
    }

    public static void main(String[] args) {
        NewThreadExtendAndDaemon d1 = new NewThreadExtendAndDaemon("first-thread");
        NewThreadExtendAndDaemon d2 = new NewThreadExtendAndDaemon("second-thread");

        d1.setDaemon(true);//守护线程
        d2.setDaemon(true);//守护线程

        d1.start();
        d2.start();

        try{
            Thread.sleep(2000);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

//以上线程d1和d2会随着主线程的离开(执行完毕)而退出. 随后JVM也会退出.

4.2.9 yield()

  • yield():暂停当前正在执行的线程对象,并执行其他线程。
  • Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
  • yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。
    因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。可看上面的图。

4.2.10 interrupt(),isInterrupted,interrupted,InterruptedException

返回线程的上次的中断状态,并清除中断状态,不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!

  • isInterrupted() 判断当前线程是否中断(Tests whether this thread has been interrupted.)
  • interrupted() 返回线程的上次的中断状态,并清除中断状态
public class NewThreadExtendAndInterrupt extends Thread {

    public NewThreadExtendAndInterrupt(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (!interrupted()) {//和interrupt();搭配使用
            System.out.println(getName() + "线程执行了 .. ");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        NewThreadExtendAndInterrupt d1 = new NewThreadExtendAndInterrupt("first-thread");
        NewThreadExtendAndInterrupt d2 = new NewThreadExtendAndInterrupt("second-thread");

        d1.start();
        d2.start();

        d1.interrupt();//JDK6才出现
    }
}

执行结果

second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 

java线程中的interrupt,isInterrupt,interrupted方法

4.2.11 sleep()和yield()的区别

  • sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;
  • yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
  • sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,
  • yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。
    实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
  • 另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

4.2.12 wait和sleep区别

  • 共同点:
  1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
  2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
    如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
    需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。
  • 不同点:
  1. Thread类的方法:sleep(),yield()等
    Object的方法:wait()和notify()等
  2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。
    sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
  3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
  4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
    所以sleep()和wait()方法的最大区别是:
        sleep()睡眠时,保持对象锁,仍然占有该锁;
        而wait()睡眠时,释放对象锁。
      但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。
    • sleep()方法
      sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
         sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
        在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。
    • wait()方法
      wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;
        wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
        wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。

4.2.13 stop

stop方法天生就不安全,因为它在终止一个线程时会强制中断线程的执行,不管run方法是否执行完了,并且还会释放这个线程所持有的所有的锁对象

public class NewThreadExtendAndStop extends Thread {

    public NewThreadExtendAndStop(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "线程执行了 .. ");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        NewThreadExtendAndStop d1 = new NewThreadExtendAndStop("first-thread");
        NewThreadExtendAndStop d2 = new NewThreadExtendAndStop("second-thread");

        d1.start();
        d2.start();

        d1.stop(); //已过期(    @Deprecated).
    }

}

运行结果如下:

second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 
second-thread线程执行了 .. 
......

4.2.14 suspend

suspend被弃用的原因是因为它会造成死锁。suspend方法和stop方法不一样,它不会破换对象和强制释放锁,相反它会一直保持对锁的占有,一直到其他的线程调用resume方法,它才能继续向下执行。
java多线程(八)为什么弃用stop和suspend

4.3 Thread相关类重要源码分析

  • Thread
package java.lang;

public
class Thread implements Runnable {

源码Thread类中优先级:
    public final static int MIN_PRIORITY = 1;
    
    public final static int NORM_PRIORITY = 5;
    
    public final static int MAX_PRIORITY = 10;
    不同操作系统对应的优先级不同. window1-10.linux?

    private volatile char  name[];
    private int            priority;
    private Thread         threadQ; 
    
    /* What will be run. */
    private Runnable target;
    
    
    * The group of this thread */
    private ThreadGroup group;
    
    //daemon  /'dimən/  守护进程;后台程序
    private boolean     daemon = false;
    
    
    /*
     * The requested stack size for this thread, or 0 if the creator did
     * not specify a stack size.  It is up to the VM to do whatever it
     * likes with this number; some VMs will ignore it.
     */
     栈的大小,如果没指定默认为(一些VM会用这个值,一些VM忽略这个)
    private long stackSize;
    
    
    构造方法1
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    构造方法2 根据name的构造方法,init(ThreadGroup g,Runable target,String name,long stackSize). 
    public Thread(String name) {
        init(null, null, name, 0);
    }
    
    
    构造方法3--传入Runnable
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }




    private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
        init(g, target, name, stackSize, null);
    }


    
   public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }


    private native void start0();
  
       
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    
    
    public final void join() throws InterruptedException {
            join(0);
        }
        
        
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);//如果子线程存活则让当前线程wait
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    
    
     public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
    
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    
    
     public boolean isInterrupted() {
        return isInterrupted(false);
    }

    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);
    
     /* Some private helper methods */
    private native void setPriority0(int newPriority);
    private native void stop0(Object o);
    private native void suspend0();
    private native void resume0();
    private native void interrupt0();
    private native void setNativeName(String name);
}

    
    
  • Runnable
@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
  • ThreadGroup 线程组源码
package java.lang;
public class ThreadGroup implements Thread.UncaughtExceptionHandler {


        private void init(ThreadGroup g, Runnable target, String name,
                              long stackSize, AccessControlContext acc) {}
        //如果ThreadGroup分组为空,则用parent.getThreadGroup();
         if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        
         activeGroupCount(活跃的线程数目).
        public int activeGroupCount() {
            int ngroupsSnapshot;
            ThreadGroup[] groupsSnapshot;
            synchronized (this) {
                if (destroyed) {
                    return 0;
                }
                ngroupsSnapshot = ngroups;
                if (groups != null) {
                    groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
                } else {
                    groupsSnapshot = null;
                }
            }
            int n = ngroupsSnapshot;
            for (int i = 0 ; i < ngroupsSnapshot ; i++) {
                n += groupsSnapshot[i].activeGroupCount();
            }
            return n;
        }
        
        interrupt(中断线程组的所有线程)
        public final void interrupt() {
            int ngroupsSnapshot;
            ThreadGroup[] groupsSnapshot;
            synchronized (this) {
                checkAccess();
                for (int i = 0 ; i < nthreads ; i++) {
                    threads[i].interrupt();
                }
                ngroupsSnapshot = ngroups;
                if (groups != null) {
                    groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
                } else {
                    groupsSnapshot = null;
                }
            }
            for (int i = 0 ; i < ngroupsSnapshot ; i++) {
                groupsSnapshot[i].interrupt();
            }
        }

}

5.常见线程名词解释

主线程:JVM调用程序main()所产生的线程。
当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。
后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束
前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。
线程类的一些常用方法:

sleep(): 强迫一个线程睡眠N毫秒。
  isAlive(): 判断一个线程是否存活。
  join(): 等待线程终止。
  activeCount(): 程序中活跃的线程数。
  enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
  isDaemon(): 一个线程是否为守护线程。
  setName(): 为线程设置一个名称。
  wait(): 强迫一个线程等待。
  notify(): 通知一个线程继续运行。
  setPriority(): 设置一个线程的优先级。

6.线程同步

6.1 概述

  • synchronized关键字可以函数的修饰符,也可作为函数内的语句,也就是同步方法和同步语句块。synchronized还可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
  • A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
  • B.每个对象只有一个锁(lock)与之相关联。
  • C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

6.2 用法

6.2.1 类.class

synchronized(A.class){}
对类的class即:class literal(类名称字面常量)加锁,即对所有此所属的类加锁

6.2.2 类中static方法

对类加锁,即对所有此类的对象加锁,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

6.2.3 类中普通方法

synchronized的方法可以称为同步方法,对类对象(object reference)加锁,即对所有此类的对象加锁,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。
这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;

6.2.4 代码块

synchronized的代码可以称为同步代码块,这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象;

public void method3(SomeObject so)
{ 
  synchronized(so){//...}
}
class Foo implements Runnable
{
  private byte[] lock = new byte[0];  // 特殊的instance变量
  Public void methodA(){
       synchronized(lock) { //… }
  }
}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。


public class SyncObject {

    private byte[] lock = new byte[0];

    public synchronized void methodA() {//对当前对象加锁

    }

    public void methodB() {
        synchronized (this) {
        }//对当前对象加锁,与methodA用法相同

    }

    public void methodB2(Object o) {
        synchronized (o) {
        }//对o加锁
    }

    /**
     对lock加锁,零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:
     生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
     */
    public void methodB3() {
        synchronized (lock) {
        }

    }

    public static synchronized void methodC() {
    }//对类加锁,即对所有此类的对象加锁

    public void methodD() {

        synchronized (SyncObject.class) {
        }//对类加锁,即对所有此类的对象加锁
    }

}

6.3 synchronized注意

  • 静态代码块是不能synchronized,即:static synchronized(this){/区块/} 是不合法的.因为静态代码块是类加载的时候执行一次,不会因为多线程造成数据不一致的情况.
  • synchronized关键字是不能继承的.基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
  • Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。
    • 可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,
      因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

6.4 优缺点

1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。

7.线程数据传递

在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。
但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。
由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。

7.1、通过构造方法传递数据

在创建线程时,必须要建立一个Thread类的或其子类的实例。
因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用类变量保存起来,
以便线程使用(其实就是在run方法中使用)。下面的代码演示了如何通过构造方法来传递数据:

public class MyThread1 extends Thread {
    private String name;

    public MyThread1(String name) {
        this.name = name;
    }

    public void run() {
        System.out.println("hello " + name);
    }

    public static void main(String[] args) {
        Thread thread = new MyThread1("world");
        thread.start();
    }
}   

由于这种方法是在创建线程对象的同时传递数据的,这样就不会造成数据在线程运行后才传入的现象。
如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。
由于Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。
因此,要想避免这种情况,就得通过类方法或类变量来传递数据。

7.2、通过变量和方法传递数据

向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,
另外一次机会就是在类中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋值。
下面的代码是对MyThread1类的改版,使用了一个setName方法来设置 name变量:


public class MyThread2 implements Runnable {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void run() {
        System.out.println("hello " + name);
    }

    public static void main(String[] args) {
        MyThread2 myThread = new MyThread2();
        myThread.setName("world");
        Thread thread = new Thread(myThread);
        thread.start();
    }
}

7.3、通过回调函数传递数据

上面两种方法都是main方法中主动将数据传入线程类的。
这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据,
如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。
从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个 value是无法事先就传入线程类的。

class Data {
    public int value = 0;
}

class Work {
    public void process(Data data, Integer... numbers) {
        for (int n : numbers) {
            data.value += n;
        }
    }
}

public class MyThread3 extends Thread {
    private Work work;

    public MyThread3(Work work) {
        this.work = work;
    }

    public void run() {
        java.util.Random random = new java.util.Random();
        Data data = new Data();
        int n1 = random.nextInt(1000);
        int n2 = random.nextInt(2000);
        int n3 = random.nextInt(3000);
        work.process(data, n1, n2, n3); // 使用回调函数
        System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+"
                + String.valueOf(n3) + "=" + data.value);
    }

    public static void main(String[] args) {
        Thread thread = new MyThread3(new Work());
        thread.start();
    }
}

–整理自网络

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值