目录
Java多线线程部分知识
/      

Java多线线程部分知识

Java多线线程部分知识

线程状态

  1. NEW

    尚未启动的线程处于此状态

  2. RUNNABLE

    在Java虚拟机中执行的线程处于此状态

  3. BLOCKED

    被阻塞等待监视器锁定的线程处于此状态

  4. WAITING

    正在等待另一个线程执行特定动作的线程处于此状态

  5. TIMED_WAITING

    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态

  6. TERMINATED

    已退出的线程处于此状态

一个线程可以在给定时间点,处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。

package cn.lacknb.test.callable;

import java.util.concurrent.TimeUnit;

/**
 * @author gitsilence
 * @version 1.0
 * @date 2020/9/17 0017 下午 16:54
 * @mail [email protected]
 */
public class ThreadState implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("running -- > " + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new ThreadState());
        Thread.State state = t.getState();
        System.out.println("启动前的状态 ---> " + state);
        t.start();
        state = t.getState();
        System.out.println("启动后的状态 ---> " + state);
        while (state != Thread.State.TERMINATED ) {
            TimeUnit.NANOSECONDS.sleep(800);
            state = t.getState();
            System.out.println("当前的状态 ---> " + state);
        }

        state = t.getState();
        System.out.println("最终的状态 ---> " + state);

    }
}

线程创建所处状态

  1. new Threa() ,线程对象一旦创建就进入到了新生状态
  2. 当调用start()方法,线程立即进入就绪状态,但不意味着立即调度执行
  3. 进入运行状态,线程才真正执行线程体的代码块。
  4. 当调用sleep,wait或同步锁定时,线程就进入了阻塞状态,就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行。
  5. 线程中断或者结束,一旦进入死亡状态,就不能再次启动。

yield

重新竞争cpu -- 礼让

package cn.lacknb.test.callable;

/**
 * @author gitsilence
 * @version 1.0
 * @date 2020/9/17 0017 下午 16:06
 * @mail [email protected]
 */
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) {

        Thread t = new Thread(new TestYield(), "子线程A ---> ");
        t.start();
        Thread t2 = new Thread(new TestYield(), "子线程B ---> ");
        t2.start();
    }
}

join

合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。

package cn.lacknb.test.callable;

/**
 * @author gitsilence
 * @version 1.0
 * @date 2020/9/17 0017 下午 16:28
 * @mail [email protected]
 */
public class TestJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            System.out.println("join ---> " + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new TestJoin());
        t.start();
        Thread.sleep(1);
        for (int i = 0; i < 300; i++) {
            if (i == 50) {
                t.join();
            }
            System.out.println("main ----> " + i);
        }
    }
}

线程优先级

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度按照优先级决定应该调哪个线程来执行。

线程的优先级用数字表示,范围从 1 ~ 10

  • Thread.MIN_PRIORITY = 1;
  • Thread.MAX_PRIORITY = 10;
  • Thread.NORM_PRIORITY = 5;

使用以下方式改变或获取优先级

getPriority()

setPriority(int xxx)

优先级不能真正决定 高的先执行,决定权还是在cpu。

package cn.lacknb.test.callable;

/**
 * @author gitsilence
 * @version 1.0
 * @date 2020/9/17 0017 下午 17:18
 * @mail [email protected]
 */
public class TestPriority implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " ---> " + Thread.currentThread().getPriority());
    }

    public static void main(String[] args) {
        // 默认 优先级 是 5
        System.out.println("main ---> " + Thread.currentThread().getPriority());
        Thread t1 = new Thread(new TestPriority(), "线程1");
        t1.setPriority(4);
        Thread t2 = new Thread(new TestPriority(), "线程2");
        Thread t3 = new Thread(new TestPriority(), "线程3");
        Thread t4 = new Thread(new TestPriority(), "线程4");
        t4.setPriority(10);
        Thread t5 = new Thread(new TestPriority(), "线程5");
        t5.setPriority(2);
        Thread t6 = new Thread(new TestPriority(), "线程6");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();

    }
}

守护线程

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

虚拟机必须确保用户线程执行完毕

虚拟机不用等待守护线程执行完毕

如,后台记录操作日志,监控内存,垃圾回收等待...

setDaemon(Boolean flag) 默认为false

线程的同步synchronized

同步方法

public synchronized void add () {
  
}

同步代码块

synchronized (对象或者是class) {
  
}

synchronized默认锁的是this,当前对象。

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

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

Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

五、以上规则对其它对象锁同样适用.

加上 synchronized,效率明显变低,但是安全。下面例子真实反映出问题。

package cn.lacknb.test.lock;

