多线程之小白系列

文章参考B站up狂神说JAVA、《Java并发编程》

1. 线程实现的三种方式

1. 继承Thread

  • 自定义类继承Thread
  • 重写run()方法
  • 调用start()开启线程
public class ThreadDemo extends Thread{
 @Override
 public void run() {
     for (int i = 0; i < 200; i++) {
         System.out.println("我在学习" + i);
     }
 }

 public static void main(String[] args) {
     ThreadDemo demo = new ThreadDemo();
     demo.start();

     for (int i = 0; i < 200; i++) {
         System.out.println("学习多线程" + i);
     }
 }
}

2. 实现Runable接口

  • 创建Runable接口的实现类
  • 重写run方法
  • 创建线程对象,丢入实现Runable接口的实现类
  • 调用start()
public class ThreadDemo2 implements Runnable {
 @Override
 public void run() {
     for (int i = 0; i < 200; i++) {
         System.out.println("我在学习" + i);
     }
 }

 public static void main(String[] args) {
     ThreadDemo demo = new ThreadDemo();
     new Thread(demo).start();
     for (int i = 0; i < 200; i++) {
         System.out.println("学习多线程" + i);
     }
 }
}

推荐使用Runable接口来实现多线程,方便一个对象可以被多个线程使用

实例: 买火车票

多个线程同时操作同一个对象

// 多线程实现卖票
// 此时会出现多个线程抢到同一种票
public class TiketDemo implements Runnable{
 private Integer tickets = 10;

 @Override
 public void run() {
     while (true) {
         if (tickets <= 0) {
             break;
         }
         System.out.println(Thread.currentThread().getName() + "抢到第" + tickets-- + "张");
     }
 }

 public static void main(String[] args) {
     TiketDemo ticket = new TiketDemo();
     new Thread(ticket,"小明").start();
     new Thread(ticket,"老师").start();
     new Thread(ticket,"黄牛").start();

 }
}

模拟龟兔赛跑

// 模拟龟兔比赛
public class RaceDemo implements Runnable{
 private String winner;
 @Override
 public void run() {
     for (int i = 0; i <= 100; i++) {


         boolean flag = gameOver(i);
         if (flag) {
             break;
         }
         System.out.println(Thread.currentThread().getName() + i + "步");
     }
}

// 判断游戏是否结束
private boolean gameOver(int steps) {
    if (winner != null) {
        return true;
    }

    if (steps >= 100) {
        winner = Thread.currentThread().getName();
        System.out.println(winner + "赢了");
        return true;
    }
    return false;
}

public static void main(String[] args) {
    RaceDemo raceDemo = new RaceDemo();
    new Thread(raceDemo, "兔子").start();
    new Thread(raceDemo, "乌龟").start();
}
}

3. Callable

  • 实现Callable接口,需要确定返回值类型
  • 重写call方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务: ExecutorService service = Executors.newFixedThreadPool(1);
  • 提交执行: Future result1 = service.submit(demo);
  • 获取结果: Boolean r1 = result1.get();
  • 关闭服务: service.shutdownNow();
public class CallableDemo implements Callable<Boolean> {
    @Override
    public Boolean call() throws Exception {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "线程" + i);
        }
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableDemo demo = new CallableDemo();
        //创建执行服务
        ExecutorService pool = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> result1 = pool.submit(demo);
        Future<Boolean> result2 = pool.submit(demo);
        Future<Boolean> result3 = pool.submit(demo);

        //获取结果
        Boolean r1 = result1.get();
        Boolean r2 = result2.get();
        Boolean r3 = result3.get();
        System.out.println(r1 +""+ r2 + r3);
        //关闭服务
        pool.shutdownNow();
    }
}

静态代理

  • 真实对象和代理对象都要实现同一个接口
  • 代理对象要代理真实对象
public class StaticProxyDemo {

    public static void main(String[] args) {
        //自己结婚
        You you = new You();
        you.HappyMarry();

        //通过代理对象完成结婚

        //他们实现方式类似
        new Thread(() -> System.out.println("Runable接口实现类")).start();

        new WeddingCompany(new You()).HappyMarry();
    }
}

interface Marry {
    void HappyMarry();
}

//真实角色,你去结婚
class You implements Marry {
    @Override
    public void HappyMarry() {
        System.out.println("要结婚了,好开心啊");
    }
}

//代理角色, 帮助你完成结婚这件事
class WeddingCompany implements Marry {
    //结婚的真实对象
    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();
        after();
    }

    //结婚后要干的事情
    private void after() {
        System.out.println("结婚之后,收尾款");
    }

    //结婚前要准备的事情
    private void before() {
        System.out.println("结婚之前,布置现场");
    }
}

代理对象可以做很多真实对象做不了的事情

真实对象专注做自己的事情

2. 线程的操作

线程的状态

image-20200504152509595

image-20200504152556491

image-20201012105621025

线程方法

方法说明
t.setPriority(int newPriority)更改线程的优先级
Thread.sleep(long m)让当前线程让出CPU,阻塞m时长,然后回到就绪态
t.join(100L)当前线程等待t线程执行完毕(其他线程阻塞),参数为超时时间
Thread.yield()让当前线程让出CPU,并转为就绪状态,重新参与CPU使用权的竞争,只有优先级大于或等于当前线程的线程才可能获得CPU使用权
t.interrupt()尝试通知线程t中断,需要在线程的任务代码中用
t.isAlive()测试线程是否处于存活状态

