多线程
概念:一个程序中可以同时运行多个线程来执行不同的任务。
什么时候需要多线程?
-
程序中需要同时执行两个或多个任务时;
-
程序中需要实现需要等待的任务;例如:搜索、用户输入
多线程的优点
-
提高了程序的响应速度
-
提高了CPU的利用率
-
可以将复杂的任务分为多个线程同时进行,改善程序结构
多线程的缺点
-
线程也是程序,所以线程需要占用内存,线程越多占用的内存也就越多;
-
多线程需要协调和管理,所以需要CPU时间跟踪线程;
注:以上两个问题可以通过提升硬件配置解决
-
线程之间对共享资源的访问会互相影响;
该问题出现的条件也较为苛刻:首先是在多线程的条件下,其次多线程需要操作同一个资源(变量);
例如:卖票、取款
线程同步
并行:多个CPU同时执行多个任务;
并发:在一个时间段内依次执行操作;例如:卖票、秒杀看似同时进行,实际上是依次执行的。
多线程同步
多个线程访问同一个共享数据时,如果不加以控制,在理论上可能会出现问题。
解决办法:加锁(synchronized)
-
继承Tread类创建线程
-
实现Runnable接口创建线程
可以使用两种方式来加锁(模拟了卖票的机制):
-
使用synchronized关键字来修饰一段代码块
synchronized(锁对象 注:必须多个线程对应同一个对象){
作用:
-
使用该对象来记录有没有线程进入到同步代码块中
-
使用该对象的对象头中的一块空间来记录锁的状态
}
static int num = 10;//static修饰后,变成了静态变量,内存中就只有一个了,所以多个线程就可以共用一个 static String s = new String();//用static修饰,确保多个线程对应一个对象即可 @Override public void run() { while (true) { synchronized (s) { if (num > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "买到了" + num--); } else { break; } } } }
-
synchronized修饰静态方法,此时锁对象变为了该类的Class类对象(只有一个)
注:当一个类用static修饰后被使用时,就会被加载到方法区中,它会为每个类创建一个class类的对象,来表示此类的信息;
synchronized修饰非静态方法时,锁对象就是this,有可能会有多个this,无法保证多个线程对应同一个对象。
static int num = 10;//static修饰后,变成了静态变量,内存中就只有一个了,所以多个线程就可以共用一个 @Override public void run() { while (true) { if (num <= 0) { break; } this. printticket(); } } public synchronized void printticket() { if (num > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "买到了" + num--); } }
-
同步执行的过程
-
第一个线程访问,锁定同步对象,执行其中代码.
-
第二个线程访问,发现同步对象被锁定,无法访问.
-
第一个线程访问完毕,解锁同步对象.
-
第二个线程访问,发现同步对象没有锁,然后锁定并访问.
Lock锁
除了使用synchronized关键字来加锁之外,也可以使用Lock对象来加锁;
ReentrantLock类中实现了Lock
同样采样卖票的机制来演示:
static int num = 10;//static修饰后,变成了静态变量,内存中就只有一个了,所以多个线程就可以共用一个
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try{
lock.lock();//加锁
if (num > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "买到了" + num--);
} else {
break;
}
}finally{
lock.unlock();//释放锁,线程执行过程中无论发生什么,都要执行此代码,来释放锁
}
}
}
synchronized与Lock的区别
-
底层实现不同
synchronized是关键字,底层实现不依赖与Java代码,依赖于底层指令
Lock锁底层实现完全是Java代码控制的
-
用法不同
synchronized既可以修饰代码块,也可以修饰方法
Lock锁只能对某一段代码进行修饰
-
加锁与释放锁的方式不同
synchronized加锁和释放锁都是隐式的;
Lock加锁(lock.lock();//加锁)和释放锁( lock.unlock();)都是显示的。
线程死锁
多个线程分别占用对方需要的同步对象(锁对象)不放手,此时程序不会报错也不会提示,相互等待对方释放锁的情况就称为死锁。
为避免程序出现死锁,在程序设计时要考虑锁的顺序,尽量减少嵌套加锁。
线程通信
线程通信指的是多个线程之间相互牵制,相互调度
所涉及到的方法:
-
wait():在Object类中定义的,表示让线程等待,必须通过notify()进行唤醒,线程等待时,可以自动释放锁对象;
-
notify():在Object()中定义的,唤醒等待状态的线程
-
notifyAll():唤醒所有等待状态的线程;
注:以上3个方法,必须在同步代码块/同步方法中进行,否则会报异常。