目录
一、基本概念
1、进程:
2、线程:
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。一个进程最少有一个线程。
- 线程是在进程基础上的进一步划分,一个进程启动后,里面的若干执行路径又可以划分为若干线程。
- 当一个应用程序一个执行的线程都没有的时候,程序就会被关闭。
3、线程调度
3.1、分时调度
所有线程轮流使用CPU使用权,平均分配每个线程占用CPU的时间。
3.2、抢占式调度
优先让优先级高的线程使用CPU,如果线程优先级相同,随机选择一个线程(线程随机性),java中使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程之间进行高速切换。对CPU的一个核心而言,某个时刻只能执行一个线程。而CPU在多个线程之间的切换速度相对于我们的感觉要快,看上去就像是在同一时刻运行。多线程并不能提高程序的运行速度,但是能提高程序的运行效率,让CPU的使用效率更高。
3.3、数据库服务器响应
上千个应用程序需要使用数据库服务器,服务器八个核心,程序访问时排队效率大于同时使用。
因为如果同时使用,那么每个程序每次只能执行1%或者更少,这样来回切换的次数可能就是1000*100=10W次;而如果采用排队,那么只需要切换1000次。
4、同步与异步
5、并发与并行
二、线程使用
1、Thread构造方法与定义
public class Thread extends Object implements Runnable{}
Thread(Runnable target,String name)
Thread.currentThread().getName(); //获取线程名称
Thread.currentThread(); //获取当前正在执行代码的线程对象。
2、内存结构
由一个线程所执行的方法,这个方法会执行在线程自己的栈内存中。
4、继承Thread
继承Thread的子类就是一个线程类,需要重写方法:run()。
- run方法中就是线程要执行的任务,里面的代码就是一条新的执行路径。
- 执行方法不能直接调用run,而是通过Thread实例化对象的start()来启动任务。
- 最开始执行的线程是主线程,后来创建的都叫做分支线程
5、实现接口Runnable
- Runnable,实现接口中的方法run()
- runnable,即创建一个任务。
- Thread类,调用有参构造方法将runnable对象传入;即创建一个线程,并为其分配任务。
- thread的start()方法,即启动线程。
6、实现Runnable与继承Thread
- 通过创建任务,给线程分配的方式来实现多线程,适合多个线程来执行相同任务的情况。
- 可以避免单继承带来的局限性。
- 任务与线程本身是分离的,提高了程序的健壮性。
- 后续的线程池技术接受Runnable类型的任务,不接受Thread类型的线程。
7、线程休眠 \ 阻塞 \ 中断
1、线程休眠
static void sleep(long millis)方法,休眠暂停;
2、线程阻塞:
3、线程中断
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。
Runnable不能进行异常抛出,只能try...catch处理。
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(new MyRunnable());
t1.start();
for (int i=0;i<5;i++){
System.out.println("你快点开始啊:"+i);
Thread.sleep(500);
}
t1.interrupt();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("我要开始了哈:"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// System.out.println("你要干啥,我还不想消亡呢");
return;
}
}
}
}
8、线程状态
9、守护线程和用户线程
守护线程,守护用户线程,当最后一个用户线程结束时,所有守护线程自动消亡;
三、线程安全和不安全
public class Test2 {
public static void main(String[] args) {
Runnable tick=new Tick();
new Thread(tick).start();
new Thread(tick).start();
new Thread(tick).start();
}
}
class Tick implements Runnable{
private int count=10;
@Override
public void run() {
while (count>0){
System.out.println("开始准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("余票还剩:"+count);
}
}
}
三种加锁机制:
- 同步代码块 synchronized
- 同步方法
- 显示锁Lock
1、同步代码块 synchronized
所有排队线程都需要使用相同的一个锁对象,不能每一个线程都有一个锁对象,这样就不能实现排队效果了。
class Tick implements Runnable{
private int count=10;
private Object obj=new Object();
@Override
public void run() {
while (true){
synchronized (obj){
if(count<=0){
break;
}
System.out.println("开始准备卖票");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"卖票,余票还剩:"+count);
}
}
}
}
2、同步方法
锁对象:非静态方法,是 this; 静态方法,是 类名.class
如果是不同的任务,就不会上锁,因为每个任务的this是不一样的。
同一个任务中有多个同步方法执行,那么其中一个方法执行,其他任何方法都执行不了。
3、显示锁Lock
有参构造方法中,ReentrantLock(true) 表示公平锁,默认是false不公平锁。
4、显示锁和隐式锁的区别
5、公平锁和不公平锁
6、线程死锁
解决办法:在任何有可能导致锁产生的方法里,不要调用另外一个方法,让另一个锁产生;
即一个方法目前已经产生锁了,不要再去使用其他任何有可能导致锁的方法。
public static void main(String[] args) {
Customer customer = new Customer();
Shopkeeper shopkeeper = new Shopkeeper();
new TestThread(customer, shopkeeper).start();
customer.say(shopkeeper);
}
static class TestThread extends Thread {
private Customer customer;
private Shopkeeper shopkeeper;
public TestThread(Customer customer, Shopkeeper shopkeeper) {
this.customer = customer;
this.shopkeeper = shopkeeper;
}
@Override
public void run() {
shopkeeper.say(customer);
}
}
//顾客
static class Customer {
public synchronized void say(Shopkeeper shopkeeper) {
System.out.println("顾客:你先给我货,我给你钱");
shopkeeper.respond();
}
public synchronized void respond() {
System.out.println("顾客:我拿到了货,你拿到了钱");
}
}
//店老板
static class Shopkeeper {
public synchronized void say(Customer customer) {
System.out.println("店老板:你先给我钱,我给你货");
customer.respond();
}
public synchronized void respond() {
System.out.println("店老板:我拿到了钱,你拿到了货");
}
}
四、多线程通信
1、wait()等待、notify()唤醒
2、经典案例:生产者与消费者问题
生产者在生产时,消费者没有消费;消费者在消费时,生产者没有生产;
- 当不存在同步方法设置,也不存在等待唤醒机制的时候,数据会出现错乱。因为生产者还没生产成功,消费者就来消费数据,数据是错误的。
- 当只有同步方法设置,没有等待唤醒机制的时候,会出现一直消费或者一直生产的情况。因为同步方法是不公平锁,会出现争抢,可能出现某一时间段一直消费或者一直生产情况。
- 设置了同步方法,也设置了等待唤醒机制的时候,生产者执行完成后,会唤醒this对象上等待的消费者,同时使自己处于等待状态;同样的,消费者消费完成后,会唤醒this对象上等待的生产者,同时使自己处于等待状态;这样保证了一次生产,一次消费有序进行。
/**
* 生产者与消费者问题
* @param args
*/
public static void main(String[] args) {
Food food=new Food();
new Thread(new Cook(food)).start();
new Thread(new Waiter(food)).start();
}
/**
* 厨师线程,生产食物
*/
static class Cook implements Runnable{
private Food food;
public Cook(Food food){
this.food=food;
}
@Override
public void run() {
//生成两种食物
for (int i=0;i<10;i++){
if(i%2==0){
food.setNameAndTaste("青椒肉丝","酸甜味");
}else{
food.setNameAndTaste("毛血旺","麻辣味");
}
}
}
}
/**
* 服务员线程,消费食物
*/
static class Waiter implements Runnable{
private Food food;
public Waiter(Food food){
this.food=food;
}
@Override
public void run() {
for (int i=0;i<10;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
food.get();
}
}
}
/**
* 食物
*/
static class Food{
//名称
private String name;
//口味
private String taste;
//flag true:生产 false:消费
private boolean flag=true;
/**
* 设置食物名称和口味
* @param name
* @param taste
*/
public synchronized void setNameAndTaste(String name,String taste){
if(flag){
this.name=name;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste=taste;
//1、生产结束,改变标志量
flag=false;
//2、唤起消费进程
this.notify();
//3、生产进程等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get(){
if(!flag){
System.out.println("服务生端走了食物:"+name+",口味:"+taste);
//1、更新标志量
flag=true;
//2、唤起生产线程
this.notify();
//3、消费线程等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
五、Runnable和Callable
Callable:主线程等待返回值,可以安排上千个线程去执行,主线程等待拿结果。
1、接口定义
2、Callable使用步骤
Class XXX implements Callable<T>{
public <T> call() throws Exception{
2、创建FutureTask对象,并传入第一步编写的类对象callable:
FutureTask<Integer> future=new FutureTask<>(callable);
3、Runnable和Callable的异同:
相同点:
- 都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动程序
不同点:
- Runnable没有返回值;Callable可以返回执行结果
- Runnable的run()不能抛出;Callable的call()允许抛出异常
4、Callable获取返回值
Callable支持获取返回结果,调用FutureTask.get()方法,此方法会阻塞主进程继续往下执行,如果不执行不会阻塞。