wait 和sleep区别:都可以通过interrupt方法中断

  • wait() 方法必须在synchronized同步块或方法中使用
  • wait()方法会释放由synchronized锁上的对象锁,而sleep不会
  • 由wait()方法形成的阻塞,可以通过针对同一对象锁的synchronized作用域调用notify()/notifyAll()来唤醒;而sleep()则无法被唤醒,其只能定时醒来或被interrupt()方法中断。

sleep和yield区别

  • 线程执行sleep方法后转入阻塞状态,并在睡眠一段时间后自动醒来,回到就绪态。而执行yield会让线程直接进入就绪态
  • 当前线程执行sleep方法后,其他线程无论优先级高低,都有机会得以运行。而执行yield方法后只会给那些具有相同优先级或更高的优先级的线程运行的机会
  • sleep方法会抛出InterruptedException,而yield方法没有声明任何异常
  • sleep方法比yield具有更好的移植性,在循环中使用yield,在Linux中容易导致死循环

停止线程

  • 建议线程正常停止—>利用次数,不建议死循环
  • 建议使用标志位-----> 设置一个标志位
  • 不要使用stop或则destroy等过时的方法
  • 捕获中断信号关闭线程
  • 使用volatile开关

捕获中断信号

public class InterruptThreadExit {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread() {
            @Override
            public void run() {
                System.out.println("i will start work");
                while (!isInterrupted()) {
                    // working..
                }
                System.out.println("I will be exiting");
            }
        };
        t.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("system will be shutdown");
        t.interrupt();
    }
}

使用volatile开关控制

由于线程的interrupt标识很可能被擦除,或则逻辑单元中不会调用任何可中断的方法,使用volatile修饰的开关关闭线程是一种常用的方法

public class FlagThreadExit {
    static class MyTask extends Thread {
        private volatile boolean closed = false;

        @Override
        public void run() {
            System.out.println("I will start work");
            while (!closed && !isInterrupted()) {
                // working
            }
            System.out.println( "I will be exiting...");
        }
        public void close() {
            this.closed = true;
            this.interrupt();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyTask task = new MyTask();
        task.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("System will be shutdown");
        task.close();
    }
}

线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数

  • sleep存在异常InterruptedException

  • sleep时间达到后线程进入就绪状态

  • sleep可以模拟网络延时,倒计时等

  • 每一个对象都有一个锁,sleep不会释放锁

sleep可以放大问题的发生性,使线程不安全暴露出来

模拟网络延时


public class TestSleep implements Runnable{
    private Integer tickets = 10;

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (tickets <= 0) {
                break;
            }
            System.out.println("tickets " + tickets--);
        }
    }

    public static void main(String[] args) {
        TestSleep ticket = new TestSleep();
        new Thread(ticket).start();
    }
}

模拟倒计时


public class TestSleep implements Runnable{
    Date date = new Date(System.currentTimeMillis());

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            date = new Date(System.currentTimeMillis());
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
        }
    }

    public static void main(String[] args) {
        TestSleep ticket = new TestSleep();
        new Thread(ticket).start();
    }
}

线程礼让(yield)

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让cpu重新调度,礼让不一定成功,要看CPU的心情
  • 调用yield后,cpu将选择优先级大于等于当前线程的线程来进行调度

image-20200504161854069

//感觉这个案例不太好
public class TestYield {
    public static void main(String[] args) {
        Yield yield = new Yield();
        new Thread(yield, "A").start();
        new Thread(yield, "B").start();

    }
}

class Yield implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "进入了");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "退出了");
    }
}

Join

  • Join合并线程,待此线程执行完成后,在执行其他线程,让其他线程阻塞
  • 可以想象成插队(让其他线程阻塞)
  • join方法与sleep一样,同样是一个可以中断的方法

public class JoinTest implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 500; i++) {
            System.out.println("我要插队" + i);
        }
    }

    public static void main(String[] args) {
        JoinTest joinTest = new JoinTest();
        Thread thread = new Thread(joinTest);
        thread.start();

        for (int i = 0; i < 100; i++) {
            if (i == 50) {
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("main thread...." + i);
        }
    }
}

如果将下面代码3中注释掉,将会看到三个线程交替输出

public class ThreadJoin {
    public static void main(String[] args) throws InterruptedException {
        // 1、定义两个线程
        List<Thread> threads = IntStream.range(1, 3)
                .mapToObj(ThreadJoin::create).collect(toList());
        // 2、启动这两个线程
        threads.forEach(Thread::start);
        // 3、执行这两个线程的join方法
        for (Thread thread: threads) {
            thread.join();
        }

        // 4、main线程循环输出
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "#" + i);
            shortSleep();
        }

    }

    // 构造一个简单的线程,每个线程只是简单的循环输出
    private static Thread create(int seq) {
        return new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "#" + i);
            }
        }, String.valueOf(seq));
    }

    private static void shortSleep() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程状态观测

Thread.State

  • NEW: 尚未启动的线程状态
  • RUNNABLE: 在java虚拟机中执行的线程处于此状态
  • BLOCKED: 被阻塞等待监视器锁定的线程处于此状态
  • WAITTING: 正在等待另一个线程执行特定动作的线程处于此状态
  • TIME_WAITTING:正在等待另一个线程执行特定动作达到指定时间的线程处于此状态
  • TERMINATED: 已退出的线程处于此状态

public class StateTest implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("thread ...");
    }

    public static void main(String[] args) {
        StateTest stateTest = new StateTest();

        Thread thread = new Thread(stateTest);
        Thread.State state = thread.getState();
        //NEW
        System.out.println(state);

        //RUNNABLE
        thread.start();
        state = thread.getState();
        System.out.println(state);

        //TIMED_WAITING
        while (Thread.State.TERMINATED != state) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(state);
            state = thread.getState();
        }

        // TERMINATED
        System.out.println(state);

    }
}

