并发基础

并发

简介:

一台电脑的性能优异基本都是看cpu,而cpu调用的最小单元是线程。当电脑只有一个cpu处理器时,单线程程序能够百分百利用cpu。但是当电脑拥有多cpu的时候,单线程的程序就会浪费很多的资源。而并发是多个线程同时进行,能够充分利用多cpu的资源,提高系统的吞吐量。

线程的基本概念:

线程表示一个单独的执行流,它有自己的程序执行计数器,有自己的栈。每一个线程的运行都会执行相应的功能,但是它也是有成本的。线程的有点和成本如下:
(1)充分的利用多cpu的计算能力,单线程只能利用一个CPU,使用多线程可以利用多CPU 的计算能力。
(2)充分利用硬盘资源,cpu和硬盘、网络是可以同时工作的,一个线程在等待网络IO的同时,另外一个 线程完全可以利用cpu做其他事情。对于多个独立的网络请求,完全可以使用多个线程同时请求。

线程基本属性和方法:

(1)属性:

每一个线程都有id和name,id是一个递增的整数,每创建一个线程就会加1。
name 的默认值是Thread-后跟一个编号。name可以通过setName(String name) 来设置一个更友好的名字,可以方便调试。

 ThreadDemo threadDemo = new ThreadDemo();
 Long id = threadDemo.getId();
 String name = threadDemo.getName();
 System.out.println(id +":"+ name);
(2)优先级:

线程有一个优先级的概念,在Java中,优先级从1到10,默认是5。这个优先级会映射到操作系统中线程的优先级。不过,操作系统不相同,不一定都是10个优先级,java不同优先级可能会被映射到操作系统相同的优先级。另外,优先级对操作系统而言主要是一种建议和提示,而非强制。简单地说,在编程中,不要过于依赖优先级。
优先级的方法:

方法返回值说明
setPriority(int newPriority)void设置优先级
getPriority()int获取当前线程优先级
 ThreadDemo threadDemo = new ThreadDemo();
 int priority = threadDemo.getPriority();
 System.out.println("优先级为: "+priority);
 thread1.setPriority(10);
(3)状态:

线程都有相应的状态,同时也提供了获取状态的方法:
public State getState();
返回值是个枚举值,有如下值:
.NEW: 没有被调用时的状态就是NEW。
.RUNNABLE:调用start 启动线程在执行run方法没阻塞时状态为RUNNABLE。不过,RUNNABLE不代表cpu一定在执行该线程的代码,可能正在执行,也可能在等待操作系统分配时间片,只是它没有在等待其他条件
.BLOCKED:在等待队列等待锁,表示阻塞。
.WAITING:在条件队列等待被唤醒,表示阻塞。
.TIMED_WAITING:在条件队列等待超时,表示阻塞。
.TERMINATED:表示线程运行结束后的状态。

 ThreadDemo threadDemo = new ThreadDemo();
 Thread.State state = thread1.getState();
 System.out.println(state);
(4)daemon线程:

daemon线程是其他线程的辅助线程。在它辅助的主线程退出的时候,它就没有存在的意义了。在我们执行只有main线程的时候,Java还会创建一个负责垃圾回收的线程,这个线程就是daemon线程,main结束后垃圾回收线程也会退出。

(5)sleep 方法:
方法名返回值说明
sleep(int millis )void使当前线程睡眠指定时间
Thread.sleep(1000);

调用该方法会使得调用线程进入睡眠,睡眠期间会让出cpu,但是不会释放锁对象。

(6)yield 方法:
方法名返回值说明
yield()void使当前线程让出cpu

调用该方法会使当前线程让出cpu。

Thread.yield();
(7) join 方法:
方法名返回值说明
join()void调用join的线程等待该线程结束
join(long millis)void调用join的线程等待指定的时间在退出
 ThreadDemo threadDemo1 = new ThreadDemo();
 threadDemo1.start();
 threadDemo1.join();
 System.out.println("main 线程执行流");

上面的代码要threadDemo1的线程执行完之后才会执行main线程后面的代码

并发线程共享内存出现的问题:

竞态条件:

每个线程表示一条单独的执行流,有自己的程序计数器,有自己的栈。但是线程之间可以共享内存,他们可以访问和操作相同的对象。静态条件是当多个线程访问和操作同一个对象时,最终执行的结果和执行顺序有关,结果可能正确和可能不正确。例子如下:

