多线程——Java
一、线程与进程
1.什么是线程?
线程是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少
有一个线程。
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分
成若干个线程。
2.什么是进程
进程是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
二、同步与异步
同步: 排队执行 , 效率低但是安全.
异步: 同时执行 , 效率高但是数据不安全.
三、线程调度
1.分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
2.抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),
Java使用的为
3.抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,
只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时
刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使
用率更高
四、获取线城名称
打印线程的名称
五、线程的休眠
sleep
单位(毫秒)
六、线程的安全
6.1 线程安全问题的展示
既然是线程安全问题,那么毫无疑问,所有的隐患都是在多个线程访问的情况下产生的,也就是我们要确保在多条线程访问的时候,我们的程序还能按照我们预期的行为去执行,我们看一下下面的代码。
public class Demo2 {
static int count = 0;
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (count<50) {
count++;
System.out.println(Thread.currentThread().getName()+" "+count);
}
});
Thread r = new Thread(() -> {
while (count<50) {
count++;
System.out.println(Thread.currentThread().getName()+" "+count);
}
});
Thread q = new Thread(() -> {
while (count<50) {
count++;
System.out.println(Thread.currentThread().getName()+" "+count);
}
});
t.start();
r.start();
q.start();
}
}
对于同一个数据count我们开启了三个线程,观察以下结果~
这里我们可以看到同时出现了两次 2
出现这种情况显然表明这个方法根本就不是线程安全的,出现这种问题的原因有很多。
最常见的一种,就是我们线程 0 在进入方法后,拿到了count的值,刚把这个值读取出来,还没有改变count的值的时候,结果线程 1 也进来的,那么导致线程 0 和线程 1 拿到的count值是一样的。
那么由此我们可以了解到,这确实不是一个线程安全的类,因为他们都需要操作这个共享的变量。其实要对线程安全问题给出一个明确的定义,还是蛮复杂的,我们根据我们这个程序来总结下什么是线程安全。
当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
搞清楚了什么是线程安全,接下来我们看看Java中确保线程安全最常用的两种方式。先来看段代码。
6.2 线程安全问题的解决方法-----上锁
6.2.1 同步代码块
关键字 synchronized
格式
synchronized( 锁对象 ) { 同步代码块 }
咱们看下面代码
public class Demo2 {
static int count = 0;
public static void main(String[] args) {
MyRunnable m = new MyRunnable();
new Thread(m).start();
new Thread(m).start();
new Thread(m).start();
}
static class MyRunnable implements Runnable{
Object o = new Object(); //创建一个锁对象
@Override
public void run() {
while (true) {
synchronized (o) { //上锁
if (count < 10) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + count);
count++;
}
} if (count>=10)
break;
}
}
}
}
运行结果如下,这就是通过同步代码块解决线程安全问题
6.2.2 同步方法
原理与同步代码块一致
把同步代码块那部分抽出来组成一个方法
咱们举个例子吧
public class Demo2 {
static int count = 0;
public static void main(String[] args) {
MyRunnable m = new MyRunnable();
new Thread(m).start();
new Thread(m).start();
new Thread(m).start();
}
static class MyRunnable implements Runnable{
Object o = new Object();
@Override
public void run() {
boolean b = true;
while (b) {
b = sale();
}
}
public synchronized boolean sale(){
if (count < 10) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + count);
count++;
}
if (count>=10)
return false;
return true;
}
}
}
6.2.3 显示锁 Lock
使用方法如下~
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo2 {
static int count = 0;
public static void main(String[] args) {
MyRunnable m = new MyRunnable();
new Thread(m).start();
new Thread(m).start();
new Thread(m).start();
}
static class MyRunnable implements Runnable{
Lock l = new ReentrantLock();
@Override
public void run() {
boolean b = true;
while (b) {
b = sale();
}
}
public boolean sale(){
l.lock();
if (count < 10) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + count);
count++;
}
if (count>=10) {
l.unlock();
return false;
}
l.unlock();
return true;
}
}
}
6.2.4 显示锁与隐式锁的区别
可以参考该文章
七、线程池 Executors
7.1 线程池的好处
降低资源消耗。
提高响应速度。
提高线程的可管理性。
7.2 Java中的四种线程池 ExecutorService
7.2.1 缓存线程池
/**
* 缓存线程池.
* (长度无限制)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程 并放入线程池, 然后使用
*/
ExecutorService service = Executors.newCachedThreadPool();
//向线程池中 加入 新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
7.2.2 定长线程池
/**
* 定长线程池.
* (长度是指定的数值)
* 执行流程:
3. 单线程线程池
4. 周期性任务定长线程池
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
7.2.3 单线程线程池
效果与定长线程池 创建时传入数值1 效果一致.
/**
* 单线程线程池.
* 执行流程:
* 1. 判断线程池 的那个线程 是否空闲
* 2. 空闲则使用
* 4. 不空闲,则等待 池中的单个线程空闲后 使用
*/
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
7.2.4 周期性任务定长线程池
public static void main(String[] args) {
/**
* 周期任务 定长线程池.
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*
* 周期性任务执行时:
* 定时执行, 当某个时机触发时, 自动执行某任务 .
*/
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 定时执行
* 参数1. runnable类型的任务
* 参数2. 时长数字
* 参数3. 时长数字的单位
*/
/*service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,TimeUnit.SECONDS);
*/
/**
* 周期执行
* 参数1. runnable类型的任务
* 参数2. 时长数字(延迟执行的时长)
* 参数3. 周期时长(每次执行的间隔时间)
* 参数4. 时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,2,TimeUnit.SECONDS);
}