线程的优先级

通过设置线程的优先级,可以改变CPU的调用顺序,并不是一定按照优先级来执行,优先级仅仅设置的是线程的权重,具体调用还是由CPU来决定

public class PriorityTest {

    public static void main(String[] args) {
        //打印主线程
        System.out.println(Thread.currentThread().getName() +"======>" + Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);

        t1.setPriority(4);
        t1.start();

        t2.setPriority(Thread.MIN_PRIORITY);    // 1
        t2.start();

        t3.setPriority(Thread.MAX_PRIORITY);    // 10
        t3.start();

        t4.setPriority(7);
        t4.start();
    }
}

class MyPriority implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() +"======>" + Thread.currentThread().getPriority());
    }
}

线程的优先级不能高于线程组的优先级

public class ThreadPriority {
    public static void main(String[] args) {
        // 定义一个线程组
        ThreadGroup group = new ThreadGroup("test");
        // 设置线程组的优先级
        group.setMaxPriority(7);

        Thread t2 = new Thread(group, "test-thread");
        t2.setPriority(10);
        // 线程的优先级不能高于线程组的优先级
        System.out.println(t2.getPriority());
        t2.start();
    }
}

守护线程(daemon)

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如:后台记录操作日志,监控内存,垃圾回收
//当用户线程结束后,守护线程就会结束,由于程序结束需要一段时间,因此守护线程会多执行一会儿
public class DaemonTest {

    public static void main(String[] args) {
        You you = new You();
        God god = new God();

        Thread thread = new Thread(god);
        thread.setDaemon(true); //开启守护线程,默认false,表示用户线程
        //启动守护线程
        thread.start();
        
        //启动用户线程
        new Thread(you).start();
    }
}

class You implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 365; i++) {
            System.out.println("活了" + i);
        }
        System.out.println("good bye world");   //线程结束
    }
}

class God implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("上帝守护者你...");
        }
    }
}

线程中断

interrupt()方法并不会“中断”某个线程,他只是向线程发送一个中断信号,修改其中断标识位,以判断线程是否应该被“中断”(中断标志位是否为true)

Object对象的wait()方法,Thread的sleep()和join()方法,都会判断中断标识位以响应中断请求

中断相关API:

  • public void interrupt() : 中断通知
  • public static boolean interrupted() : 中断后立即擦除标志
  • public boolean isInterrupted() : 判断是否被中断

interrupt

调用如下方法会是得当前线程进入阻塞状态,而调用当前线程的interrupt方法,就可以打断阻塞

  • Object的wait方法

  • Thread的sleep方法

  • Thread的join方法

  • 其他方法

中断并不等于结束该线程的生命周期,仅仅是打断了当前线程的阻塞状态

阻塞线程被打断后,都会抛出一个InterruptedException的异常,相当与一个signal一样通知当前线程被打断了

例:

public class ThreadInterrupt {
    public static void main(String[] args) {
        Thread th = new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.MINUTES.sleep(1);
                } catch (InterruptedException e) {
                    System.out.println("interrupte....");
                    e.printStackTrace();
                }
            }
        });
        th.start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        th.interrupt();
      
    }
}

isInterrupted

主要用于判断当前线程是否被中断,仅仅是对interrupt标识的一个判断,并不会影响标识发生任何改变。

public class ThreadisInterrupted {
    public static void main(String[] args) {
        Thread th = new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        TimeUnit.MINUTES.sleep(1);
                    } catch (InterruptedException e) {
                        // e.printStackTrace();
                    5    System.out.println("interrupted ?" + isInterrupted()); // false
                    }
                }
            }
        };
        th.setDaemon(true);
        th.start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(th.isInterrupted()); // false
        th.interrupt();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(th.isInterrupted()); // false
    }
}

由于sleep是可中断的,会捕获到中断信号,并且会擦除interrupt标识,从而结果都是false

interrupted

调用该方法会直接擦除掉线程的interrupt标识,需要注意的是,如果当前线程被打断了,那么第一次调用interrupted会返回true, 并且立即擦除了interrupt标识;第二次包括以后的调用都会返回false,除非在次打断

public class ThreadInterrupted {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println(Thread.interrupted());
                }
            }
0        };
        thread.setDaemon(true);
        thread.start();

        TimeUnit.MILLISECONDS.sleep(2);
        thread.interrupt();
    }
}

3.线程池

创建线程池:

// 创建固定大小的线程池
//ExecutorService pool = Executors.newFixedThreadPool(20);

// 创建可变大小的线程池
// ExecutorService pool = Executors.newCachedThreadPool();

// 创建一个只有一个线程的线程池
// ExecutorService executorService = Executors.newSingleThreadExecutor();
// ExecutorService pool = Executors.newFixedThreadPool(1);

// 创建一个支持定时或周期性任务执行的线程池
// ScheduledExecutorService es = Executors.newScheduledThreadPool(2);

ExcutorService