package concurrent;

import java.util.ArrayList;
import java.util.List;

/**
 *共享内存及可能存在的问题 竞态条件
 * 每个线程表示一条单独的执行流,有自己的程序计数器,有自己的栈。
 * 但是线程之间可以共享内存,他们可以访问和操作相同的对象。
 */

public class ConcurrentMemoryDemo{

    private static int shared = 0;
    private static void incrShared() {
        shared++;
    }

    static class ChildThread extends Thread {
        List<String> list;

        public ChildThread(List<String> list) {
            this.list = list;
        }


        @Override
        public void run() {
            incrShared();
            list.add(Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //不同执行流可以访问和操作相同的变量。如shared和list。不同执行流可以执行相同的代码。
        //当多条执行流执行相同的代码时,会出现竞态条件和内存可见性的问题。静态条件是当多个线程访问和
        //操作同一个对象时,最终执行的结果和执行顺序有关,可能正确和可能不正确
       List<String> list = new ArrayList<>();
       Thread thread = new ChildThread(list);
       Thread thread1 = new ChildThread(list);
       thread.start();
       thread.join();
       thread1.start();
       thread1.join();
       System.out.println(shared);
       System.out.println(list);
    }
}

内存可见性:

多个线程可以共享访问和操作相同的变量。但一个线程对一个共享变量的修改,另外一个线程不一定马上就能看到,甚至永远也看不到。这是因为什么呢?因为在计算机系统里,除了内存,数据还会被缓存到在cpu的寄存器和各级缓存中。当访问一个变量时,可能是直接从寄存器和缓存中取的,而不一定从内存中取。当修改一个变量时,也可能先修改到缓存中,稍后才会同步到内存里。在多线程的程序中,尤其是多cpu的情况下,这就是一个严重的问题。一个线程对内存的修改,另外一个线程看不到。一是修改没有及时同步到内存。二是另外一个线程根本没去内存读。

package concurrent;

/**
 * 内存可见性
 */
public class MemoryShow {
    private static boolean shutdown = false;

    static  class HelloThread extends Thread {

