目录太长了,体验不好,就看左边的目录吧,自动生成的目录有点水。。
在冯诺依曼体系下,整个计算机设备分为,应用程序,操作系统,处理器(cpu),主存,I/O设备。应用程序在操作系统的调节下在处理器上进行合理的资源分配,而在其中一个运行起来的程序,就是进程。
cpu有一个概念,核心数和线程,核心为物理核心,线程为逻辑核心
而进程管理其实分为两步:
1.描述一个进程:使用结构体或类,把一个进程有哪些信息,表示出来。
2. 组织这些进程:使用一定的数据结构,把这些结构体/对象,放在一起。
内存分配:
操作系统给进程分配的内存,是以“虚拟地址空间”的方式进行分配。
之前说过,进程是一个运行程序,然而一个程序内的功能有很多个,而这其中就有一个问题,就是客户可能会同时用一个程序的多个功能。诺是按照以前我们的写发就是一个main方法,去实现一个主要功能,肯定是不行的。为了应对这个情况,多线程运行就在所难免。
需求决定技术发展
线程是更轻量的的进程。约定一个进程可以包含多个线程,此时多个线程每个线程都是一个独立可以调度执行的执行流(并发),这些线程公用同一份进程的系统资源。
可以理解为,一个工厂(进程),中有很多个生产线:(线程)(调用同一份资源,内存空间,文件描述符)。
其中几个问题要重点理解。一个厂子也就意味着资源和场地是一定的,如果为了生产效率,盲目去增加生产线,不去顾忌这些,反而会使的整个生成效率变慢。同理一个主机的核心也是有限,所以增加的线程数和进程数也是有限度。而一台主机到限度了,就可以增加另一台主机,从而使得核心数增加(也就是分布式处理)。
进程和线程的区别:
1.进程包含线程
2.进程有自己独立的内存空间和文件描述符,同一个进程的多个线程之间,共享同一份地址空间和文件描述符
3.进程是操作系统资源分配的基本单位,线程是操作系统调度的基本单位
4.进程之间具有独立性,一个进程挂了,不会影响到别的进程;同一个进程里的多个线程之间,一线程挂了,可能会把整个进程带走,会影响到其他线程的。
这是我们第一个多线程。
public class ThreadDemo {
private static class MyThread extends Thread {
@Override
public void run() {
Random random = new Random();
while (true) {
// 打印线程名称
System.out.println(Thread.currentThread().getName());
try {
// 随机停止运行 0-9 秒
Thread.sleep(random.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
Random random = new Random();
while (true) {
// 打印线程名称
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(random.nextInt(10));
} catch (InterruptedException e) {
// 随机停止运行 0-9 秒
e.printStackTrace();
}
}
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("这里是线程运行的代码");
}
}
创建一个线程实例
MyThread t = new MyThread();
调用 start 方法启动线程
t.start(); // 线程开始运行
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这里是线程运行的代码");
}
}
Thread t = new Thread(new MyRunnable());
调用start方法
t.start(); // 线程开始运行
class MyRunnabble implements Runnable{
@Override
public void run() {
while (true)
{
System.out.println("123__true");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class test1 {
public static void main(String[] args) {
MyRunnabble runnabble=new MyRunnabble();
Thread t=new Thread(runnabble);
t.start();
while (true) {
System.out.println("main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
常见方法:
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
常见属性:
// 使用匿名类创建 Thread 子类对象Thread t1 = new Thread () {@Overridepublic void run () {System . out . println ( " 使用匿名类创建 Thread 子类对象 " );}};
// 使用匿名类创建 Runnable 子类对象Thread t2 = new Thread ( new Runnable () {@Overridepublic void run () {System . out . println ( " 使用匿名类创建 Runnable 子类对象 " );}});
// 使用 lambda 表达式创建 Runnable 子类对象Thread t4 = new Thread (() -> {System . out . println ( " 使用匿名类创建 Thread 子类对象 " );});
public class test3 {
static int count=0;
public synchronized static void sum(){
count++;
}
public static void main(String[] args) throws InterruptedException {
long time=System.currentTimeMillis();
Thread t1=new Thread(()->{
for (int i = 0; i < 10000; i++) {
sum();
}
});
Thread t2=new Thread(()->{
for (int i = 0; i < 10000; i++) {
sum();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
System.out.println(System.currentTimeMillis()-time);
}
}
public class ThreadDemo {
private static class MyRunnable implements Runnable {
public volatile boolean isQuit = false;
@Override
public void run() {
while (!isQuit) {
System.out.println(Thread.currentThread().getName()
+ ": 转账");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
System.out.println(Thread.currentThread().getName()
+ ": 让李四开始转账。");
thread.start();
Thread.sleep(10 * 1000);
System.out.println(Thread.currentThread().getName()
+ ": 通知李四对方是个骗子!");
target.isQuit = true;
}
}
public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public void run() {
// 两种方法均可以
while (!Thread.interrupted()) {
//while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName()
+ ": 转账!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
System.out.println(Thread.currentThread().getName()
+ ": 开始转账。");
thread.start();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()
+ ": 对方是个骗子!");
thread.interrupt();
}
}
方法 | 说明 |
public void interrupt() |
中断对象关联的线程,如果线程正在阻塞,则以异常方式通知, 否则设置标志位
|
public static boolean
interrupted()
|
判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean
isInterrupted()
|
判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
方法 | 说明 |
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
public class test2 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(){
@Override
public void run() {
while (true) {
System.out.println("123545_run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
t.start();
t.join();
while (true) {
System.out.println("123545_run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
NEW: 安排了工作 , 还未开始行动RUNNABLE: 可工作的 . 又可以分成正在工作中和即将开始工作 .BLOCKED: 这几个都表示排队等着其他事情WAITING: 这几个都表示排队等着其他事情TIMED_WAITING: 这几个都表示排队等着其他事情TERMINATED: 工作完成了 .
public class ThreadState {
public static void main(String[] args) {
for (Thread.State state : Thread.State.values()) {
System.out.println(state);
}
}
}
public class test3 {
static int count=0;
public static void sum(){
count++;
}
public static void main(String[] args) throws InterruptedException {
long time= System.nanoTime();
Thread t1=new Thread(()->{
for (int i = 0; i < 10000; i++) {
sum();
}
});
Thread t2=new Thread(()->{
for (int i = 0; i < 10000; i++) {
sum();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
我们会发现这个代码出现了一个问题,与我们想要的预期不一致。即出现bug
其本质是,count++操作,本质是有三个CPU指令构成
1.load,把内存中的数据读到cpu寄存器中。
2.add,就是把寄存器中的值,进行+1操作
3.save,把寄存器中值写回内存中。
大家肯定学过数学,那么对于组合,肯定是有过了解的。那么我问个问题,现在线程有两个,分别对count进行++操作。对于cpu来说有几种组合方式?3*3共有9种,那么问题来了,我们只要唯一的结果,不需要这么结果可能。
其实在进一步理解,可以理解为,两个线程对同一个变量,进行了相互作用。
1.抢占式执行(大部分的原因)
2.多个线程修改同一个变量(不安全)(而有几种情况是安全:
一个线程改同一个变量(安全),多个线程读同一个变量(安全),多个线程修改不同变量)
3.修改操作,不是原子性的。
4.内存可见性,引起的线程不安全。
5.指令重排序,引起的线程不安全。
static class Counter {
public int count = 0;
synchronized void increase() {
count++;
}
synchronized void increase2() {
increase();
}
}
可重入锁的内部, 包含了 "线程持有者" 和 "计数器" 两个信息:
synchronized 使用:
public class SynchronizedDemo {
public synchronized void methond() {
}
}
public class SynchronizedDemo {
public synchronized static void method() {
}
}
public class SynchronizedDemo {
public void method() {
synchronized (this) {
}
}
}
public class SynchronizedDemo {
public void method() {
synchronized (SynchronizedDemo.class) {
}
}
}
static class Counter {
public int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
while (counter.flag == 0) {
// do nothing
}
System.out.println("循环结束!");
});
Thread t2 = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("输入一个整数:");
counter.flag = scanner.nextInt();
});
t1.start();
t2.start();
}
根据上面的代码,发现问题没有,无论我们在控制台中输入什么值,程序都不会结束。为什么呢,就像上面所说的那样,一个cup的寄存器中数据并没进行更新。另一个线程所拿到的数据没有进行跟换。其主要原因是计算机运算速度太快了。寄存器和缓存的速度都太快了,
使用特点:
1.volatile 不保证原子性
2.volatile 适用于一个线程读,一个线程写
3.synchronized 既能保证原子性, 也能保证内存可见性.
改正后:
static class Counter {
volatile public int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
while (counter.flag == 0) {
// do nothing
}
System.out.println("循环结束!");
});
Thread t2 = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("输入一个整数:");
counter.flag = scanner.nextInt();
});
t1.start();
t2.start();
}
此时就可以结束进程了。
wait和notify的使用(只能一个结束,一个开始)
static class WaitTask implements Runnable {
private Object locker;
public WaitTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
while (true) {
try {
System.out.println("wait 开始");
locker.wait();
System.out.println("wait 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class NotifyTask implements Runnable {
private Object locker;
public NotifyTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
System.out.println("notify 开始");
locker.notify();
System.out.println("notify 结束");
}
}
}
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(new WaitTask(locker));
Thread t2 = new Thread(new NotifyTask(locker));
t1.start();
Thread.sleep(1000);
t2.start();
}
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
加锁进行实例化对象,是很耗资源。其实一个实例创建后,在内存中已经存在,其他线程其实更多的是读操作。那么就没必要去进行加锁操作。
对上述代码改进:
class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
//判断是否为空,诺不为空,就不需进行加锁实例化。
if (instance == null) {
synchronized (Singleton.class) {
//进行实例化判断
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
两个if所代表的意义有所不同。
1.加锁if:把if和new变为原子操作
2.双重 if:减少不必要的加锁操作
3.使用volatile 禁止指令重排序,保证后续线程肯定拿到的是完整对象。
单例模式:线程安全问题:
饿汉模式:天然就是安全的,只是读操作
懒汉模式:不安全的,有读也有写。
本质是一个循环队列,但是它带有阻塞特性;
1.如果入队列为空,尝试出队列,就会阻塞等待。等待到队列不空为止。
2.如果队列满了,尝试入队,就会阻塞等待,等待到队列不满为止。
这个就有一个经典的模型进行解释--生产者消费者模型,什么是生产者消费者模型?其实简单理解为,生产效率与消费效率的比值。比如一个面包厂1小时生产4个面包,而此时有很多人等着吃面包。这个就是简单的生产者消费者模型。
这个数据结构的模式有几个好处:
1.可以让上下游模块之间,可以更好的“解耦和”。
队列与具体业务无关,队列中的某一个线程挂了,不影响其他线程,比如电脑有时候网页会卡,但是某些功能还在运行。
2.削峰填谷
不知道各位有没有打游戏,王者农药肯定都听过,其中有个事情,在游戏早期,它出了一款皮肤,这个皮肤很受玩家喜欢,再上线的那一刻,众多玩家,蹲点购买。使得当时的支付系统蹦了几分钟。为了应对这种情况,阻塞队列就可以减少这种风险。
在Java中提供了一个阻塞队列的数据的集合,BlockingQueue
BlockingQueue queue = new LinkedBlockingQueue<>();
// 入队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞.
String elem = queue.take();
主要分为三步:
1.先实现一个普通队列
2.加上线程安全
3.加上阻塞功能
class MyBlockingQueue{
//普通队列
private int [] items=new int[1000];
//规定head--tail的范围为有效范围
volatile private int head=0;
volatile private int tail=0;
volatile private int size=0;
//入队列
synchronized public void put(int elem) throws InterruptedException {
//队列元素满了
while (size==items.length){
this.wait();
}
items[tail]=elem;
tail++;
//判断是否到达末尾,队列中的元素没有满的情况下
if (tail==items.length){
tail=0;
}
//可读性下差,开发效率慢
//tail=tail%items.length;
this.notify();
size++;
}
//出队列
synchronized public Integer take() throws InterruptedException {
while (size==0){
this.wait();
}
int value=items[head];
head++;
if (head==items.length){
head=0;
}
this.notify();
size--;
return value;
}
}
public class test8 {
public static void main(String[] args) {
MyBlockingQueue queue=new MyBlockingQueue();
Thread t1=new Thread(()->{
while (true){
try {
int value= queue.take();
System.out.println("消费:"+value);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread t2=new Thread(()->{
int value=0;
while (true) {
try {
System.out.println("生产:"+value);
queue.put(value);
Thread.sleep(1000);
value++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
}
}
入队成功后其他线程才能出队,出队成功后其他线程才能入队。
在java中提供了Timer类。Timer 类的核心方法为 schedule ,其中包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)。
Timer timer = new Timer ();timer . schedule ( new TimerTask () {@Overridepublic void run () {System . out . println ( "hello" );}}, 3000 );
class MyTask implements Comparable{
public Runnable runnable;
//为了方便后续,使用绝对的时间戳
public long time;
public MyTask(Runnable runnable,long delay){
this.runnable=runnable;
//获取当前时刻的时间戳+delay,作为任务的实际执行时间
this.time=System.currentTimeMillis()+delay;
}
@Override
public int compareTo(MyTask o) {
//设置比较器,构建优先级队列
return (int)(this.time-o.time);
}
}
class MyTimer{
//这个结构,带有优先级的阻塞对列,核心数据结构
//创建一个锁对象
private Object loker=new Object();
private PriorityBlockingQueue queue=new PriorityBlockingQueue<>();
//此处的dalay 是一个形如3000这样的数字(多长时间后执行)
public void schedule(Runnable runnable,long dalay){
//根据参数,构造MyTask,插入队列即可
MyTask myTask=new MyTask(runnable,dalay);
queue.put(myTask);
synchronized (loker){
loker.notify();
}
}
//构造线程
public MyTimer(){
Thread t=new Thread(()->{
while (true) {
try {
synchronized (loker){
MyTask myTask=queue.take();
long curTime=System.currentTimeMillis();
if (myTask.time <= curTime){
//时间到了,执行任务
myTask.runnable.run();
}else {
//时间还没到
//将刚刚取出的任务,重新塞回队列
queue.put(myTask);
loker.wait(myTask.time-curTime);
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
}
}
public class test10 {
}
(就是就是装有很多线程的仓库,使用线程从里面拿就好)
ExecutorService pool = Executors . newFixedThreadPool ( 10 );pool . submit ( new Runnable () {@Overridepublic void run () {System . out . println ( "hello" );}});
class Worker extends Thread {
private LinkedBlockingQueue queue = null;
public Worker(LinkedBlockingQueue queue) {
super("worker");
this.queue = queue;
}
@Override
public void run() {
// try 必须放在 while 外头, 或者 while 里头应该影响不大
try {
while (!Thread.interrupted()) {
Runnable runnable = queue.take();
runnable.run();
}
} catch (InterruptedException e) {
}
}
}
public class MyThreadPool {
private int maxWorkerCount = 10;
private LinkedBlockingQueue queue = new LinkedBlockingQueue();
public void submit(Runnable command) {
if (queue.size() < maxWorkerCount) {
// 当前 worker 数不足, 就继续创建 worker
Worker worker = new Worker(queue);
worker.start();
}
// 将任务添加到任务队列中
queue.put(command);
}
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool = new MyThreadPool();
myThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("吃饭");
}
});
Thread.sleep(1000);
}
}
上面我说过,锁是为了解决线程冲突的问题。但是我也说过加锁操作会影响程序的效率。(因为阻塞),为了应对这个我们应该合理去进行加锁操作,那么就应该有策略的操作。
乐观锁: 预测接下来冲突概率不大(做的工作少)--->效率会快一些
悲观锁:预测接下了的冲突概率不大(做的多)--->x效率会慢一些
其实这两个就是预测接下来的锁冲突(阻塞等待)的概率是大,还是不大,根据这个冲突的概率,决定接下来怎么做。
轻量级锁:加锁解锁的过程更快更高效。(一个乐观锁很可能是一个轻量级锁)
重量级锁:加锁解锁,过程更慢,更低效。(一个悲观锁很可能是一个重量级锁)
自旋锁:是轻量级锁的一种典型实现(纯用户态的不需要经过内核态(时间相对更短))
加锁失败后,不停等待的去问是否可以加锁了
挂起等待锁:是重量级锁的一种典型实现(通过内核机制来实现挂起等待(时间更长了))
加锁失败后,先去做其他事情,等这个锁给我信号后我就回来加锁。
Synchronized 既是悲观锁,也是乐观锁,既是轻量级锁,也是重量级锁;轻量级锁部分基于自旋锁实现,重量级锁部分基于挂起等待锁实现。
而Synchronized 会根据当前锁竞争的激烈程度,自适应;
互斥锁:
synchronized是一个互斥锁,就单纯的加锁。通常只有两种操作:
读写锁:
有一种锁,把读操作和写操作分开加锁(线程安全):
约定:
Java中专门提供了读锁一个类,写锁一个类。
如何产生死锁,我们对一个代码加两次锁,此时内部的锁要等待外部的锁释放才能加锁,而此时外部的锁释放,需要等待内部锁加锁成功。然后逻辑上矛盾了,于是产生了死锁。
两个线程两把锁,即使单个线程是可重入锁,也会死锁。
线程1的外部锁加锁,需要等待线程2内部锁释放,同理线程2外部锁加锁,需要等待线程1内部锁释放,此时逻辑矛盾,产生死锁。
哲学家,就餐问题(N个线程,M把锁)
一个桌子上有五只筷子。也有五个人,桌上有一碗面,每个人只能用一双筷子吃一口。诺是五个同时拿起一只筷子,场上就构不成一双筷子的条件,也就是谁都吃不了面。此时就死锁了。
怎么办,很简单,五个人约定一个规则,谁先吃,谁后吃,此时就可以避开死锁的情况。
死锁的四个必要条件
实践中如何避免死锁?
对锁进行编号,如果需要获取多把锁,就约定加锁顺序,务必先对编号小的加锁,在对编号大的加锁。
公平锁VS非公平锁
约定:
遵循先来后到,就是公平锁,不遵守先来后到的(等概率竞争是不公平的),非公平锁。
synchronized是非公平的,要实现公平就需要在synchronized的基础上,加个队列来记录这些加锁线程的顺序。
总结一下synchronized的特点:
但是CAS不是没有问题,最典型的问题A->B->A问题,其实就是我们要内存改变的值与内存的值一样,是得不断在A--B--A中不断横跳。在具体一点就是,两个线程(t1,t2)对数据进行减法,(t3)还有一个对数据进行加法,而加的数据与减的数据一样。
那么就会有一个问题。两个线程中其中一个线程(t1)提前做了减操作,接下来是(t3)加操作,此时内存的值没变,t2线程发现值是原来的值,又做了一次减操作。(这显然不是我们所期望的)
如何解决呢?
加入一个衡量内存的值是否变化的量,俗称版本号,版本号只能增加无法减少,每一次修改版本+1,这样我只需对比版本号本身就可以避免aba问题。
synchronized的锁策略:锁升级
先让线程针对锁,有个标记,如果整个代码执行过程中没有遇到别的线程和我竞争这个所,我就加锁了。但是如果有人来竞争,就升级为真的锁。这样既保证了效率,也保证了线程安全。
基础逻辑是,非必要不加锁。编译器+JVM 判断锁是否可消除,如果可以,就直接消除。检测当前代码是否多线程执行,判断是否有必要加锁,如果没有必要,但是又加上了锁,就会在编译过程中自动取消掉。
比如StringBuffer,在源码内加入了synchronized关键字。诺是单线程就必要加锁了,也就可以取消掉。
锁的粒度,synchronized代码块,包含代码的多少(代码越多,粒度越粗,越少,粒度越细),多数情况希望锁的粒度更小。(串行代码少,意味着并发代码就多。)
如果有一个场景需要频繁的加锁解锁,此时就会将整个场景锁起来,变成一个更粗的锁
//创建一个类 Result , 包含一个 sum 表示最终结果, lock 表示线程同步使用的锁对象
static class Result {
public int sum = 0;
public Object lock = new Object();
}
public static void main(String[] args) throws InterruptedException {
Result result = new Result();
Thread t = new Thread() {
@Override
public void run() {
//main 方法中先创建 Result 实例, 然后创建一个线程 t. 在线程内部计算 1 + 2 + 3 + ... + 1000
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
synchronized (result.lock) {
result.sum = sum;
//主线程同时使用 wait 等待线程 t 计算结束
result.lock.notify();
}
}
};
t.start();
synchronized (result.lock) {
//
while (result.sum == 0) {
result.lock.wait();
}
//当线程 t 计算完毕后, 通过 notify 唤醒主线程, 主线程再打印结果.
System.out.println(result.sum);
}
}
创建线程计算 1 + 2 + 3 + ... + 1000(callable)
Callable callable = new Callable() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
return sum;
}
};
FutureTask futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
int result = futureTask.get();
System.out.println(result);
Callable中泛型是什么,就返回什么。
Callable 和 Runnable的区别
FutureTask 的理解,其实可以理解为,炖汤,通常炖汤我们将食物放入砂锅中,只需要等待时间过去2-3小时,砂锅就能为我们呈现一锅鲜美的汤。
本质是一个计数器,描述了当前“可用资源”的个数
如果计数器为0,就阻塞等待,等待出现资源时,及继续申请等待。
Semaphore semaphore = new Semaphore(4);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("申请资源");
semaphore.acquire();
System.out.println("我获取到资源了");
Thread.sleep(1000);
System.out.println("我释放资源了");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 20; i++) {
Thread t = new Thread(runnable);
t.start();
}
public class Demo {
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(10);
Runnable r = new Runable() {
@Override
public void run() {
try {
Thread.sleep(Math.random() * 10000);
latch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
// 必须等到 10 人全部回来
latch.await();
System.out.println("比赛结束");
}
}
常用的集合类:ArrayList,LinkedList,HashMap,PriorityQueue。。。线程是不安全的。
如果要使用怎么办?
1.可以手动对集合的修改操作加锁。(synchronized 或者 ReentrantLock)
2.使用java标准库提供的一些线程安全的版本的集合类。
是线程安全的,给关键方法加上synchronized,颗粒度比较粗。它对整个哈市表加锁,任何的增删查操作,都会触发加锁,也就意味着会有锁竞争。其实没有必要,哈希表是有桶的,修改值是要通过key计算hash值,然后将新元素放到链表上。
两个线程对不同量进行修改,不会产生冲突,但是由于方法上加了锁也就意味着,两个线程同时使用一个方法会阻塞。(所以不建议)