通过Excutors创建的线程池都实现了ExcutorService接口,调用他的execute或submit方法即可向线程池中提交任务,下面是一些常用的方法

  • shutdown:异步关闭线程池,调用后将不允许向线程池中继续添加新任务,但允许继续执行已提交的任务,调用后立即返回,不会阻塞当前线程
  • shutdownNow:立即异步关闭线程池,会立即取消线程池中的任务,并且会调用线程池中所有线程的interrupt方法来尝试中断正在执行的任务
  • awaitTermination(timeout, unit):调用此方法将阻塞当前线程,以等待线程池中的任务执行完毕,参数为最大等待时长,此方法必须在调用shutdown,或shutdownNow方法之后执行,否则将无效
  • isTerminated(): 用于判断在调用shutdown后判断线程池中的线程任务是否都已完成
  • execute:向线程池中提交一个任务,且不需要返回值
  • submit:向线程池中提交一个任务,且需要返回值
  • isShutdown(): 返回线程池是否已关闭。
  • invokeAll(task,timeout, unit):启动多个线程,并发地执行多个任务,这些任务要么执行成功,要么因为异常或超时而被取消。
  • invokeAny:启动多个线程,相互独立(无同步)地计算一个结果,一旦其中一个线程成功返回,则终止其他线程。

基本使用:

public class ThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();
        long l = System.currentTimeMillis();

        for (int i = 0; i < 5; i++) {
            es.execute(new Runnable() {
                @Override
                public void run() {
                    int j = 0;
                    while (!Thread.interrupted() && j < Integer.MAX_VALUE) {
                        j++;
                        System.out.println(Thread.currentThread().getName() + j);

                    }
                }
            });
        }

        es.shutdown();
        // es.shutdownNow();
        es.awaitTermination(5l, TimeUnit.SECONDS);
        System.out.println(System.currentTimeMillis() - 1);
    }
}

4. 线程安全与效率

并发: 同一个对象被多个线程同时操作

同步:进行排队

队列和锁: 将对象上锁, 保证线程的安全性,(相当于厕所上锁)

线程同步(synchronized)

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized ,当一个线程获得对象的排它锁,独占资源, 其他线程必须等待,使用后释放锁即可,存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

线程不安全示例:

  1. 车站买票

    //多个线程操作了同一个资源
    public class UnsafeBuyTickets {
        public static void main(String[] args) {
            BuyTickets station = new BuyTickets();
            new Thread(station, "A").start();
            new Thread(station, "B").start();
            new Thread(station, "C").start();
        }
    }
    
    class BuyTickets implements Runnable {
        private int ticketsNum = 10;
        private boolean flag = true;
        @Override
        public void run() {
    
            //进行买票
            while (flag) {
                //使用sleep方法验证问题的发生性
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                buy();
            }
        }
    
        //买票
        private void buy() {
            if (ticketsNum <= 0) {
                flag = false;
                return;
            }
            System.out.println(Thread.currentThread().getName() + "购买第" + ticketsNum--);
        }
    }
    
  2. 银行取钱

    //两个人去银行取钱
    public class UnsafeBank {
        public static void main(String[] args) {
            Account account = new Account(100, "中国银行");
            new Drawing(account, 50, "me").start();
            new Drawing(account, 100, "girlFirend").start();
        }
    }
    
    //账户
    class Account {
        int money; //余额
        String name;
        public Account(int money, String name) {
            this.money = money;
            this.name = name;
        }
    }
    
    //银行模拟取款
    class Drawing extends Thread {
        Account account; //账户
        int drawingMoney; //取了多少钱
        int nowMoney;   //现在手里有多少钱
    
        public Drawing(Account account, int drawingMoney, String name) {
            super(name);
            this.account = account;
            this.drawingMoney = drawingMoney;
        }
    
        //取钱
        @Override
        public void run() {
            //增加延时,扩大问题的可见性
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //判断是否有钱
            if (account.money - drawingMoney < 0) {
                System.out.println(this.getName() + "余额不够啦");
                return;
            }
            account.money = account.money - drawingMoney;
            nowMoney = nowMoney + drawingMoney;
            System.out.println(account.name + "余额" + account.money);
            System.out.println(this.getName() + "手里的钱" + nowMoney);
    
        }
    }
    
  3. 集合不安全

    //此时将发生ConcurrentModificationException
    public class UnsafeList {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 100; i++) {
                new Thread(() -> {
                    list.add(Thread.currentThread().getName());
                }).start();
                list.forEach(System.out::println);
            }
    
            System.out.println(list.size());
        }
    }
    
    

同步方法

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所有我们要针对方法提出一套机制,这套机制就是synchronized关键字,它有两种用法:synchronized方法和synchronized块

    同步方法: public synchronized void method(){}

  • synchronized方法控制对对象的访问权,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这把锁,继续执行

    缺陷: 若将一个大的方法声明为synchronized将会影响效率

同步块

  • 同步块: synchronized(Obj)

  • Obj 称为同步监视器

    • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this ,就是这个对象本身,或者是class
  • 同步监视器的执行过程

    1. 第一个线程访问,锁定同步监视器,执行其中代码.
    2. 第二个线程访问,发现同步监视器被锁定,无法访问.
    3. 第一个线程访问完毕,解锁同步监视器.
    4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

简而言之:上锁就是把增删改的代码加synchronized关键字

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形某一 个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题.

产生死锁的四个必要条件:

  1. 互斥条件: 一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生

public class DeadLock {
    public static void main(String[] args) {
        Makeup girl = new Makeup(0, "灰姑凉");
        Makeup girl2 = new Makeup(1, "白雪公主");
        girl.start();
        girl2.start();
    }
}

//口红对象
class Lipstick {

}

//镜子
class Mirror {

}

class Makeup extends Thread {
    //static 确保只初始化一次
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    private int choice;
    private String girlName;

    Makeup(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        makeup();
    }