/**
 * @author gitsilence
 * @version 1.0
 * @date 2020/9/17 0017 下午 19:26
 * @mail [email protected]
 */
public class SynchronizedTest implements Runnable {

    private int count = 1000;

    @Override
    public void run() {
        // 加上同步代码块
        synchronized (this) {
            for (int i = 0; i < 500; i++) {
                if (count > 0) {
                    count--;
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
                System.out.println(Thread.currentThread().getName() + " ------> " + count);
            }
        }
        // 不加 同步代码块
//        for (int i = 0; i < 500; i++) {
//            if (count > 0) {
//                try {
//                    Thread.sleep(500);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                count--;
//            } else {
//                break;
//            }
//            System.out.println(Thread.currentThread().getName() + " ------> " + count);
//        }

    }


    public static void main(String[] args) {
        SynchronizedTest synchronizedTest = new SynchronizedTest();
        new Thread(synchronizedTest, "线程A").start();
        new Thread(synchronizedTest, "线程B").start();
    }
}

访问同一个对象的 同步代码块 和 非同步代码块

package cn.lacknb.test.lock;

/**
 * @author gitsilence
 * @version 1.0 访问 非 同步代码块 不受影响
 * @date 2020/9/17 0017 下午 19:55
 * @mail [email protected]
 */
public class SynchronizedTest02 {

    public void hi () throws InterruptedException {
        synchronized (this) {
            for (int i = 0; i < 100; i++) {
                Thread.sleep(500);
                System.out.println(Thread.currentThread().getName() + " ----> hi ---> " + i);
            }
        }
    }

