Java多线程拾遗 --- 创建线程方式,线程的相关方法,线程锁,线程通信

回顾一下Java基本的多线程,为JUC打一下基础

1、线程创建

注意:调用run方法和start方法的区别

image-20211219174312196

1.1 继承Thread类

继承Thread类,重写run()方法,编写线程执行体

线程对象使用start()方法启动线程

class MyThread1 extends Thread{

    @Override
    public void run() {
        System.out.println("继承Thread创建==>>"+Thread.currentThread().getName());
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        //1.new一个线程对象  然后执行start方法
        MyThread1 t1 = new MyThread1();
        t1.start();
        //2.或者直接运行
        new MyThread1().start();
    }
}

1.2 实现Runnable接口

一遍推荐实现Runnable接口来创建线程,因为继承有局限性

Thread类本身实现了Runnable接口

和继承Thread类一样,需要重写run()方法,编写线程执行体

线程对象使用start()方法启动线程

注意:要new一个Thread类来代理启动或者用线程池方式等代理启动

class MyThread2 implements Runnable{

    @Override
    public void run() {
        System.out.println("实现Runnable创建==>>"+Thread.currentThread().getName());
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        //实现Runnable的类需要一个 Thread代理类帮助其启动
        Thread t2 = new Thread(new MyThread2());
        t2.start();
        //或者直接运行
        new Thread(new MyThread2()).start();
    }
}

1.3 实现Callable接口

重写call方法,需要抛出异常

使用线程池方式:

先创建一个服务:ExecutorService threadPool = Executors.newFixedThreadPool(1);

再提交执行:Future submit = threadPool.submit(t3);

可以获取call()函数的返回值:submit.get();

最后要关闭服务:threadPool.shutdown();

class MyThread3 implements Callable<String>{

    /*call类型和Callable<?>一致,可自定义*/
    @Override
    public String call() throws Exception {
        String str = "实现Callable创建==>>" + Thread.currentThread().getName();
        System.out.println(str);
        return str;
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {

        MyThread3 t3 = new MyThread3();
        //实现Callable的类启动方式:FutureTask或线程池等
        FutureTask<String> futureTask = new FutureTask<>(t3);
        new Thread(futureTask).start();

        //创建一个线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(1);
        //submit()参数可以为实现Callable类的
        Future<String> submit = threadPool.submit(t3);
        try {
            String str = submit.get();
            System.out.println(str);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        threadPool.shutdown();
    }
}

2、线程状态和相关方法

2.1 线程五大状态

image-20211219174941216

  1. 线程刚new出来进入新生状态

  2. 调用start()方法进入就绪状态

  3. 就绪状态的线程经过cpu调度进入运行状态

  4. 运行队列满或者调用sleep,wait,被阻塞在临界区外时是阻塞状态

  5. 线程中断或结束进入死亡状态,死亡后不能再次启动

2.2 线程相关方法

2.2.1 优先级setPriority

image-20211219175105427

  • 优先级默认为5,可设置范围为1-10,超出范围会抛出IllegalArgumentException
package com.bandit.ThreadMethod;

public class ThreadDemo2 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();

        Thread thread1 = new Thread(myThread);
        Thread thread2 = new Thread(myThread);
        Thread thread3 = new Thread(myThread);
        Thread thread4 = new Thread(myThread);
        Thread thread5 = new Thread(myThread);

        thread1.setPriority(6);
        thread2.setPriority(3);
        thread3.setPriority(Thread.NORM_PRIORITY);  //默认5
        thread3.setPriority(Thread.MAX_PRIORITY);   //10
        thread4.setPriority(Thread.MIN_PRIORITY);  //1

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();

    }
}

class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"优先级为"+Thread.currentThread().getPriority());
    }
}

实际上运行有很多种结果,这个优先级只是cpu调度该线程的概率

2.2.2 线程休眠sleep

线程执行过程中可以通过sleep进行休眠

注意:sleep不会释放锁

try {
	Thread.sleep(200);
} catch (InterruptedException e) {
	e.printStackTrace();	
}
public class ThreadSleep {
    public static void main(String[] args) {
        ticketThead ticketThead = new ticketThead();

        new Thread(ticketThead,"小明").start();
        new Thread(ticketThead,"小王").start();
        new Thread(ticketThead,"黄牛").start();
    }
}

class ticketThead implements Runnable{

    //票数
    private int ticketNums = 10;
    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) {
                break;
            }
            //捕获异常
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "--->拿到了第" + ticketNums-- + "张票");
        }
    }
}

image-20211219185615031

查看运行,会有拿到0票和负票的,解决方法:加锁

2.2.3 线程停止

不建议使用JDK提供的stop()和destory()方法

推荐设置一个标志位作为终止变量,当flag=false时终止线程运行

public class ThreadStop {
    public static void main(String[] args) {
        stopThread t1 = new stopThread();
        new Thread(t1).start();
		
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //停止
        t1.stop();
    }
}