    //化妆方法
    private void makeup() {
        if (choice == 0) {
            synchronized (mirror) {
                System.out.println(Thread.currentThread().getName() + "拿到镜子了");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                /*//拿口红
                synchronized (lipstick) {
                    System.out.println(Thread.currentThread().getName() + "拿到口红了");
                }*/
            }
            //拿口红
            synchronized (lipstick) {
                System.out.println(Thread.currentThread().getName() + "拿到口红了");
            }
        } else {
            synchronized (lipstick) {
                System.out.println(Thread.currentThread().getName() + "拿到lipstick了");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                /*synchronized (mirror) {
                    System.out.println(Thread.currentThread().getName() + "拿到mirror了");
                }*/
            }
            synchronized (mirror) {
                    System.out.println(Thread.currentThread().getName() + "拿到mirror了");
                }
        }
    }
}

Lock(锁)

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过 显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
public class LockTest {
    public static void main(String[] args) {
        MyLock lock = new MyLock();
        new Thread(lock, "A").start();
        new Thread(lock, "B").start();
        new Thread(lock, "C").start();
    }
}
class MyLock implements Runnable {
    private int ticketsNum = 50;
    private final Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();    //加锁
                if (ticketsNum > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ticketsNum--);
                } else {
                    break;
                }
            } finally {
                lock.unlock();  //解锁
            }

        }
    }
}

synchronized与Lock对比

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synichronized是隐式锁, 出了作用域自动释放

  • Lock只有代码块锁,synchronized有代码块锁和方法锁
    使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

  • 优先使用顺序:

  • Lock >同步代码块(已经进入了方法体,分配了相应资源) >同步方法(在方法体之外)


5. 线程协作

生产者消费者模式

java提供了几个方法解决线程之间的通信问题

方法名作用
wait()表示线程一 直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout)指定等待的时间
notify()唤醒一个处于等待状态的线程
notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级最高的线程优先调度

注意:所有方法都是Object中的方法,只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException

解决方式:

管程法:

生产者将物品放入缓存区,消费者从缓冲区去去物品

public class PCTest {
    public static void main(String[] args) {
        CacheContainer cacheContainer = new CacheContainer();
        new Producer(cacheContainer).start();
        new Consumer(cacheContainer).start();
    }
}

//生产者
class Producer extends Thread {

    CacheContainer container;

    public Producer(CacheContainer container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Item(i));
            System.out.println("生产了第" + i+ "个商品");
        }
    }
}

//消费者
class Consumer extends Thread{
    CacheContainer container;

    public Consumer(CacheContainer container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            Item item = container.pop();
            System.out.println("消费第" + item.getId() + "个商品");
        }
    }
}

//物品
class Item {
    private int id;
    Item(int id) {
        this.id = id;
    }
    public int getId() {
        return this.id;
    }
}
//缓冲容器
class CacheContainer {
    private Item[] items = new Item[10];
    int count = 0;

    //生产者进行生产
    public synchronized void push(Item item) {
        //如果容器满了,就需要等待消费者消费
        if (count == items.length - 1) {
            //通知消费者消费,生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //容器没有满,需要放入商品
        items[count++] = item;
        //通知消费者消费
        this.notify();
    }

    //消费者消费商品
    public synchronized Item pop() {
        //如果容器中没有商品,就等待生产者生产
        if (count == 0) {
            //通知生产者生产,消费者等待
            try {
                this.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //进行消费
        Item item = items[count - 1];
        count--;
        //通知生产者生产
        this.notify();
        return item;
    }
}

信号灯法:

用java模拟人们穿过斑马线
使用人和汽车作为两个对象,当为红灯时汽车正常行驶,为绿灯时,人通过红绿灯

//模拟人根据红绿灯过斑马线
public class SingalTest {
    public static void main(String[] args) {
        Road road = new Road();
        new Person(road).start();
        new Lamp(road).start();
    }
}


class Person extends Thread{
    private Road road;
    public Person(Road road) {
        this.road = road;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i % 2 == 0) {
                road.personAcross("小刚");
            } else {
                road.personAcross("小红");
            }
        }
    }
}

class Lamp extends Thread{

    private Road road;
    public Lamp(Road road) {
        this.road = road;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i % 2 != 0) {
                road.carAcross("保时捷");
            } else {
                road.carAcross("兰博基尼");
            }
        }
    }
}

//斑马线
class Road {
    // true: 红灯,false:绿灯,相对斑马线来看
    boolean flag = true;

    //人穿马路
    public synchronized void personAcross(String name) {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(name + "走斑马线过马路");
        //更改红绿灯状态,让汽车通过
        this.flag = !this.flag;
        this.notify();
    }

    //汽车放行
    public synchronized void carAcross(String carName) {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //汽车通过马路
        System.out.println(carName + "通过了红路灯路口");
        //通知人可以穿过马路
        this.flag = !this.flag;
        this.notify();
    }
}

线程池

背景: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大

线程池的使用:

  • JDK 5.0起提供了线程池相关API: ExecutorService和Executors
  • ExecutorService: 真正的线程池接口。常见子类ThreadPoolExecutor
    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执
      行Runnable
    • Future submit(Callable task):执行任务,有返回值,一般用来执行
      Callable
    • void shutdown() :关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
public class ThreadPoolTest {
    public static void main(String[] args) {
        //创建一个线程池
        ExecutorService service = Executors.newFixedThreadPool(4);
		//执行Runable的实现类
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        //关闭服务
        service.shutdown();
    }
}

class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

以上总结

三种线程创建方式:


