在一切开始之前,我们需要明白什么是进程和线程。
化繁为简:一个内存里运行的应用程序,并且本身都有个独立内存空间就是进程。
把进程当作意大利,当你坐飞机进入并且降落到进程这个国度的空间,你可能会看到许多执行路径(线程)。这里条条大路通罗马,可以自由切换你要走的线程,也可以分身同时走不同的线程(并发执行)。当然某条路径/线程也可以拥有其它的小径(路径/线程)。
但是,当你的脑子在想象这些线程的时候,同时模拟出所有路径的精确走向是比较困难的。所以调度方式这个概念就出来了。
分时调度:线程们轮流使用 CPU ,平均分配每个线程占用 CPU 的时间。
抢占式调度:优先级高的线程先用CPU,如相同优先级猜拳(随机)决定顺序。
所以,假如电脑一年是我们的一秒钟,那抢占调度模式在多个线程间几天一换对我们就是高速的切换,这让我们产生一切在同时发生的错觉。总而言之,前后者时间的总量是没有变化的,但是效率调高了(我更觉得是整体感观质量提升了)。
再讲两个基础概念区别:
同步:排队执行 , 效率低但数据安全。 异步:同时执行 , 效率高但数据不安全。
并发:指多件事在同一个时间段内发生。 并行:多件事同时发生。
线程安全:
举一个最简单的例子:
因为count不是局部变量,假设俩线程A和B同时进来,那count都是0变成1,但是事实上应该有序地去0+1,1+1,2+1 ...... 这样自增。
Integer count = 0; // 不是局部变量
public void getCount() {
count ++;
System.out.println(count);
}
所以,下面延伸出锁对于线程安全的概念:(上才艺)
注意:建议看一下thread的初始化博客甚至是源代码或者API,我也会后面专门补充基础的Thread类调用的常用方法。最好将下列代码放进去IDEA或者eclipse里去慢慢耐心地一行行感受与理解消化。都很基础但化繁为简。
import com.sun.source.tree.SynchronizedTree;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 多线程不安全问题
* 排队卖票案例
* 下面solution 1 和 2 是隐式锁 // 代表修饰符层面的解决方案,JVM层面的锁,源码的monitorexit来负责退出锁
* 3 显示锁 精准唤醒thread,可中断,性能高,手动开和放,API层面的锁 (对象.lock OR unlock)需要手动调试
* 123都是默认非公平锁,也就是大家抢
* 显示锁可以在构造方法(设置为true)变成公平锁,也就是大家排队先到先得
*/
public class Security {
public static void main(String[] args) {
//solution1,同步代码块 synchronized
//格式:synchronized(和你的锁对象)
//check锁对象有没有被某个线程标记了,有的话其它等待,然后等那个自动解锁后
//其它争夺这个锁,抢到执行line47(synchronized (o) {//排队,就像试衣间一把锁轮流开锁解锁这样。。})那行下面的👇代码,以此类推。。
Runnable run = new Ticket();
new Thread(run).start(); //-> run对象 is the lock锁
new Thread(run).start(); //-> run对象 is the lock锁
new Thread(run).start(); //-> run对象 is the lock锁
//solution2
//请跳到👇的 Ticket2 类
Runnable run2 = new Ticket2();
new Thread(run2).start(); //-> run2对象 is the lock锁
new Thread(run2).start(); //-> run2对象 is the lock锁
new Thread(run2).start(); //-> run2对象 is the lock锁
}
}
class Ticket implements Runnable {
private int count = 10;
private Object o = new Object(); //一把锁
// Solution3: 自己创造一个锁(显示锁),同一把
private Lock l = new ReentrantLock();
private Lock l2 = new ReentrantLock(true);//表示为公平锁
@Override
public void run() {
while (true) {
//l.lock();// sol3:
//同步代码块
synchronized (o) {//排队,就像试衣间一把锁轮流开锁解锁这样
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("卖票成功 余票:" + count);
}
l.unlock(); // sol3解锁
}
}
}
}
//solution2:同步方法(其实就是语法的改变)
class Ticket2 implements Runnable {
private int count = 10;
// 因为不需要语法synchronized代码块去定一个锁对象的格式,直接就用synchronized方法也可以做到锁的这个功能
@Override
/**
* task
*/
public void run() {
// 如果有另一个锁,其它锁用不了,因为之前这个锁已经锁上了
// synchronized (this){
// //...
// }
while (true) {
boolean tag = sale();
if (!tag) {
break;
}
}
}
/**
* 同理,你再创建一个n个锁
* 只有一个能实行,其它锁都不行
* 相当于n个房间只有一个door
* 那你肯定是第一个进去锁了,其它用也用不了
* @return
*/
//public synchronized boolean sale2() {return true;}
/**
* solution2 同步方法
* @return
*/
public synchronized boolean sale() { //boolean是为了让while能够break 不至于死循环
//排队,就像试衣间一把锁轮流开锁解锁这样
//写英文了,锻炼一下哈
// this - > run2 (in this case) this is the lock
// if this method is static, then the lock itself is the class (Ticket2) which implements this method
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "卖票成功 余票:" + count);
return true; // 票还有
}
//票卖完了
return false;
}
}
好了就先粗略地写到这,写多了你们看久也烦。下期除了刚刚说的要说一下Thread线程和自定义Thread,开始进入线程池和lambda再去简化这个匿名内部类的知识点了。
感谢阅读~