class stopThread implements Runnable{
    private boolean flag = true;
    public void stop() {//向外提供一个stop方法设置flag
        this.flag = false;
    }
    @Override
    public void run() {
        while (flag){
            System.out.println("运行");
        }
    }
}

2.2.4 线程礼让yield

让当前正在执行的进程暂停,但不阻塞,即由运行态->就绪态

让cpu重新调度,礼让不一定成功,全靠cpu调度

public class ThreadYield {

    public static void main(String[] args) {
        YieldThread t1 = new YieldThread();
        new Thread(t1,"线程1").start();
        new Thread(t1,"线程2").start();
    }
}

class YieldThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开始执行");
        Thread.yield();//礼让
        System.out.println(Thread.currentThread().getName() + "线程停止执行");
    }
}

礼让成功:

image-20211219190716583

2.2.5 线程插队join

join可以想象成插队,让其他进程等待此进程执行完后再执行

public class ThreadJoin {
    public static void main(String[] args) {
        JoinThread joinThread = new JoinThread();
        Thread thread = new Thread(joinThread);
        thread.start();

        for (int i = 0; i < 200; i++) {
            if(i==100){//在100时插队
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("主线程"+i);
        }

    }
}

class JoinThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("插队进程"+i);
        }
    }
}

如果不要这段,会先跑完主进程再执行子进程

if(i==100){//在100时插队
    try {
        thread.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

2.3 线程状态观测

image-20211219192316286

public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } 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);//Run
        while (state != Thread.State.TERMINATED) {//只要现成不终止,就一直输出状态
            Thread.sleep(100);
            state = thread.getState();//更新线程状态
            System.out.println(state);
        }
        //死亡后的线程不能再启动了,启动会报异常
        //thread.start();
    }
}

2.4 守护线程

线程分为用户线程和守护线程

  • 用户线程:虚拟机必须保证用户线程执行完毕才关闭
    • main线程和自定义的一些其他Thread线程
  • 守护线程:虚拟机不用等待守护线程执行完毕
    • gc垃圾回收,监控内存,后台记录操作日志等线程

可以通过thread.setDaemon(true)设置线程为守护线程,默认为false

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

        Thread thread = new Thread(god);
        //默认false表示是用户线程,正常的线程都是用户线程...
        thread.setDaemon(true);
        //上帝守护线程启动
        thread.start();
        //你 用户线程启动
        new Thread(you).start();
    }
}
//上帝
class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("上帝保佑着你");
        }
    }
}

//你
class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你一生都开心的活着");
        }
        System.out.println("====goodbye!world====");
    }
}

3、线程锁

多个线程访问一个临界资源时需要加锁,让访问的线程排列成队列互斥访问临界区

3.1 synchronized

当一个线程获得对象的排他锁,独占资源,其他线程必须等待该进程使用资源后释放锁才能访问

存在问题:

  • 一个线程持有锁会导致其他需要此锁的线程挂起
  • 多线程竞争时加锁解锁频繁会引起性能问题
  • 如果一个优先级低的线程拿到了锁,让优先级高的等待会导致优先级倒置现象

3.1.1 方法锁

缺陷:如果将一个大方法直接申明为synchronized会影响效率

改造之前买票的案例

public class SynchronizedDemo {
    public static void main(String[] args) {
        ticketThead ticketThead = new ticketThead();

        new Thread(ticketThead,"小明").start();
        new Thread(ticketThead,"小王").start();
        new Thread(ticketThead,"黄牛").start();
    }

}

class ticketThead implements Runnable{

    //票数
    private int ticketNums = 10;
    @Override
    public synchronized void run() {
        while (true) {
            if (ticketNums <= 0) {
                break;
            }
            //捕获异常
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "--->拿到了第" + ticketNums-- + "张票");
        }
    }
}

此处临界区为run方法,所以在run方法上加锁

3.1.2 块锁

  • synchronized ( Obj ) { }
  • Obj 称为 同步监视器,就是上锁的对象
public class SynchronizedDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                synchronized (list){//给list上锁
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        System.out.println(list.size());
    }
}

3.2 Lock

  • 从JDK5.0开始,java提供了一个更强大的同步锁Lock

  • 每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象

  • ReentrantLock类实现了Lock,拥有与synchronized相同的并发性和内存语义,可以显示加锁解锁

购票问题的Lock实现

public class LockDemo {
    public static void main(String[] args) {
        ticketThead ticketThead = new ticketThead();

        new Thread(ticketThead,"小明").start();
        new Thread(ticketThead,"小王").start();
        new Thread(ticketThead,"黄牛").start();
    }
}


class ticketThead implements Runnable{

    //票数
    private int ticketNums = 10;
    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();//加锁
                //买票
                if (ticketNums <= 0) {
                    break;
                }
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();//解锁
            }
            System.out.println(Thread.currentThread().getName() + "--->拿到了第" + ticketNums-- + "张票");
        }
    }
}