public class FinallyThread {
    public static void main(String[] args) {
        new Thread1().start();
        new Thread(new Thread2()).start();

        //FutureTask 父类实现了Runnable
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new Thread3());
        new Thread(futureTask).start();
        try {
            Integer integer = futureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class Thread1 extends Thread {
    @Override
    public void run() {
        System.out.println(this.getName());
    }
}

class Thread2 implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

class Thread3 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName());
        return 520;
    }
}

6.编程进阶

volatile关键字

多线性内存模型:

image-20201013215906759

一般情况下,当线程获得CPU使用权后,开始执行前会将主内存(主存)中的变量Load到自己的工作内存中,处理完成后在save回主内存

volatile关键字被用于解决线程间内存可见性问题的手段之一,使用volatile关键字修饰变量后,编译器会在该变量的读写指令前后加上Load和Save动作,这就是所谓的内存屏障,内存屏障的另一个作用是禁止编译器对相关的指令进行重新排序,并确保CPU严格按照指令的输入顺序串行地执行这些指令。

public class VolatileDemo implements Runnable {

    public static volatile boolean flag = true;
    public static int value;


    @Override
    public void run() {
        while (flag) {
            value++;
            value--;

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(value);
        }
        System.out.println("flag:" + flag);
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();
        long l = System.currentTimeMillis();
        es.execute(new VolatileDemo());
        es.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                VolatileDemo.flag = false;
                System.out.println("set flag:" + VolatileDemo.flag);
            }
        });

        // 异步关闭线程
        es.shutdown();
        // 等待线程中的方法执行完毕
        es.awaitTermination(10L, TimeUnit.SECONDS);
        System.out.println(System.currentTimeMillis() - l);
    }
}

内存屏障确保了线程对volatile变量所做的单一读写操作,对其他线程可见。但是volatile并不能保证复合操作原子性的问题, 当两个线程同时LOAD count ,读到count的值都为0,都进行+1,并进行save写入主存中结果仍然是1,而不是2

synchronized

用于标记一个方法或代码块,通过显式或隐式地给对象上锁的方式,将自己的作用域变成了一个临时区,从而实现线程之间的同步。

当多个线程都执行到被synchronized修饰的方法或代码块时,也就是准备进入临界区时,他们将通过竞争指定对象的锁来取得进入相应临界区的权限。一旦某个线程获得了对象的锁,进入了临界区,其他试图进入临界区的线程将会被阻塞,并等待这个线程离开临界区(自动释放相应的“监视器”)或主动(暂时性)释放“锁”(调用“锁”对象的wait()方法)。

修饰代码块

// 要求当前线程具有data对象的锁
synchronized (data) {
    
}

// 要求当前线程获得MyRunnable的所有对象的内置锁(即Class对象的锁)
synchronized(MyRunnable.class) {
    
}

修饰非静态方法

// 此句要求当前线程获得add方法所属对象的锁
public synchronized void add(int i) {
    data.add(i);
}

修饰静态方法

// 要求当前线程获得add方法所属类的所有对象的内置锁(即Class对象的锁)
public synchronized static void add(int i) {
    data.add(i);
}

wait/notify/notifyAll

如果线程1在获得对象的锁后,需要等待线程2的结果,最理性的做法就是让线程2通知线程1继续执行,类似这种在线程间传递信息的动作称为线程间通信。

Object 类的wait()/notify()/notifyAll() 方法就是为此而设计的,其实线程间的重要手段之一。由于这3个方法都是基于synchronized关键字所提供的对象锁来实现的,因此,他们只能用于synchronized关键字修饰的同步方法或同步块里面使用。

调用a.wait()方法会是当前线程(线程1)放弃它持有a对象的锁,进入阻塞状态。直到别的线程(线程2)抢到对象a的锁,并调用a.notify()或a.notifyAll()后,线程1才可以被唤醒,重新回到就绪状态

注意:如果有10个线程先后抢到对象a的锁,并都调用的a.wait()方法,那么某个线程抢到对象a的锁,并调用a.notify()时,将唤醒10个线程中的一个,调用a.notifyAll()则会将这10个线程都唤醒,让他们都进入就绪状态,但最终只有一个线程通过竞争获得锁而执行。

此外,notify()/notifyAll()只是唤醒某个阻塞状态的线程,而不会释放锁,所以在编程中,尽量使用了notify()/notifyAll()后立即退出临界区,以便唤醒的线程可以尽快执行。

案例:

public class WaitNotifyDemo implements Runnable {


    private static final byte[] flag = new byte[0];

    @Override
    public void run() {
        synchronized (flag) {
            try {
                System.out.println("Before flag wait()");
                flag.wait();
                System.out.println("after flag wait()");
                // TimeUnit.SECONDS.sleep(1);
                flag.wait(10000L);  // 阻塞10s,到了10s会自动唤醒
                System.out.println("after sleep()");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        es.execute(new WaitNotifyDemo());
        es.execute(new WaitNotifyDemo());
        es.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (flag) {
                    System.out.println("flag.notifyAll");
                    flag.notifyAll();
                    // flag.notify();  // 只会唤醒一个线程,另一个线程始终处于阻塞状态
                }
            }
        });
        es.shutdown();

    }


}

CAS操作

synchronized是一种独占锁、悲观锁,存在以下问题

  • 某一个线程持有锁后,其他所有竞争此锁的线程都将被阻塞,影响性能。
  • 容易存在死锁的风险。
  • 锁的竞争、加锁、解锁都会导致比较多的上下文切换和调度时延,开销较大。
  • 一个优先级搞的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