        @Override
        public void run() {
           while(!shutdown){

           }
            System.out.println("exit hello");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new HelloThread().start();
        Thread.sleep(1000);
        shutdown = true;
        System.out.println("exit main");
    }
}

解决上面的问题都是需要用到vollatile关键字和synchronized关键字或者显示锁

synchronized关键字:

synchronized 关键字可以用于修饰实例方法,代码块,静态方法。

synchronized的特征:

(1)可重入性:

可重入性指的是同一个执行线程,获得锁之后,再调用其他需要同样锁的代码时,可以直接调用。比如在一个Synchronized实例方法内,可以直接调用其他Synchronized实例方法。 可重入是通过记录锁的持有线程和持有数量来实现的,当调用被Synchronized保护的代码时,检查对象是否被锁, 如果是, 再检查是否被当前线程锁定,如果是增加持有数量。如果不是被当前线程锁定,加入等待队列。当释放锁的时候,减少持有数量,当数量变为0时才释放锁。

(2)内存可见性:

保证共享变量的内存可见性。在释放锁的时候,所有写入都会写到主内存中,获取的锁后都会从内存中取。保证内存可见性用Synchronized成本太高。有个轻量级的方式,就是用volatile。加入volatile之后,Java会在操作对应变量时插入特殊的指令,保证读写内存最新值,而非缓存的值。

(3) 死锁:

使用Synchronized或者其他锁时,都需要注意死锁。所谓死锁就是类似:有a、b两个线程,a持有A锁, 在等待B锁。而b持有B锁,在等待A锁。a和b陷入了互相等待,最后谁都执行不下去。解决死锁的方法是尽量避免在持有一个锁的时候去申请另外一个锁,确实需要多个锁,所有代码应该按相同的顺序去申请锁。比如都先约定申请A锁,在申请B锁。当然在复杂的项目是很难约定的。那这时就要用显示锁接口lock。它支持尝试获取锁, 和带时间限制的获取锁方法。使用这些方法可以在尝试获取不到锁的时候释放已持有的锁,然后再次尝试获取锁或者干脆放弃,以避免死锁的情况。如果还是出现死锁,java自带的 jstack命令会报告发现的死锁。

public class SynchronizedDemo {
    private static void startLockA(){
        Thread thread = new Thread(() -> {
            synchronized (Counter.class){
                try {
                    //休眠不释放锁
                    Thread.sleep(1000);
                    System.out.println("持有锁A线程休眠一秒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (SynchronizedDemo.class){
                    System.out.println("申请锁B");
                }
            }

        });
        thread.start();
        System.out.println(thread.getName());
    }

    private static void startLockB(){
        Thread thread = new Thread(() -> {
            synchronized (SynchronizedDemo.class){
                try {
                    Thread.sleep(1000);
                    System.out.println("持有锁B线程休眠一秒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (Counter.class){
                    System.out.println("申请锁A");
                }
            }
        });
        thread.start();
        System.out.println(thread.getName());
    }

    static class Counter {
        private static int scount;
        private int count;

        //Synchronized关键字用于静态方法,静态方法Synchronized保护的时类对象。每一个对象都有锁和等待队列,类对象也不例外
        public synchronized  void incr(){
            count++;
        }

        public synchronized  int  getCount(){
            return count;
        }

        public static synchronized  void inScr(){
            scount++;
        }


        public static synchronized  int  getCountS(){
            return scount;
        }

        //Synchronized 关键字锁住的 的同步代码块,代码块的锁对象可以时任意对象。
        {
            synchronized (this){
                System.out.println("同步代码块");
            }
        }
    }

    static class CountThread extends Thread {

        Counter counter;

        public CountThread(Counter counter) {
            this.counter = counter;
        }

        @Override
        public void run() {
            for (int i = 0; i<1000;i++){
                   counter.incr();
              }
            }
        }

    public static void main(String[] args) throws InterruptedException {
        //Synchronized 实例方法实际保护的是同一个对象的方法调用,确保同时只能一个线程执行。
        //synchronized 实例方法保护的是当前实例对象,即this,this 对象有一个锁和一个等待队列
        //锁只能被一个线程持有,其他试图获得同样锁的线程需要等待。
        //执行synchronized实例方法大致如下:
        //(1)尝试获得锁,如果获得锁,继续下一步。否则加入等待队列,阻塞并等待唤醒。
        //(2)执行实例方法体代码。
        //(3)释放锁,如果等待队列上有等待的线程,从中取一个唤醒。如果有多个等待的线程,唤醒哪一个是不一定的,不保证公平性。
        //当前线程不能获得锁的时候,它会加入等待队列,线程的状态为BLOCKED。
        //Synchronized方法不能防止非Synchronized方法被同时执行。所以,一般在保护变量时,需要在所有访问变量的方法
        //上加Synchronized关键字。
      /*  int num = 1000;
        Counter counter = new Counter();
        Thread [] threads = new Thread[num];
        for (int i = 0; i < num;i++){
            threads[i] = new CountThread(counter);
            threads[i].start();
            threads[i].join();
        }
        System.out.println(counter.getCount());*/
      startLockA();
      startLockB();

    }
}

同步容器及注意事项:

同步容器是容器的所有方法都加synchronized关键字,让所有方法调用都变成原子操作。同时也保证变量的内存可见性。但是客户在调用这些方法的时候是否绝对安全呢?不是的,还得注意一下问题:
(1)复合操作,比如先检查在更新。
(2)伪同步
(3)迭代

Collection的这些方法可以返回线程安全的同步容器:

方法返回值说明
synchronizedCollection(Collection c)Collection c将collection集合转为同步集合
synchronizedListList将普通list集合转为同步list集合
synchronizedMap(Map<K,V>map)Map<K,V>将普通Map转为同步map
package concurrent;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * Collection中有一些方法,可以直接返回安全的同步容器,比如:
 * public static <T> Collection<T> synchronizedCollection(Collection<T> c)
 * public static <T> List<T> synchronizedList(List<T> list)
 * public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
 * 这些都是给容器所有方法加上synchronized来实现安全的。
 * 同步容器及其注意事项:
 * (1)复合操作,比如先检查在更新。
 * (2)伪同步。
 * (3)迭代。
 */
public class ConcurrentContainerDemo {

    //复合操作
    static class EnhancedMap<K,V> {
        Map<K,V> map;
        public EnhancedMap(Map<K,V> map){
            this.map = Collections.synchronizedMap(map);
        }

        //这个方法不是同步安全的方法。本来map的方法都是安全的。但是现在这种复合操作,在多线程的时候,
        //可能多个线程都执行完检查这一步,都发现没有键值,然后都会调用put,这样就破坏了这个方法保持的语义了
        public V putIfAbsent(K key,V value){
            V old = map.get(key);
            if(old != null){
                return old;
            }
            return map.put(key,value);
        }

        //伪同步:
        //这里加上synchronized 就能实现同步吗?肯定是不能的,因为锁对象不同。这个方法的锁对象是EnhancedMap,
        //而put的锁是Collections.synchronizedMap(map)返回的map对象
        public synchronized V putIfAbsent1(K key,V value){
            V old = map.get(key);
            if(old != null){
                return old;
            }
            return map.put(key,value);
        }

        //加入map对象锁,这个方法才是同步的,安全的
        public  V putIfAbsent12(K key,V value) {
            synchronized (map) {
                V old = map.get(key);
                if (old != null) {
                    return old;
                }
                return map.put(key, value);
            }
        }
    }
    //迭代,对于同步容器虽然单个操作是安全的,但迭代不是。
    private static void  startModifyThread(final  List<String> list){
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i =0; i< 100; i++) {
                    list.add("新增"+(i+1));
                    try{
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();
    }

    private static void  startIterationThread(final List<String> list){
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (list) {
                        for (String s : list) {
                            System.out.println(s);
                        }
                    }
                }
            }
        });
        thread.start();
    }