3.3 synchronized 和 lock 对比

image-20210216110142335

4、线程通信

4.1 引入

操作系统中生产者消费者问题:采用信号量法

对信号量有WaitSignal操作,

同样的,在Java中也有类似的操作:

  • wait():线程一直等待直到其他线程通知,与sleep不同,会释放锁
  • wait(Long timeout):指定等待时间
  • notify():唤醒一个等待状态的线程
  • notfiyAll():唤醒同一个对象上所以调用wait()方法的线程,优先级高的线程先唤醒

注意:以上方法只能在同步方法或同步块中使用,否则会抛出异常

4.2 生产者消费者问题

4.2.1 缓冲区法

//缓冲区法
public class Demo01 {
    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();
        new Producer(synContainer).start();
        new Consumer(synContainer).start();
    }
}



//生产者
class Producer extends Thread {
    //容缓冲区
    SynContainer container;

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

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

//消费者
class Consumer extends Thread {
    //容缓冲区
    SynContainer container;

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

    //消费
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了-->" + container.pop().id + "件产品");
        }
    }
}

//产品
class Product {
    int id;//产品编号

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

//缓冲区
class SynContainer {
    //需要一个容器大小
    Product[] products = new Product[10];
    //容器计数器
    int count = 0;

    //生产者放入产品
    public synchronized void push(Product product) {
        //如果容器满了,需要等待消费者消费
        /*如果是if的话,假如消费者1消费了最后一个,这是index变成0此时释放锁被消费者2拿到而不是生产者拿到,这时消费者的wait是在if里所以它就直接去消费index-1下标越界,如果是while就会再去判断一下index得值是不是变成0了*/
        while (count == products.length) {
            //通知消费者消费,等待生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有满,需要丢入产品
        products[count] = product;
        count++;
        //通知消费者消费
        this.notifyAll();
    }

    //消费者消费产品
    public synchronized Product pop() {
        //判断是否能消费
        while (count <= 0) {
            //等待生产者生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        count--;
        Product product = products[count];
        //吃完了 通知生产者生产
        this.notifyAll();
        return product;
    }
}

4.2.2 标志位法

//产品-->节目
public class TV {

   //演员表演,观众等待
   //观众观看,演员等待
   String voice;   //表演的节目
   boolean flag = true;

   //表演
   public synchronized void play(String voice) {

       if(!flag){
           try {
               this.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }

       System.out.println("演员表演了" + voice);
       //通知观众观看
       this.voice = voice;
       this.notifyAll();
       this.flag = !flag;

   }

   //观看
   public synchronized void watch(){
       if(flag){
           try {
               this.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       System.out.println("观众观看了" + voice);
       //通知演员表演
       this.notifyAll();
       this.flag = !flag;
   }
}

public class Player extends Thread {
   private TV tv = null;

   public Player(TV tv) {
       this.tv = tv;
   }

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           tv.play("表演了" + i + "号节目");
       }
   }
}

public class Watcher extends Thread {

   private TV tv = null;

   public Watcher(TV tv) {
       this.tv = tv;
   }

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           tv.watch();
       }
   }
}

//测试生产者消费者问题:信号灯法,标志位解决
public class Main {

   public static void main(String[] args) {
       TV tv = new TV();
       new Watcher(tv).start();
       new Player(tv).start();

   }
}

5、线程池

5.1 介绍

背景: 经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完毕放回池中,可以避免频繁的创建销毁,实现重复利用,类似生活中的工共交通工具
好处:

  • 提高了响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理(…)
  • corePoolSize: 核心池的大小
  • maximumPoolSize: 最大线程数
  • keepAliveTime: 线程没有任务时最多保持多长时间后会终止

5.2 使用线程池

先创建一个线程池:ExecutorService threadPool = Executors.newFixedThreadPool(1);

再提交执行:

  • Future submit = threadPool.submit(t3); 括号里为Callable接口方法
  • threadPool.invokeAll(callables); 括号里为Callable接口方法的集合
  • threadPool.execute(new MyThread()); 括号里为Runnable接口方法

可以获取call()函数的返回值:submit.get();

最后要关闭服务:threadPool.shutdown();

public class Demo02 {

    public static void main(String[] args) {

        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        //callable
        Future<String> submit = threadPool.submit(new MyThread());
        //runnable
        threadPool.execute(new MyThread2());
        //callable List任务集合
        ArrayList<Callable<String>> callables = new ArrayList<>();
        callables.add(new MyThread());
        callables.add(new MyThread());
        try {
            threadPool.invokeAll(callables);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        
        threadPool.shutdown();
    }

}

class MyThread implements Callable<String> {

    @Override
    public String call() throws Exception {
        String str = "实现Callable创建==>>" + Thread.currentThread().getName();
        System.out.println(str);
        return str;
    }
}

class MyThread2 implements Runnable{

    @Override
    public void run() {
        System.out.println("实现Runnable创建==>>"+Thread.currentThread().getName());
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值