​ volatile虽然能解决内存可见性的问题,但他不能保证原子性,适用场景有限。

CAS(compare and swap):其原理与数据库中的乐观锁原理相似,在更新某个变量前,检查变量的当前指是否符合期望值,如果符合就修改替换的值;否则循环重试(自旋),直到成功。

CAS与volatile的结合是java.util.concurrent包实现非阻塞算法的基础,但是CAS操作任然存在以下三方面的问题

  • ABA问题:因为CAS需要在操作值的时候检测值有没有发生变化,如果没有变化则更新;如果一个值原来是A,被另一个线程修改为了B,在次被另一个线程修改为A,那么在使用CAS检查时就不会发现它的值已经发生的变化。解决ABA问题可以使用版本号
  • 循环开销。当冲突严重时CAS中的循环重试(自旋)将增加CPU的负担。
  • 只能保证一个共享变量的原子操作。CAS不支持对多个共享变量的原子操作,这种情况扔需要锁机制来解决。也可以将多个变量组合为一个变量,或则作为成员变量放到对象中,在给这个对象添加版本号。java5提供了AtomicReference类来提供针对对象引用的原子性,可以把多个变量放在这样一个对象里来进行操作,从而保证对这些变量操作的原子性。

Lock自旋锁

java.util.concurrent.locks包中提供了锁(Lock)接口及其实现类(ReentrantLock)、读写分离锁(ReadWriteLock)接口及其实现类(ReentrantReadWriteLock)。与基本对象监视器的锁不同,线程在获取Lock锁的过程中不会阻塞锁,而会通过循环不断地重试(自旋),直到当前持有该Lock锁的线程释放该锁。Lock锁也被形象称为自旋锁。

