多线程

理解多线程(java.Thread)

线程简介

进程:

应用程序的执行实例,有独立内存空间和系统资源

线程:

cpu调度和分派的基本单位,进程中执行运算最小的单位,可完成一个独立顺序控制流程

多线程:

在一个进程中同时运行了多个线程,完成不同的工作

多个线程交替占用cpu资源,而非真正并行执行

好处:

充分利用cpu资源

简化编程模型

带来良好用户体验

创建线程的三种方式

  • 继承Thread class
  • 实现Runnable 接口(重要)
  • 实现Callable 接口

继承Thread类

  1. 一个类继承Thread类。
  2. 从写run()方法,写入需要执行的方法。
  3. 实例化类,通过实例调用start()方法开启线程。
public class CustomThread extends Thread {
    //重写的run方法
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run"+i);
        }
    }

    public static void main(String[] args) {
        CustomThread customThread = new 				CustomThread();
        customThread.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("main"+i);
        }
    }
}

这里实例是不能调用run()方法。

不过这个方式在jdk1.5之后就已经过时了。

实现Runnable接口

  1. 类实现Runnable接口
  2. 重写Run()方法
  3. 实例化rRunnable接口,然后new Thread()调用start()方法将实例化的接口放入Thread中。
public class RunnableTest implements Runnable {

    //重写的run方法
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run"+i);
        }
    }

    public static void main(String[] args) {
        RunnableTest runnableTest = new RunnableTest();
        new Thread(runnableTest).start();
        for (int i = 0; i < 20; i++) {
            System.out.println("main"+i);
        }
    }
}

实现这个接口在于这样的方式可以开启多个线程操控同一个实例。简单实例看代码:

public class RunnableTest2 implements Runnable {

    private int ticketNum = 0;

    public void run() {
        while (true){
            if(ticketNum<=0){
                break;
            }
            //睡眠,模拟延时操作
            try {
                Thread.sleep(200L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"===>拿到了第"+ticketNum+"张票");
        }
    }

    public static void main(String[] args) {
        RunnableTest2 runnableTest2 = new RunnableTest2();
        //开启多条线程
        new Thread(runnableTest2,"小何").start();
        new Thread(runnableTest2,"小心").start();
        new Thread(runnableTest2,"小牛").start();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JRkgBlxX-1611478756729)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1611386506491.png)]

但是发现这样开启多线程有一个问题(不安全),多个线程就会抢夺同一个资源,这也是一个简单的并发问题。

下面来一个经典龟兔赛跑小实例:

public class Race implements Runnable {

    //胜利者的名字
    private String winner;

    public void run() {
        for (int i = 0; i <= 100; i++) {
            //这里模拟“博尔特儿”跑的太快了,在终点前休息等待刘翔儿
            if(Thread.currentThread().getName().equals("博尔特儿")&&i==98){
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if(isOver(i)){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"=====>跑了"+i+"步");
        }
    }

    //判断比赛是否已经结束
    private boolean isOver(int i){
        if(winner != null){
            return true;
        }{
            if(i >= 100){
                winner = Thread.currentThread().getName();
                System.out.println("胜利者是"+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Runnable runnable = new Race();

        new Thread(runnable,"博尔特儿").start();
        new Thread(runnable,"刘翔儿").start();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gIZ56egO-1611478756733)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1611388073353.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zpCIwDi1-1611478756736)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1611388081906.png)]

实现Callable接口

简单案例:

//这里<>中写道是call方法中返回值类型
public class CallableTest implements Callable<Boolean> {

    private String name;
    private int ticketNum = 1;

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

    public Boolean call() throws Exception {
        while (true){
            if(ticketNum<=0){
                break;
            }
            //睡眠,模拟延时操作
            try {
                Thread.sleep(200L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name+"===>拿到了第"+ticketNum--+"张票");
        }
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableTest runnableTest2 = new CallableTest("小何");
        CallableTest runnableTest3 = new CallableTest("小牛");
        //创建执行服务
        ExecutorService service = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> r1 = service.submit(runnableTest2);
        Future<Boolean> r2 = service.submit(runnableTest3);
        //获取结果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        //关闭执行服务
        service.shutdownNow();
    }
}

线程五大状态

  1. 创建状态

    也就是new

  2. 就绪状态

    当调用了start()方法是,线程就进入了就绪状态

  3. 阻塞状态

    当调用了sleep或wait方法时

  4. 运行状态

    由就绪状态可进入运行状态

  5. 死亡状态

    线程结束

线程方法

暂停

不推荐使用jdk中推出的stop方法和destroy方法,可以看到jdk中这方法也被遗弃了(destroy

推荐的暂停方法就是通过标记(flag)来暂停。

public class ThreadFunctionTest implements  Runnable{

    private boolean flag = true;

    @Override
    public void run() {
        while (flag){
            int i = 0;
            System.out.println("Thread ..... run " + i++);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //改变flag值可以将现场结束
    public void stop(){
        this.flag = false;
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadFunctionTest threadFunctionTest = new ThreadFunctionTest();
        new Thread(threadFunctionTest).start();
        for (int i = 0; i < 50; i++) {
            System.out.println("main ... run " + i);
            Thread.sleep(50);
            if(i == 25){
                threadFunctionTest.stop();
            }
        }
    }
}
睡眠
Thread.sleep(Long time);
//线程会进入阻塞状态
礼让

礼让其实就是重新调配cpu,使线程重新竞争资源。并不一定可以成功。

public class TestYield implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始======>");
        //礼让
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"结束======>");
    }

    public static void main(String[] args) {
        TestYield testYield = new TestYield();
        new Thread(testYield,"a").start();
        new Thread(testYield,"b").start();
    }
}
join,强制执行

强制执行线程,使其他线程进入阻塞状态。程序中尽量少用容易造成程序阻塞。(可以理解为插队)

public class TestJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"=======>"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin,"a");
        thread.start();
        for (int i = 0; i < 50; i++) {
            System.out.println("main ... run"+i);
            if(i == 25){
                //强制执行
                thread.join();
            }
        }
    }
}