    public void hello () throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName() + " ----> hello ---> " + i);
        }
    }

    public static void main(String[] args) {
        final SynchronizedTest02 synchronizedTest02 = new SynchronizedTest02();
        new Thread(() -> {
            try {
                synchronizedTest02.hi();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                synchronizedTest02.hello();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

}

对于以上规则对象锁同样适用。

package cn.lacknb.test.lock;

/**
 * @author gitsilence
 * @version 1.0 同一个对象锁,获得对象锁的才能运行。
 * @date 2020/9/17 0017 下午 20:03
 * @mail [email protected]
 */
public class SynchronizedTest03 {

    static class TotalCount {

        private int count = 100;

        public void decrease1 () {
            for (int i = 0; i < 50; i++) {
                count--;
                System.out.println(Thread.currentThread().getName() + " ----> " + count);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        public void decrease2 () {
            for (int i = 0; i < 50; i++) {
                count--;
                System.out.println(Thread.currentThread().getName() + " ----> " + count);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public void execute (TotalCount totalCount) {
        synchronized (totalCount) {
            totalCount.decrease1();
        }
    }

    public void execute2 (TotalCount totalCount) {
        synchronized (totalCount) {
            totalCount.decrease2();
        }
    }

    public static void main(String[] args) {
        final TotalCount totalCount = new TotalCount();
        final SynchronizedTest03 synchronizedTest03 = new SynchronizedTest03();

        new Thread(() -> synchronizedTest03.execute2(totalCount)).start();
        new Thread(() -> synchronizedTest03.execute(totalCount)).start();
    }

}

Lock锁

https://blog.csdn.net/qq_38737992/article/details/89607758

synchronized: 是自动获取锁和释放锁

Lock: 是手动获取锁和释放锁、可中断的获取锁、超时获取锁。

Lock锁,可以得到和 synchronized一样的效果,即实现原子性、有序性和可见性。

相较于synchronized,Lock锁可手动获取锁和释放锁、可中断的获取锁、超时获取锁。

Lock 是一个接口,两个直接实现类:ReentrantLock(可重入锁), ReentrantReadWriteLock(读写锁)。

lock只有代码块锁,synchronized有代码锁和方法锁

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

优先使用的顺序:

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

线程通信

这是一个线程同步的问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

  • 对于生产者,没有生产产品之前,要通知消费者等待;而生产了产品之后,又需要马上通知消费者消费。
  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。
  • 在生产者和消费者问题中,仅有synchronized是不够的
    • synchronized 可阻止并发更新同一个共享资源,实现了同步
    • synchronized 不能用来实现不同线程之间的消息传递(通信)

wait 表示线程一直等待,直到其他线程通知,与sleep不同,wait会释放锁。

wait(int timeout) 指定等待的毫秒数

notify() 唤醒一个处于等待状态的线程

notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度。

均是object类的方法, 都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException

Synchronized 和 Lock 的区别

  1. Synchronized 内置的Java关键字,Lock是一个Java类
  2. Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
  3. Synchronized 会自动释放锁,lock必须要手动释放锁,如果不释放锁,死锁。
  4. Synchronized 线程1(获得锁、阻塞),线程2(等待,傻傻的等)Lock锁不一定会等待下去
  5. Synchronized 可重入锁,不可以中断,非公平的;lock可重入锁,可以判断锁,非公平(可以自己设置)
  6. Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码。

并发协作模型 ” 生产者 / 消费者模式 “ --- 管程法

  • 生产者:负责生产数据的模块(可能是方法、对象、线程、进程)
  • 消费者:负责处理数据的模块(可能是方法,对象、线程、进程)
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个缓冲区

生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

package cn.lacknb.test.proconsumer;

/**
 * @author gitsilence
 * @version 1.0 利用缓冲区解决: 管程法
 * @date 2020/9/19 0019 上午 10:19
 * @mail [email protected]
 */
public class ProviderConsumer {

    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();
        new Provider(synContainer).start();
        new Consumer(synContainer).start();
    }

}

/**
 * 生产者
 */
class Provider extends Thread {
    SynContainer synContainer;

    public Provider (SynContainer synContainer) {
        this.synContainer = synContainer;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println("生产了 第" + i + "只鸡");
            synContainer.push(new Chicken(i));
        }
    }
}

/**
 * 消费者
 */
class Consumer extends Thread {
    SynContainer synContainer;

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了 第" + synContainer.pop().id + "只鸡");
        }
    }
}

/**
 * 产品
 */
class Chicken {
    int id;

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

/**
 * 缓冲区
 */
class SynContainer {
    // 容器大小
    Chicken[] chickens = new Chicken[10];

    // 容器计数器
    int count = 0;

    // 生产者放入产品
    public synchronized void push (Chicken chicken) {
        // 如果容器满了,就需要等待消费者 消费
        if (count == chickens.length - 1) {
            // 通知消费者消费
            try {
                // 生产者等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果没有满,我们就需要丢入产品
        chickens[count] = chicken;
        count++;
        // 通知消费者 消费了
        this.notifyAll();

    }

    // 消费者 消费产品
    public synchronized Chicken pop () {
        // 判断能否消费
        if (count == 0) {
            // 等待生产者生产,消费者等待。
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果可以消费
        count--;
        Chicken chicken = chickens[count];

        // 吃完了,通知生产者生产
        this.notifyAll();
        return chicken;
    }
}

并发协作模型 ” 生产者 / 消费者模式 “ --- 信号灯法

取一个标志位 flag

package cn.lacknb.test.proconsumer;

/**
 * @author gitsilence
 * @version 1.0
 * @date 2020/9/19 0019 上午 11:08
 * @mail [email protected]
 */
public class ProviderConsumer02 {
    public static void main(String[] args) {
        Tv tv = new Tv();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}

// 生产者 -> 演员
class Player extends Thread {
    Tv tv;
    public Player (Tv tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                this.tv.play(" 快乐大本营 ");
            } else {
                this.tv.play( " 变形记 ");
            }
        }
    }
}

// 消费者 -> 观众
class Watcher extends Thread {
    Tv tv;
    public Watcher (Tv tv) {
        this.tv = tv;
    }

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

// 产品 -> 节目
class Tv {
    // 演员表演 观众等待 T
    // 观众观看,演员等待 F
    String voice;  // 表演的节目
    boolean flag = true;  // 为true,说明当前没节目,需要进行表演

    // 表演
    public synchronized void play (String voice) {
        if (!flag) {
            // 有节目,观众正在观看,等待观众观看完毕
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了:  " + voice);
        this.flag = !this.flag;
        this.voice = voice;
        // 表演之后,通知观众可以看了。
        this.notifyAll();
    }

    // 观看
    public synchronized  void watch () {
        if (flag) {
            // 说明 没节目,需要等待 演员 表演完节目
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 有节目了之后,观众可以观看了
        System.out.println("观看了 " + voice + " 节目");
        this.flag = !this.flag;
        // 节目看完了,需要通知演员继续表演节目了
        this.notifyAll();
    }
}

线程池

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

思路:可以提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

好处:

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

ExecutorService : 真正的线程池接口。常见子类ThreadPoolExecutor

  • void execute (Runnable command) : 执行任务 / 命令,没有返回值,一般用来执行Runnable
  • Future submit(Callable task) :执行任务,有返回值,一般用来执行Callable
  • void shutdown () :关闭线程池。

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
  允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool:
  允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

Positive example 1:
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());

Positive example 2:
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();

//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown

//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown

Positive example 3:



property
ref

property
ref
property
ref
">
<property name=




<property name=


标题:Java多线线程部分知识
作者:MrNiebit
地址:https://blog.lacknb.cn/articles/2020/10/07/1602034654423.html