线程通信(代码几乎不用)

经典问题:生产者与消费者问题

生产不能过剩,消费不能停止(消息队列)

this.notifyAll()
this.wait()

线程状态

public class Thread {
    
    public static enum State {
        NEW,
        RUNNABLE, // 可运行
        BLOCKED,  // 锁阻塞
        WAITING,  // 无限等待
        TIMED_WAITING, // 计时等待
        TERMINATED;  // 被终止
        
        private State(){}
    }
}

新建状态:

// 只是线程对象,没有线程特征
Thread t = new MyThread();

可运行状态:(就绪/运行)

// 只代表可运行(有资格竞争CPU,得到CPU才进入运行状态)
t.start();
// 被其他线程调用唤醒,要抢锁
t2.notifyAll();

被终止状态:

执行完毕或出现异常

无限等待:

// 被唤醒要去抢锁
this.wait();

计时等待:

// 线程休眠不会放锁,时间到不用抢锁
t.sleep(3000);
// 时间到自己醒,抢锁
this.wait(4000);

线程池

线程池在java中的代表类:ExecutorService

// 创建线程池
ExecutorService pools = Executors.newFixedThreadPool(3);
// 添加线程任务
Runnable target = new MyRunnable();
pools.submit(target);
pools.submit(target);
pools.submit(target);
pools.submit(target); // 复用线程

pools.shutdown(); // 等执行完关闭线程池
pools.shutdownNow(); // 立即关闭,无论是否执行完毕


class MyRunnable implements Runnable {
    @Override
    public void run() {
        for () {
			System.out.println(Thread.currentThread().getName());
        }
    }
}
// 创建线程池
ExecutorService pools = Executors.newFixedThreadPool(3);
// 添加线程任务
Future<String> t1 = pools.submit(new MyCallable(1));
Future<String> t2 = pools.submit(new MyCallable(2));
Future<String> t3 = pools.submit(new MyCallable(3));
Future<String> t4 = pools.submit(new MyCallable(4)); // 复用线程

try {
    String s1 = t1.get();
    String s1 = t1.get();
    String s1 = t1.get();
    String s1 = t1.get();
} catch (Exception e) {
	e.printStackTrace();
}

pools.shutdown(); // 等执行完关闭线程池
pools.shutdownNow(); // 立即关闭,无论是否执行完毕


class MyCallable implements Callable<String> {
    private int x;
    public MyCallable(int x) {
        this.x = x;
    }
    @Override
    public String call() {
        return Thread.currentThread().getName();
    }
}

死锁

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

  1. 互斥,当一个资源被占用,别的线程不能使用;
  2. 资源请求者不能强制资源占有者释放资源,资源只能由占有者主动释放;
  3. 资源请求者在请求其他资源时把持对原有资源的占有
  4. 循环等待,p1要p2的资源,p2要p1的资源

写一个必然死锁的程序:

new Thread(new Runnable() {
    @Override
    public void run() {
        // 占用资源1
        synchronized (resource01) {
            Thread.sleep(1000);
            // 请求资源2
            synchronized (resource02) {
                System.out.println("success01");
            }
        }
    }
});

new Thread(new Runnable() {
    @Override
    public void run() {
        // 占用资源2
        synchronized (resource02) {
            Thread.sleep(1000);
            // 请求资源1
            synchronized (resource01) {
                System.out.println("success02");
            }
        }
    }
});

Volatile关键字(不保证线程安全)

线程间变量不可见性

一个线程修改了值,另一个线程不可见

Java内存模型:

主内存 + 每个线程有独立的工作内存(保存共享变量的工作副本)

线程间变量的值的传递需要通过主内存中转

两种常见的解决方式:

  1. 加锁(得到锁后会清空工作内存)

  2. 对共享变量进行Volatile关键词修饰

    Volatile修饰的变量被修改后会通知所有工作内存值失效

###原子性

是指一批操作是一个整体。同时成功、同时失败

Volatile只能保证线程间变量的可见性,不能保证变量的原子性

保证原子性操作:

  1. 加锁 — 》 还能保证可见性

    加锁机制性能较差,别人连读都读不了

  2. 使用原子类(CAS机制):atomic包(且性能好 && 安全)

    AtomicInteger帮助更新数量的原子更新操作

CAS:Compare And Swap(比较再交换)

在修改变量前 比较复制的值和原来的值是否相同,相同才能修改

不相同重新获取值