    public static void main(String[] args) {
        //这样迭代会抛出同步异常,因为在遍历同步容器时容器发生了结构变化,就会抛出异常,需要解决这个问题,需在遍历时
        //给整个容器对象加锁
        final List<String> list = Collections.synchronizedList(new ArrayList<>());
        startModifyThread(list);
        startIterationThread(list);

        //并发容器:CopyOnWriteArrayList。ConcurrentHashMap。ConcurrentLinkedQuee。ConcurrentSkipListSet
        //这些容器类都是线程安全的,但都没有使用synchronized,没有迭代问题,直接支持一些复合操作,性能也更高
    }
}

线程的基本协作机制:

多线程之间除了竞争访问同一资源外,也经常需要相互协作。在Java中线程之间的协作是通过wait和notify 来协作的。
线程的协作场景:

(1)生产者和消费者模式:常见的协作模式,生产者线程和消费者线程通过共享队列进行协作,生产者将数据或任务放到队列上,而消费者从队列上取数据或者任务,如果队列长度有限,在队列满的时候,生产者需要的等待,而队列为空时,消费者需要等待。

(2)同时开始:类似运动员比赛,听到信号枪时需要同时开始。在一些程序,尤其是仿真程序中,要求多个线程能同时开始。比如考试系统的发卷和收卷都是同时开始和同时结束。

(3)等待结束:主从协作模式也是常见的协作模式。主线程将任务分解为若干个任务,为每个子任务创建一个线程, 主线程在继续执行其他任务之前需要等待每个子任务执行完毕。

(4)集合点:比如并行迭代计算中,每个线程复杂一部分计算,然后在集合点等待其他线程完成,所有的线程到齐后交换数据和计算结果,在进行下一次迭代。

线程协作的方法:

都是Object的方法:

方法返回值说明
wait()void当前线程无限期的等待,除非被唤醒
wait(long timeout)void当前线程等待指定的时间
notify ()void唤醒当前锁对象条件队列里的任意一个线程,然后将其在条件队列移除
notifyAll ()void唤醒当前锁对象条件队列里的所有等待线程,然后清空条件队列

wait 和notify的作用:
每个对象都有一个锁和等待队列,一个线程进入synchronized代码块后,尝试获得锁,获取不到就进入等待队列。其实除了等待队列,还有另外一个等待队列,表示条件队列,该队列用于线程间的协作。当调用wait就会把当前线程放到条件队列并阻塞,表示当前线程执行不下去了,需要等待一个条件,这个条件需要其他线程改变。同时调用wait时会释放锁对象。当别的线程执行 完任务,改变了条件。就调用notify 唤醒条件线程中的任意一个线程并在条件线程中删除,或者调用notifyAll唤醒条件队列的所有线程 并移除所有在条件队列的所有线程。notify 不会释放锁对象,因为锁对象是要当前持有锁对象的线程持有调用的数量为0时才会释放锁,简单来说就是当前线程synchronized保护的代码全部执行完才会释放锁。

注意事项:
wait()、wait(long timeout)和notify()、notifyAll();这四个方法必须在synchronized的代码块才能使用。因为线程的协作都是操作共享内存里的变量,而变量在多线程的操作时需要被synchronized保护的。

public class ConcurrentCooeration {