Lambda表达式

lambda表达式是为了简化代码用的。

简单认识一下lambda,创建一个线程:

new Thread(()->System.out.print("多线程学习")).start();

lambda表达式是在java 8后引入的,在这之前需要理解一下函数式接口Function Interface

Function Interface:

  • 任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口
  • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
public class lambdaTest {

    public static void main(String[] args) {

        //使用lambda首先接口要是函数型接口
        Inter inter = (int a,String b) -> {
            System.out.println("hello world 1"+a+b);
        };
        //省略参数类型一个省略全需省略,若参数只有一个可以省略(),
        inter = (a,b) -> {
            System.out.println("hello world 1"+a+b);
        };
        //若执行代码只有一行可以省略{}
        inter = (a,b) -> System.out.println("hello world 1"+a+b);

        inter.hello(1,"2");
    }

}

interface Inter{
    void hello(int a,String b);
}

class InterImpl implements Inter{

    public void hello(int a,String b) {
        System.out.println("hello world 1"+a+b);
    }
}

Thread静态代理

静态代理模式我的请看设计模式

Thread类中,我们可以在源码或者java的文档中看到Thread是实现了Runnable接口的,这里其实就可以把Runnable看成一个实例,二Thread就是一个代理(Proxy)。不理解的可以去看一下我的设计模式。

观测线程状态

Thread.State

public class ThreadState {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("****************");
        });

        //新生状态
        Thread.State state = thread.getState();
        System.out.println(state);
        thread.start();
        //就绪状态
        state = thread.getState();
        System.out.println(state);
        //监测线程状态
        while (state != Thread.State.TERMINATED){
            state = thread.getState();
            System.out.println(state);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

线程优先级

priority

先设置优先级后开启线程。

public class PriorityTest {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"**********"+Thread.currentThread().getPriority());
        Runnable r1 = ()-> System.out.println(Thread.currentThread().getName()+"**********"+Thread.currentThread().getPriority());

        Thread t1 = new Thread(r1,"r1");
        Thread t2 = new Thread(r1,"r2");
        Thread t3 = new Thread(r1,"r3");
        Thread t4 = new Thread(r1,"r4");

        //设置最小优先级为1
        t1.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        //设置默认优先级为5
        t2.setPriority(Thread.NORM_PRIORITY);
        t2.start();
        //设置最自定义优先级
        t3.setPriority(6);
        t3.start();
        //设置最大优先级为10
        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();
    }
}

但是优先级也不一定生效,也是由cpu调度的

守护线程(daemon)

public class TestDaemon {
    public static void main(String[] args) {
        Runnable r1 = ()->{
            while (true){
                System.out.println("我是守护进程!");
            }
        };
        Runnable r2 = ()->{
            for (int i = 0; i < 365; i++) {
                System.out.println("我活了"+i+"天了!");
            }
            System.out.println("我结束了!");
        };
        
        Thread thread = new Thread(r1);
        //设置r1位守护进程!
        thread.setDaemon(true);
        thread.start();
        
        new Thread(r2).start();
    }
}

