进程:
进程是内存运行的应用程序,每个进程都有一个独立的内存空间。
线程:
线程是进程的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程。
线程实际上是在进程的基础上进一步划分,一个进程启动后 ,里面的若干执行路径又可以划分成若干个线程。
线程的调度
分时调度:
所有线程轮流使用CPU使用权,平均分配每个线程占用CPU时间。
抢占方式调度:
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行高速切换。对于CPU的一个核心而言,某个时刻,只能执行一个线程,而CPU在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。(单线程处理器)多线程并不能提高程序运行的速度,但能提高重新运行效率,让CPU使用率更高。
同步与异步
同步:排队执行,效率低但是安全。
异步:同时执行,效率高但是数据不安全。
并发与并行
并发:指俩个或多个事件在同一时间段发生。
并行:指俩个或多个事件在同一时刻发生(同时发生)。
Thread类
线程:
类型:守护线程,用户线程。
- 守护线程:守护用户线程,当最后一个用户线程结束后,所有守护线程自动死亡。setDaemon(true)。(人在塔在)。
- 用户线程:当进程没有存活的用户线程时结束。
Thread类
构造方法:
- Thread()分配新的Thread对象。
- Thread(Runnable target)。
- Thread(Runnable target,String name)
静态方法:
- sleep()睡眠时间毫秒。
- currentThread()获取当前运行的线程对象。
成员方法:
- .interrupt()让线程抛出异常处理语句。
- 在处理语句中return可以结束线程。
重写run方法,run方法是此线程需要执行的逻辑。在main方法创建对象然后使用start()方法启动线程。
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
for (int i = 0;i<10;i++) {
System.out.println(i+"嘻嘻");
}
}
public class MyThread extends Thread {
/**
* 是线程要执行的任务方法
* 通过thread对象的start()方法启动线程自动执行
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("D"+i);
}
}
}
实现Runnable接口(多采用此)
与继承Thread相比
- 通过创建任务方式,然后给线程分配方式实现多线程,更适合多个线程同时执行相同任务情况。
- 避免单继承带来的局限性。
- 任务与线程本身是分离的,提高程序的健壮性。
- 线程池,接受Runnable类型的任务,不接收Thread类型。
MyRunnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
t.start();
for (int i = 0;i<10;i++) {
System.out.println(i+"嘻嘻");
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("D"+i);
}
}
}
线程安全与不安全
不安全:多个线程去抢夺一个数据资源,修改数据,导致每个线程读取的数据不一致,造成数据错乱。
安全:
- 隐式锁:线程同步代码块-synchronized(锁对象){}---任何对象都可以成为锁对象。代表此代码块为原子操作。
while (true) {
synchronized (o) {
if (ticket>0){
System.out.println("卖票中");
ticket--;
System.out.println(Thread.currentThread().getName()+"已卖出剩余票数:"+ticket);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
- 隐式锁:Synchronized锁方法,(如果run方法在调用锁方法的上面有加锁了其他线程并不能访问锁方法)
/**
* 锁对象:此方法调用者加锁
*/
public synchronized boolean ticket() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖票中");
ticket--;
System.out.println(Thread.currentThread().getName() + "已卖出剩余票数:" + ticket);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
} else {
return false;
}
}
- 显式锁:需要新建一个Lock lock = new ReentrantLock();对象然后lock.lock()方法为加锁,lock.unlock方法为解锁。
public void run() {
while (true) {
lock.lock();
try {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖票中");
ticket--;
System.out.println(Thread.currentThread().getName() + "已卖出剩余票数:" + ticket);
Thread.sleep(1000);
} else {
break;
}
} catch (InterruptedException e) {
lock.unlock();
e.printStackTrace();
}
}
}
公平锁:
先来先到,哪个线程先到则先执行。(java实现公平锁方法---Lock l=ReentrantLock(true))
- 优点:所有的线程都能得到资源,不会饿死在队列中。
- 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
非公平锁(java):
抢占锁。
- 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
- 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。