    /**
     * 生产者和消费者模式:
     */
    static class ProconModels<E> {
        private Queue<E> queue;
        private int limit;

        public ProconModels(int limit) {
            this.limit = limit;
            this.queue = new ArrayDeque<>(limit);
        }

        //添加方法
        private synchronized  void put(E e)throws InterruptedException {
            while (queue.size() == limit){
                wait();
            }
            queue.add(e);
            notifyAll();
        }

        //获取方法
        private synchronized E  take()throws InterruptedException {
            while (queue.isEmpty()){
                wait();
            }
            E e = queue.poll();
            notifyAll();
            return e;
        }
    }

    //生产者
    static class Producer extends Thread{
        private ProconModels<String> queue;

        public Producer(ProconModels<String> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            int num =0;
            try{
                while(true){
                    String task = String.valueOf(num);
                    queue.put(task);
                    System.out.println("produce task:" + task);
                    num++;
                    Thread.sleep((int)(Math.random() *100));
                }
            }catch (InterruptedException i){
                i.printStackTrace();
            }
        }
    }

    //生产者
    static class Consumer extends Thread {
        private ProconModels<String> queue;

        public Consumer(ProconModels<String> queue) {
            this.queue = queue;
        }


        @Override
        public void run() {
            while(true){
                try {
                    String task = queue.take();
                    System.out.println("consumer handle task:" + task);
                    Thread.sleep((int)(Math.random() *100));
                }catch (InterruptedException i){
                    i.printStackTrace();
                }

            }
        }
    }

    //同时开始:考试例子
    static class FireFlag {
        private  volatile boolean startFlag = false;
        private volatile boolean endFlag = false;

        //上面加了轻量级的volatile关键字,java会插入特殊指令将最新的变量数据读写到内存
        //下面的方法为啥还要加synchronized呢?因为wait和notifyAll要在synchronized保护的代码块下使用,
        //也是为了保证共享变量在内存的可见性
        private  synchronized void waitTestStart() {
            while(!startFlag){
                try {
                    wait();
                }catch (InterruptedException i){
                    i.printStackTrace();
                }
            }
            System.out.println("学生统一开始考试!!");
        }

        private synchronized void waitTestend(){
            try {
            while(!endFlag){
                wait();
            }
            }catch (InterruptedException i){
                i.printStackTrace();
            }
        }

        private synchronized void startFire(){
            this.startFlag = true;
            notifyAll();
        }

        private synchronized void endFire(){
            this.endFlag = true;
            notifyAll();
            System.out.println("教师统一结束收卷");
        }
    }

    static class Student extends Thread {
        FireFlag fireFlag;

        public Student(FireFlag fireFlag) {
            this.fireFlag = fireFlag;
        }

        @Override
        public void run() {
            this.fireFlag.waitTestStart();
        }
    }

    static class Teacher extends Thread {
        FireFlag fireFlag;

        public Teacher(FireFlag fireFlag) {
            this.fireFlag = fireFlag;
        }

        @Override
        public void run() {
            this.fireFlag.waitTestend();
            try {
                Thread.sleep(5000);
            }catch (InterruptedException i){
                i.printStackTrace();
            }
        }
    }

    /**
     * 同时结束
     */
    static class MyLatch {
        private int count;

        public MyLatch(int count) {
            this.count = count;
        }

        public synchronized  void await() throws InterruptedException {
            while (count > 0){
                wait();
            }
        }

        public synchronized void countDown(){
            count--;
            while (count <= 0){
                notifyAll();
                return;
            }
        }

    }

    static class Worker extends Thread {
        MyLatch myLatch;

