学习目标
多线程
- 线程安全
- 线程死锁
- 线程状态
- 线程通信
- 线程池
1. 多线程
1.1 多线程能解决什么问题 ?
从软件或者硬件上实现多个线程并发执行的技术,提高任务的执行性能1.2 线程的状态
<br /> 
2. 解决线程安全问题的方式有哪些 ?
2.1 同步代码块
格式:(锁的对象唯一)
synchronized(同步锁..任意对象) {
多条语句操作共享数据的代码
}
作用:使用同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证该变量的唯一性和准确性。
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,降低程序的运行效率
- 代码实现
```java
public class Ticket implements Runnable {
// 定义100张票的变量
private int ticketCount = 100;
@Override
public void run() {
} }// 模拟卖票.需要使用循环,因为我们要把100张票都卖出去
while (true) {
synchronized (Ticket.class) {
// 没有票了
if (ticketCount <= 0) {
break;// 结束死循环
} else {
// 休眠200毫秒
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 还有票 , 没卖出一张票,记录票数的变量需要自减
ticketCount--;
System.out.println(Thread.currentThread().getName() + "卖出了一张票,还剩余" + ticketCount + "张");
}
}
}
```java
public class TicketDemo {
public static void main(String[] args) {
// 创建任务类对象
Ticket ticket = new Ticket();
// 创建三个线程类对象
Thread t1 = new Thread(ticket,"窗口1");
Thread t2 = new Thread(ticket,"窗口2");
Thread t3 = new Thread(ticket,"窗口3");
// 开启三个线程
t1.start();
t2.start();
t3.start();
}
}
2.2 同步方法
- 定义:把synchronized关键字加到方法上
- 格式:修饰符 synchronized 返回值类型 方法名(方法参数) { }
- 同步代码块与同步方法的区别:
- 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
- 同步代码块可以指定锁对象,同步方法不能指定锁对象
- 注意:同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的
- 对于非static方法,同步锁就是this
- 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。 Class类型的对象
代码实现: ```java public class Ticket implements Runnable { // 定义100张票的变量 private int ticketCount = 100;
@Override public void run() {
// 模拟卖票.需要使用循环,因为我们要把100张票都卖出去
while (true) {
if (method()) {
break;// 结束死循环
}
}
} //同步方法 private synchronized boolean method() {
if (ticketCount <= 0) {
return true;
} else {
// 休眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 还有票 , 每卖出一张票,记录票数的变量需要自减
ticketCount--;
System.out.println(Thread.currentThread().getName() + "卖出了一张票,还剩余" + ticketCount + "张");
}
return false;
}
<a name="xFclC"></a>
### 2.3 锁机制,Lock锁
- **方法:**
- void lock():获得锁
- void unlock():释放锁
- **实例化:**ReentrantLock():创建一个ReentrantLock的实例
- **代码实现:**
```java
public class Ticket implements Runnable {
// 定义100张票的变量
private int ticketCount = 100;
//Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
//创建Lock锁对象
private static final ReentrantLock l = new ReentrantLock();
@Override
public void run() {
// 模拟卖票.需要使用循环,因为我们要把100张票都卖出去
while (true) {
l.lock();//获得锁
try {
if (ticketCount <= 0) {
break;//没有票了, 结束死循环
} else {
// 休眠200毫秒
Thread.sleep(100);
// 还有票 , 没卖出一张票,记录票数的变量需要自减
ticketCount--;
System.out.println(Thread.currentThread().getName() + "卖出了一张票,还剩余" + ticketCount + "张");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
l.unlock();//释放锁
}
}
}
}
3.线程池
3.1 线程池存在的优势 ?
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
- 提高响应速度。当任务到达时,任务可以不需要等待线程创建 , 就能立即执行
提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存 (每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机
3.2 线程池能处理什么任务 ?
Callable和Runnable的不同点
- Callable支持结果返回,Runnable不行
-
3.2.1线程池处理Runnable任务
public class Test1 {
public static void main(String[] args) {
//1.创建线程池指定线程开启的数量,通过类名调用静态方法获取线程池对象
ExecutorService threadPool = Executors.newFixedThreadPool(3);
//2.提交任务给线程池,传入Runnable接口的实现类对象
threadPool.submit(new Student("老大"));
threadPool.submit(new Student("老二"));
threadPool.submit(new Student("老三"));
threadPool.submit(new Student("老四"));
threadPool.submit(new Student("老五"));
threadPool.shutdown();//关闭线程,一般不会关闭
}
}
//Runnable的实现类(任务类)
class Student implements Runnable{
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"教"+name+"学习游泳");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"学习完毕!!");
}
}
3.2.2线程池处理Callable任务
```java public class Test2 { public static void main(String[] args) throws ExecutionException, InterruptedException { //1.获取线程池对象 ExecutorService threadPool = Executors.newFixedThreadPool(5); //2.给线程池提交任务 Future
future = threadPool.submit(new CalculateTask(10)); Integer sum = future.get(); System.out.println(sum); //threadPool.shutdown(); } }
class CalculateTask implements Callable
public CalculateTask(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 1; i <= num; i++) {
sum+=i;
}
return sum;
}
}
<a name="h2twA"></a>
## 4. 线程间的通信
<a name="QNaHZ"></a>
#### 4.1 线程间的通信能解决什么问题?
线程间的通讯技术就是通过等待和唤醒机制,来实现多个线程协同操作完成某一项任务。等待唤醒机制其实就是让线程进入等待状态或者让线程从等待状态中唤醒。
<a name="Ygcff"></a>
#### 4.2 方法使用
- **等待方法**
- void wait() 让线程进入无限等待
- void wait(long timeout) 让线程进入计时等待
- 以上两个方法调用会导致当前线程释放掉锁资源
- **唤醒方法**
- void notify() 唤醒在此对象监视器(锁对象)上等待的单个线程
- void notifyAll() 唤醒在此对象监视器上等待的所有线程
- 以上两个方法调用不会导致当前线程释放掉锁资源
- **注意**
- 等待和唤醒的方法,都要使用锁对象调用(需要在同步代码块中调用)
- 等待和唤醒方法应该使用相同的锁对象调用
<a name="GSzqJ"></a>
#### 4.3 代码实现
```java
public class Test4 {
static boolean flag=true;
public static void main(String[] args) {
final Object lock = new Object();
//开启线程A
new Thread(() -> {
while (true) {
//加锁,使线程执行完毕
synchronized (lock) {
if (flag) {
System.out.print("传");
System.out.print("智");
System.out.print("教");
System.out.println("育");
flag=false;
lock.notify();//A线程执行完毕把对方唤醒
} else {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}//synchronized执行结束,释放锁
}
}, "A").start();
//开启线程B
new Thread(() -> {
while (true) {
synchronized (lock) {
if (!flag) {
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.println("员");
flag=true;
lock.notify();//
} else {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "B").start();
}
}
5.线程的死锁
- 原因: 同步代码块的锁进行嵌套使用 , 就会大概率产生死锁
- 存在条件:
- 多个线程
- 存在锁对象的循环依赖