线程同步

队列和锁。synchronized锁。安全但是存在性能问题。

**同步块:**synchronized(Obj){}

1.synchronized属性,在之前讲的那个买车票的问题中,出现的一个线程不安全的问题,就比如车票等出现负一等。只要在改变车票数的方法上加一个synchronized属性,就比如让他们排队买票。

public class TestSyn1 implements Runnable{
        private int ticketNum = 10;

        @Override
        public void run() {
            while (true){
                if(ticketNum<=0){
                    break;
                }
                //睡眠,模拟延时操作
                try {
                    Thread.sleep(200L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                check();
            }
        }

        //这里增加了synchronized
        private synchronized void check(){
            if(ticketNum<=0){
                return;
            }
            System.out.println(Thread.currentThread().getName()+"===>拿到了第"+ticketNum--+"张票");
        }

        public static void main(String[] args) {
            TestSyn1 testSyn1 = new TestSyn1();
            //开启多条线程
            new Thread(testSyn1,"小何").start();
            new Thread(testSyn1,"小心").start();
            new Thread(testSyn1,"小牛").start();
        }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5NdCGBWo-1611478756742)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1611470955799.png)]

另一个例子,需要用到同步块,传入同步监视器,这个同步监视器最好是一个线程执行中改变的量。

public class TestSyn2 {
    public static void main(String[] args) {
        Account account = new Account(100);
        Staction st1 = new Staction(account,50);
        Staction st2 = new Staction(account,100);
        new Thread(st1,"我").start();
        new Thread(st2,"你").start();
    }
}

//假设这是你的银行账户
class Account{
    //账户余额
    public int allMoney;

    public Account(int allMoney) {
        this.allMoney = allMoney;
    }
}

//假设这是银行
class Staction implements Runnable{

    //你的账户
    private Account account;
    //你要取的钱
    private int money;

    public Staction(Account account,int money){
        this.account = account;
        this.money = money;
    }

    @Override
    public void run() {
        //设置同步块变量为account
        synchronized (account) {
            if (account.allMoney - money < 0) {
                System.out.println(Thread.currentThread().getName()+"钱不够了,无法取钱啦");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.allMoney = account.allMoney - money;
            System.out.println(Thread.currentThread().getName() + "取了" + money + "万元,账户中还剩余" + account.allMoney);
        }
    }
}

我们知道ArrayList()是线程不安全的集合,可能因为两个线程同时操作一个ArrayList将值覆盖了。但是只要加上同步块就可以解决。


public class TestSyn3 {
    public static void main(String[] args) {
        List<String> list = new ArrayList();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized (list) {
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

JUC CopyOnWriteArrayList线程安全的集合

public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                    list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

死锁

产生死锁的必要条件

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

简单例子:

public class TestDead {
    public static void main(String[] args) {
        ThreadTest t1 = new ThreadTest("小光",0);
        ThreadTest t2 = new ThreadTest("小亮",1);
        t1.start();
        t2.start();
    }
}

class Test1{}

class Test2{}

class ThreadTest extends Thread{
    Test1 test1 = new Test1();
    Test2 test2 = new Test2();

    String name;

    int choose ;

    ThreadTest(String name,int choose){
        this.name = name;
        this.choose = choose;
    }

    @Override
    public void run() {
        if(choose == 0){
            synchronized (test1){
                System.out.println(name+"拿到了test1,正在等待test2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (test2){
                    System.out.println(name+"拿到了test2,正在等待test1");
                }
            }
        }else{
            synchronized (test2){
                System.out.println(name+"拿到了test2,正在等待test1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (test1){
                    System.out.println(name+"拿到了test1,正在等待test2");
                }
            }
        }
    }
}

Lock锁

可重入锁

public class TestLock implements Runnable{
    private int ticketNum = 10;

    //lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                lock.lock();
                //睡眠,模拟延时操作
                try {
                    Thread.sleep(200L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(ticketNum>0) {
                    System.out.println(Thread.currentThread().getName() + "===>拿到了第" + ticketNum-- + "张票");
                }else {
                    break;
                }
            }finally {
                lock.unlock();
            }

        }
    }

    public static void main(String[] args) {
        TestLock testLock = new TestLock();
        //开启多条线程
        new Thread(testLock,"小何").start();
        new Thread(testLock,"小心").start();
        new Thread(testLock,"小牛").start();
    }
}

线程协作

生产者消费者模式

解决方法1(缓冲区,管程法):

public class TestOne {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        new Producer(buffer).start();
        new Consumer(buffer).start();
    }
}

//汉堡
class Hamburger{
    public int id;