        public Worker(MyLatch myLatch) {
            this.myLatch = myLatch;
        }


        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                this.myLatch.countDown();
            }catch (InterruptedException i){
                i.printStackTrace();
            }
        }
    }

    //集合点
    static class AssemblePoint {
        private int num;

        public AssemblePoint(int num) {
            this.num = num;
        }

        public synchronized  void await() throws InterruptedException {
            if(num > 0){
                num--;
                if(num < 0){
                    notifyAll();
                }else{
                    while(num != 0){
                        wait();
                    }
                }
            }
        }
    }

    static class Tourist extends Thread {
        AssemblePoint assemblePoint;

        public Tourist(AssemblePoint assemblePoint) {
            this.assemblePoint = assemblePoint;
        }


        @Override
        public void run() {
            System.out.println("游客前往集合点");
            try{
            Thread.sleep(1000);
            //集合

            assemblePoint.await();
                System.out.println("Arrived");
            }catch (InterruptedException i){
                i.printStackTrace();
            }

        }
    }

    public static void main(String[] args) throws InterruptedException {
        //生产者和消费者模式:
      ProconModels<String> queue = new ProconModels<>(10);
      new Producer(queue).start();
      new Consumer(queue).start();

      //同时开始:
      FireFlag fireFlag = new FireFlag();
      Thread[] threads = new Thread[10];
      for(int i = 0; i < 10; i++){
          threads[i] = new Student(fireFlag);
          threads[i].start();
      }
      Thread.sleep(1000);
      fireFlag.startFire();
      Teacher teacher = new Teacher(fireFlag);
      teacher.start();
      fireFlag.endFire();

      //同时结束:
       int num = 10;
       MyLatch myLatch = new MyLatch(num);
       Worker[] workers = new Worker[num];
       for (int i = 0; i < num; i++){
           workers[i] = new Worker(myLatch);
           workers[i].start();
       }
       myLatch.await();
       System.out.println("同时结束!");

       //集合点:
        AssemblePoint assemblePoint = new AssemblePoint(num);
        Tourist[] tourists = new Tourist[num];
        for (int i = 0; i < num; i++){
            tourists[i] = new Tourist(assemblePoint);
            tourists[i].start();
        }
        System.out.println("集合完毕");
    }
}

线程的中断:

在现实生活中有时候同时进行的事情有时候是需要中断的,比如钓鱼比赛,当前钓起第一条鱼时,其它还在钓鱼的就也会被中断,表示比赛结束。当然在并发线程下,也有这些需求。

取消和关闭线程的场景:
(1)很多线程的运行模式是死循环,比如生产者和消费者模式。当我们要停止程序的时候,我们需要一种’优雅’的方法以关闭线程。

(2)从远程服务器下载文件,在在下载的时候会想取消该任务。

(3)从第三方服务器查询一个结果,我们希望在限定的时间内获得结果,如果得不到,我们会取消该任务。

4)抢火车票,会让多个朋友帮忙从多个渠道抢票,只要有一个渠道抢到了,我们会通知其他取消抢票。

中断方法:

方法返回值说明
isInterrupted()boolean返回对应线程的中断标志位是否位true
interrupt()void中断当前线程,设置中断标志位
interrupted()boolean返回当前线程的中断标志位是否位true。但它有一个重要的副作用,就是会清空中断标志位,连续两次调用,第一次的 结果是true,那么第二次就会false

线程对中断的反应:
interrupt()对线程的影响与线程的状态和进行的IO操作有关。我们主要考虑线程的状态,IO操作 的影响和具体的Io以及操作系统有关。
线程的状态:

  • (1)RUNNABLE:线程在运行或具备运行条件,只是在等待操作系统调度。
  • (2)WAITING /TIMED_WAITING: 线程在等待某个条件或超时。
  • (3)BLOCKED:线程在等待锁,试图进入同步块。
  • (4)NEW/TERMINATED:线程还未启动或已结束。
public class ThreadInterruptDemo {
    //(1)RUNNABLE:如果线程在运行,且没有执行IO操作,interrupt()只是会设置线程的中断标志位,
    //没有任何其他作用。线程应该在运行过程中合适的位置检查中断标志位。比如主体代码是循环体,
    //可以在循环开始的进行检查,如下:
    static class InterruptRunnableDemo extends Thread {

