多线程:
高可用、高性能、高并发
juc
一.概念
线程 独立的执行路径
多线程 开辟了多条路径
Process与Thread的区别:
Process: 作为资源分配的单位;每个进程都有独立的代码和数据空间,进程的切换会有较大的开销;
在操作系统中能同时运行多个任务,系统在运行时会为不同的进程分配不同的内存区域;
没有线程的进程可视为单线程,如果一个进程拥有多个线程,则执行过程不是一条线的,而是由多线程共同完成
Thread: 调度和执行的单位;线程可视为轻量级的进程,线程共享堆和方法区,每个线程拥有独立的栈、本地方法栈和程序计数器,线程的切换开销小
除了CPU之外,不会为线程分配内存,线程组只是共享资源
线程是进程的一部分,所以线程又被称为轻权进程
注意:很多多线程是模拟出的,真正的多线程是指多个CPU,即多核,如服务器
在程序运行时,即使没有自己创建线程,后台也会存在多个线程,如GC线程、主线程
main()称为主线程,作为系统的入口点,用于执行整个程序
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序时不能人为干预
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
线程会带来额外的开销,如CPU调度时间,并发控制开销
每个线程在自己的工作内存交互,加载和存储主内存控制不当会造成数据不一致
二.继承Thread
1.创建线程的三种方法
继承Thread类
实现Runnable接口
实现Callable接口(juc包下)
2.案例
public class StartThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("唱歌");
}
}
public static void main(String[] args) {
new StartThread().start(); //不保证立即运行,由CPU调度
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("跳舞");
}
}
}
查看start(),调用本地方法

public class StartRun implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("唱歌");
}
}
public static void main(String[] args) {
new Thread(new StartRun()).start(); //不保证立即运行,由CPU调度
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("跳舞");
}
}
}
public class Web12306 implements Runnable {
/*共享资源 线程并发*/
private int num=99; //99张票
@Override
public void run() {
while (true) {
if(num<=0){
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到" + num--);
}
}
public static void main(String[] args) {
Web12306 web12306 = new Web12306(); //同一份资源
/*多个代理*/
new Thread(web12306,"小明").start();
new Thread(web12306,"小红").start();
new Thread(web12306,"校长").start();
}
}
线程同步
1.问题
当使用多个线程去访问同一资源,且多个线程对资源有写的操作时,容易出现线程安全问题
2.方案
1.同步代码块
2.同步方法
3.锁机制
3.同步代码块
1.格式:
synchronized(锁对象){
可能会出现线程安全问题的代码块(访问了共享数据的代码)
}
注意:
1.通过代码块的锁对象,可以使用任意的对象
2.但必须保证多个线程使用的锁对象是同一个
3.锁对象作用:只让一个线程在同步代码块中执行
同步技术原理:
使用一个锁对象(同步锁 对象监视器),多个线程一起抢夺CPU的执行权时, 谁抢到谁执行run();A线程抢到CPU的执行权,执行run(),遇到synchronized代码块
这时会检测synchronized代码块是否存在有锁对象,发现有,立即获取锁对象,然后进入同步中执行;B线程抢到CPU的执行权,遇到synchronized代码块,检查synchronized代码块是否有锁对象,发现没有
线程B进入阻塞状态,会一直等待A线程归还锁对象,直到A线程执行完同步代码,才会归还锁对象给同步代码块,B线程才能执行
同步保证只有一个线程在同步中执行共享数据,保证了安全,程序频繁的判断锁,获取锁,程序的效率会降低
4.同步方法
使用步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.把方法添加synchronized修饰符
锁对象 this
静态同步方法(静态方法优于对象,锁对象是本类的class属性)
5.使用Lock锁
Lock接口 lock()获取锁 unlock()释放锁
ReentrantLock implements Lock接口
使用步骤:
在成员位置创建ReentrantLock对象
在可能会出现安全问题的代码前调用Lock接口中的lock()
在可能会出现安全问题的代码后调用Lock接口中的unlock()
```java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class RunnableImpl implements Runnable {
private int ticket = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "卖出第" + ticket + "张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); //释放锁
}
}
}
}
public static void main(String[] args) {
RunnableImpl runnable = new RunnableImpl();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
线程状态
1.新建状态 new
2.运行状态 start() 获取到CPU执行权
3.阻塞状态 没有CPU执行权
4.死亡状态 run()结束 stop()
5.休眠状态 sleep(时间) wait(时间)
6.无限等待状态 Object.wait()
等待唤醒:线程之间的通信
重点:有效的利用资源
进入到Timewaiting(计时等待)有两种方式
sleep(long m) 在毫毛值结束之后,线程睡醒进入到Runnable/Blocked状态
wait(long m)
唤醒的方法
notify()
notifyAll()
wait方法和notify方法必须由同一个锁对象调用。对应的锁对象可通过notify唤醒使用同一锁对象调用的wait方法后的线程
wait方法和notify方法属于Object类的方法,锁对象可以是任意对象
wait方法和notify方法必须在同步代码块或同步函数中使用
10万+

被折叠的 条评论
为什么被折叠?