private static final Lock lock = new ReentrantLock();
    
    public void run() {
        lock.lock();
        try {
            // do something
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

ReetrantLock提供了4中上锁方式,有效避免死锁

  • lock():上锁,如果不成功则阻塞,直到成功。在此期间不响应中断。
  • tryLock():尝试以非阻塞方式上锁,如果不成功,则返回失败。
  • lock.tryLock(time, unit): 尝试以非阻塞方式在指定时间内上锁,超时则失败。
  • lockInterruptly():上锁,如果不成功则阻塞,直到成功。在阻塞期间允许中断。

Condition条件变量

由于使用synchronized的同步机制要求所有线程等待同一对象的监视器“锁”标记,在通过wait()、signal()、signalAll()方法进行通信时,只能随机的唤醒这些线程之一,无法有选择的通知要唤醒哪些线程,也无法避免某些线程因为运气而长时间得不到执行的问题,因此这是一种粗粒度的非公平锁。

ReentrantLock运行创建多个条件变量,线程可以根据需要在不同的Condition上执行await、signal、signall方法。

一个Lock可以有多个Condition,并且await、signal方法都是面向Condition而不是Lock锁,也就是说signal方法只会通知在同一个Condition上执行了await的线程。

每个Condition对象都有一个队列 (AbstractQueuedSynchronizer) 。一旦线程调用condition方法的await方法,当前线程就被挂起进入此队列。内部由链表实现,当调用condition.signal()方法时,将唤醒condition对列中的第一个等待线程,signalAll()将会唤醒等待对列中的所有线程。

ReentrantLock允许通过构造函数的参数来指定是否为公平锁(默认为非公平锁),如果当前ReentrantLock对象使用的公平锁,则当前线程将被添加到Condition的挂起线程队列尾,以确保等待最久的线程最先被取出并唤醒,反之,将不保证线程的入队顺序。

public class ConditionTest implements Runnable {
    private static final Lock lock = new ReentrantLock();
    private static final Condition condition1 = lock.newCondition();
    private static final Condition condition2 = lock.newCondition();
    private boolean flag;

    public ConditionTest(boolean flag) {
        this.flag = flag;
    }

    public void run() {
        lock.lock();
        try {
            if (flag) {
                System.out.println("before condition1.await()" + currentThread().getName());
                condition1.await();
                System.out.println("after conditon1 await()" + currentThread().getName());
            } else {
                System.out.println("before condition2 await()" + currentThread().getName());
                condition2.await();
                System.out.println("after conditon2 await" + currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        es.execute(new ConditionTest(true));
        es.execute(new ConditionTest(false));
        es.execute(new ConditionTest(false));
        es.execute(new ConditionTest(false));
        es.execute(new ConditionTest(false));
        es.execute(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {

                    Thread.sleep(1000L);
                    System.out.println("condition1.signalAll");
                    condition1.signalAll();

                    Thread.sleep(2000L);
                    System.out.println("condition2.signalAll");
                    condition2.signal();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        });
        es.shutdown();
    }
}

ThreadLocal类

ThreadLocal称为线程本地变量或线程本地储存,作用是为“当前线程”提供一个临时持有和传递对象的方法。由同一线程所执行的代码,只要持有同一个ThreadLocal对象的引用,就都能访问到与当前线程绑定的同一个数据对象。

public class ThreadLocalDemo implements Runnable {
    protected static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    @Override
    public void run() {
        System.out.println("start" + threadLocal.get());
        threadLocal.set(Thread.currentThread().getName());
        try {
            test();
        } finally {
            threadLocal.remove();
        }
    }

    public void test() {
        System.out.println("test=" + threadLocal.get());
    }

    public static void main(String[] args) {
        // 线程池的大小小于提交的任务数才看得出效果
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            ThreadLocalDemo t = new ThreadLocalDemo();
            es.execute(t);
        }
        es.shutdown();
        try {
            es.awaitTermination(10L, TimeUnit.SECONDS);

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

CountDownLatch计数器

相当于一个倒叙的计数器,用来协调多个线程的执行,多个线程通过调用它们所共享的计数器(CountDownLatch对象)的countDown方法来让计数器减一,通过CountDownLatch对象的await方法来阻塞当前线程,直到计数器的值变为0,后续的代码才能够被执行

public class CountDownLatchTest {
    static CountDownLatch countDownLatch = new CountDownLatch(2);

    public static void main(String[] args) throws Exception{
        Thread parse1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println("parse1 finish");
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread parse2 = new Thread(() -> {
            countDownLatch.countDown();
            System.out.println("parse2 finish...");
        });
        parse1.start();
        parse2.start();
        // 到达指定时间后,将不再继续等待,继续执行下面的程序
        countDownLatch.await(1, TimeUnit.SECONDS);
        System.out.println("all parse finish.");
    }
}

CyclicBarrier栅栏

CyclicBarrier是一种可重用的线程阻塞器,通过调用其await方法在代码中形成栅栏,率先到达栅栏处的线程将被栅栏(await()方法)阻塞,直到指定数量的线程都到达栅栏处,后续的代码才能够被执行。

其构造方法用来指定要阻塞多少个线程,barrierAction参数则用来指定当条件满足时优先执行的内容。

// 当有n个线程调用await后才会执行, 线程到达屏障时会优先执行A
public class CyclicBarrierTest {
    static CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new A());

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                cyclicBarrier.await();
                System.out.println(1);
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();

        try {
            cyclicBarrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println(2);
    }

    static class A implements Runnable {
        @Override
        public void run() {
            System.out.println(3);
        }
    }
}

Semaphore信号量

Semaphore用来保护对一个或则多个共享 资源的访问,内部维护了一个计数器,其值为可以同时访问资源的许可证个数。

当一个线程要访问共享资源时,需要先获取一个许可证(信号量),如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器减去1,在访问共享资源。如果计数器为0,表示没有资源可以访问,线程被阻塞,直到某个线程使用完共享资源并释放信号量,将信号量内部的计数器加1,之前被阻塞的线程将被唤醒再次试图获得信号量。

public class SemaphoreTest {
    private static final int THREAD_COUNT = 30;
    private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
    private static Semaphore semaphore = new Semaphore(10);

    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            threadPool.execute(() -> {
                try {
                    semaphore.acquire();
                    System.out.println("available permits: " + semaphore.availablePermits());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            });
        }

        threadPool.shutdown();
    }
}

fork/join框架

为了充分利用CPU资源,提高多线程、多进程的并发能力,提高任务的处理速度,把一个大任务拆分成若干个子任务,交给多个线程来处理,然后将每个线程的输出结果进行合并,获得大任务的最终处理结果,这种处理方式被称为fork/join

public class ForkJoinTest extends RecursiveTask<Integer> {
    private static final int THRESHOLD = 2;
    private int start;
    private int end;
    public ForkJoinTest(int start, int end) {
        this.start = start;
        this.end = end;
    }


    @Override
    protected Integer compute() {
        int sum = 0;

        if ((end - start) < THRESHOLD) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            int mid = (end + start) / 2;
            ForkJoinTest leftResult = new ForkJoinTest(start, mid);
            ForkJoinTest rightResult = new ForkJoinTest(mid + 1, end);
            leftResult.fork();
            rightResult.fork();
            Integer leftValue = leftResult.join();
            Integer rightValue = rightResult.join();
            sum = leftValue + rightValue;
        }
        return sum;
    }

    public static void main(String[] args) throws Exception {
        ForkJoinTest forkJoinTest = new ForkJoinTest(1, 4);
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> submit = forkJoinPool.submit(forkJoinTest);
        Integer integer = submit.get();
        System.out.println(integer);

    }
}

Exchanger

用于线程间协作的工具类,主要用作线程间的数据交换,他提供了一个同步点当两个线程都到达了这个同步点,那么这两个线程可以交换数据。

如果一个线程先执行了exchange方法,那么他会等待第二个线程执行exchange方法

// 可以用于校对两个人做的工作是否相同
public class ExchangerTest {
    private static final Exchanger<String> exgr = new Exchanger<>();
    private static final ExecutorService es = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        es.execute(() -> {
            try {
                String A = "bank A";
                exgr.exchange(A);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        es.execute(() -> {
            try {
                String B = "bank B";
                String A = exgr.exchange(B);
                System.out.println("A 和 B是否一样: " + A.equals(B));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        es.shutdown();
    }
}

生产者、消费者

// lock版生产者消费者
public class B {


    public static void main(String[] args) {
        // 新版
        Data2 data = new Data2();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    data.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    data.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    data.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    data.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"D").start();

    }

}

// 资源类  属性,方法
class Data2{
    private int num = 0;
    // 定义锁
    Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    // +1
    public void increment() throws Exception{

        // 加锁
        lock.lock();

        try {
            //判断
            while (num!=0){
                condition.await(); //等待
            }
            // 干活
            num++;
            System.out.println(Thread.currentThread().getName()+"\t"+num);
            // 通知
            condition.signalAll();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 解锁
            lock.unlock();
        }
    }

    // -1
    public void decrement() throws Exception{


        // 加锁
        lock.lock();
        try {
            // 判断
            while (num==0){
                condition.await(); //等待
            }
            // 干活
            num--;
            System.out.println(Thread.currentThread().getName()+"\t"+num);
            // 通知
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 解锁
            lock.unlock();
        }
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值