    public Hamburger(int id){
        this.id = id;
    }
}

//生产者
class Producer extends Thread{

    private Buffer buffer;
    public Producer(Buffer buffer){
        this.buffer =  buffer;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 100; i++) {
                buffer.Push(new Hamburger(i));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

//消费者
class Consumer extends Thread{

    private Buffer buffer;
    public Consumer(Buffer buffer){
        this.buffer =  buffer;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 100; i++) {
                buffer.Pop();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

//缓冲区
class Buffer{
    //缓冲区中有汉堡的数量
    int count = 0;
    //缓冲区中最大汉堡的数量
    int Max_Count = 10;
    //缓冲区中汉堡的数组
    Hamburger[] hamburgers = new Hamburger[Max_Count];

    //生产者把汉堡放入缓冲区
    public synchronized void Push(Hamburger hamburger) throws InterruptedException {
        //如果缓冲区中的汉堡放满了,则生产者停止生产
        if(count == hamburgers.length){
            //生产者等待,通知消费者消费
            this.wait();
        }
        //生产者把汉堡放到缓冲区
        hamburgers[count] = hamburger;
        System.out.println("生产者放入了第"+hamburger.id+"个汉堡");
        count++;
        //通知消费者
        this.notifyAll();
    }

    //消费者消费缓冲区中的产品
    public synchronized void Pop() throws InterruptedException {
        //若缓冲区中没有产品,者消费者等待通知生产者
        if(count == 0){
            //消费者等待
            this.wait();
        }
        //消费者消费缓冲区中的产品
        System.out.println("消费者消费了第"+hamburgers[count-1].id+"的汉堡");
        count--;
        //通知生产者
        this.notifyAll();
    }

}

解决方法2:(信号灯法)

public class TestTwo {

    public static void main(String[] args) {
        Flag flag = new Flag();
        new Producer1(flag).start();
        new Consumer1(flag).start();
    }

}

//生产者
class Producer1 extends Thread{

    private Flag flag;

    public Producer1(Flag flag){
        this.flag =  flag;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 20; i++) {
                flag.Push(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

//消费者
class Consumer1 extends Thread{

    private Flag flag;

    public Consumer1(Flag flag){
        this.flag =  flag;
    }
    @Override
    public void run() {
        try {
            for (int i = 0; i < 20; i++) {
                flag.Pop();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

//信号标志类
class Flag{
    int msg;
    //默认flag为true
    boolean flag = true;

    //生产者生产
    public synchronized void Push(int msg) throws InterruptedException {
        //如果flag标志位不为true生产者就等待
        if(!flag){
            this.wait();
        }
        this.msg = msg;
        System.out.println("生产者生产了:=======>"+this.msg);
        //通知消费者唤醒他们
        this.notifyAll();
        //改变标志位
        this.flag = !this.flag;
    }

    //消费者消费
    public synchronized void Pop() throws InterruptedException {
        //如果flag标志位为true消费者就等待
        if(flag){
            this.wait();
        }
        System.out.println("消费者消费了:=======>"+this.msg);
        //通知生产者生产唤醒
        this.notifyAll();
        //改变标志位
        this.flag = !this.flag;
    }

}

线程池

线程经常的创建和销毁,在线程很多的情况下特别是并发情况,这样的方式非常的耗费资源。所以就需要线程池,将线程先创建好,放入一个池中,用完可以放回池中,避免频繁的销毁创建、实现重复利用。总之好处多多

//这里<>中写道是call方法中返回值类型
public class CallableTest implements Callable<Boolean> {

    private String name;
    private int ticketNum = 1;

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

    public Boolean call() throws Exception {
        while (true){
            if(ticketNum<=0){
                break;
            }
            //睡眠,模拟延时操作
            try {
                Thread.sleep(200L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name+"===>拿到了第"+ticketNum--+"张票");
        }
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableTest runnableTest2 = new CallableTest("小何");
        CallableTest runnableTest3 = new CallableTest("小牛");
        //创建执行服务
        ExecutorService service = Executors.newFixedThreadPool(3);
        //提交执行       service.execute() 是Runnable的线程池调用方法,没有返回值
        Future<Boolean> r1 = service.submit(runnableTest2);
        Future<Boolean> r2 = service.submit(runnableTest3);
        //获取结果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        //关闭执行服务
        service.shutdownNow();
    }
}

源码地址:https://gitee.com/fantg/process

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值