        @Override
        public void run() {
            for (int i = 0; i<10; i++){
                while(!this.isInterrupted()) {
                    System.out.println("中断RUNNABLE状态下的线程");
                }
            }
        }

        public void cancle(){
            try {
                sleep(1000);
                this.interrupt();
            }catch (InterruptedException i){
                i.printStackTrace();
            }
        }
    }

    //(2)WAITING/TIMED_WAITING:线程调用join、wait、sleep方法会进入WAITING/TIMED_WAITING,
    //在这个状态时,对线程调用interrupt()会使得该线程抛出InterruptedException。需要注意的
    //是,抛出异常后,中断标志会被清空,而不是被设置。比如:
    static class InterruptWaiting extends Thread {

        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            }catch (InterruptedException i){
                //打印出来的是false,InterruptionException 是一个收检查的异常,线程必须处理
                // 。异常处理方式有两种:
                //(1)不知道如何处理,直接向上传递该异常,这使得该方法成为一个可中断的方法了,但调用者需要进行处理。
                //(2)有些时候,不能向上抛出异常。比如Thread的run方法。这时应该捕抓异常进行处理。处理后一般调用
                //interrupt()设置中断标志位。使得其他代码有办法知道它发生了中断。
                System.out.println(isInterrupted());
                //清除中断的操作
                System.out.println("清理操作");
                //重设中断标志位
                Thread.currentThread().interrupt();
            }
            System.out.println(isInterrupted());
        }
    }

    //(3)BLOCKED:如果线程在等待锁,对线程对象调用interrupt()只是会设置线程的中断标志位,线程
    //依然会处于BLOCKED状态,也就是说,interrupt()并不能使一个在等待锁的线程真正'中断'。如下:
    static class BlockedInterrupt extends Thread {
        private static Object lock = new Object();

        @Override
        public void run() {
            synchronized (lock){
                System.out.println(Thread.currentThread().getName());
                while(!Thread.currentThread().isInterrupted()){
                    System.out.println("执行业务方法");
                }
                System.out.println("exit");
            }
        }

        public static void test() throws InterruptedException {
            synchronized (lock){
                BlockedInterrupt blockedInterrupt = new BlockedInterrupt();
                blockedInterrupt.start();
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName());
                blockedInterrupt.interrupt();
                blockedInterrupt.join();
            }
        }
    }

    //NEW、TERMINATE:如果线程尚未启动(NEW),或者已经结束(TERMINATE),则调用interrupt()对它没有任何效果
    //中断标志位也不会被设置。
    public static void main(String[] args) throws InterruptedException {
        //Runnable状态下的中断:
        InterruptRunnableDemo interruptRunnableDemo = new   InterruptRunnableDemo();
        interruptRunnableDemo.start();
        interruptRunnableDemo.cancle();

        //waiting、timed_waiting状态下的中断:
        InterruptWaiting interruptWaiting = new InterruptWaiting();
        interruptWaiting.start();
        interruptWaiting.interrupt();

       //BLOCKED 状态下的中断:
        BlockedInterrupt.test();
        System.out.println("jiesu");
    }

}

总结:

以上都是线程的基本用法和原理,常用的方法是sleep(),join(),wait(),notify。前面两个是Thread 类的方法,后面两个是Object方法。并发需要注意的是竞争条件和内存可见性的问题,当多个线程对同一个变量操作时,执行的顺序会导致结果不正确。而且也会导致上一个线程修改了变量而别的线程却看不到修改的值,还是取的之前的值。这是因为电脑不仅是会把数据存到内存,还会放到cpu的寄存器上,也有可能是各级缓存中。同时取数据时不一定时从内存中取,这就导致了读写没有同步,造成内存可见性的问题。这些都可以通过使用synchroized关键字进行对变量加锁,使操作原子化,强制读写都是去内存中操作。虽然synchronized 可以解决这些问题,但是synchronized成本很高,同时也会造成死锁的问题。所以使用时需要注意,当然java提供了许多高性能的并发容器,没有了同步容器的复合操作、伪同步,迭代的问题,同时也极大的避免了死锁的 问题。这些并发容器由下一章节讲解,希望本文能给大家带来帮助。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值