目录
线程
线程的介绍
进程:进程是程序的执行,比如QQ打开了,就是一个进程;微信打开了,也是一个进程
线程:线程是依附于进程的,进程可以启动线程,一个进程可以有多个线程;比如QQ同时跟多人聊天;迅雷同时下载多个不同的文件,每个下载都是一个线程;如果主线程结束了,但是还有其他线程,那么进程也不会结束;
并发:并发是指一个cpu在一个时间片里面轮换的执行进程,假的同时,实际上是一个执行一会儿。
并行:并行是多核cpu在同时间执行不同的任务,真正的同时。
问题1:为什么不直接调用Run方法?
因为run方法实际上就是一个普通方法,并没有真正的启动线程,就会把这个run方法执行完毕才会执行后面的代码。只有调用start方法才会真正的启动一个线程,实现多线程。
问题2:为什么调用start()方法,不调用run()方法
start()方法的源码,实际上是调用start0()方法,而start0方法是一个native方法,实际上是JVM来调用的;调用该方法后,该线程不一定会马上执行,只是将线程变成了可运行状态,具体什么时候执行,取决于CPU,由CPU统一执行。
线程终止
基本说明:
-
当线程完成任务后,会自动退出。
-
还可以通过使用变量来控制run方法退出的方式停止线程,即通知的方式
-
可以给线程设置一个变量,然后主线程改变这个变量,就能达到终止线程的效果
-
线程中断(interrupt)
Interrupt方法:
如果线程在休眠的时候,被别人调用了他的interrupt方法,则会抛出一个异常:InerruptedException中断方法,可以通过catch来写业务逻辑。
线程礼让(yield)
yield方法:
让出CPU,让其他线程先执行,但是礼让的时间不确定,所以也不一定礼让成功
线程插队(join)
join方法:
调用其他线程的join方法,自己主动放弃CUP,等待其他线程执行结束之后,再执行自己。t1线程调用t2.join,让t2执行完毕,再执行t1
线程的生命周期,6种状态
线程同步机制
同步的概念:
-
在多线程编程里面,一些敏感的数据不允许被多个线程同时访问,此时使用同步访问技术,保证数据在任何时刻最多有一个线程访问,以保证数据的完整性!!
-
也可以这样理解,例如,有一个线程在内存进行操作的时候,其他线程都不能对这个内存地址进行操作,直到该线程完成操作。就像上厕所,有一个人在上的时候,别人是不能进去上厕所的,只能等到那个人上完厕所。
同步具体的方法:Synchronized
-
同步代码块
synchronized(对象){ }
-
synchronized放在方法的声明中,表示整个方法为同步方法
public synchronized void method(){ //需要被同步的代码 }
互斥锁(对上面的解释)
基本介绍
-
Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性.
-
每个对象都应对应一个可以称为"互斥锁"的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
-
synchronized来与对象进行互斥锁的联系,当某个对象使用synchronized修饰的时候,就表名该对象在任一时刻只能由一个线程访问。
-
同步的局限性:导致程序的执行效率降低(例如高速公路上,收费站只有一个,能同时通过的很少,所以一般堵车的地方就是收费站附近)
-
同步方法如果是非静态的,这个锁可以是this,也可以是其他对象(要求是用一个对象)
-
如果是静态的,这个锁实际上是加在当前类的
释放锁
以下操作会释放锁:
-
当线程的同步方法、同步代码块执行结束。
例如:上完厕所出来
-
当前线程在同步代码块、同步方法中遇到break、return。
例如:没有正常的完事,但是发生火灾了,不得从厕所出来
-
当前线程在同步代码块、同步方法中出现了未处理的Error或者Exception,导致异常结束。
例如:没有正常完事儿,发现忘记带纸了,不得已出来
-
当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
例如:拉不出来,先去休息一下,酝酿一下,所以出来
一下操作不会释放锁:
-
当前线程在同步代码块、同步方法时,程序调用了Thread.slepp()、Thread.yield()方法,暂停当前线程的执行,不会释放锁。
案例:上厕所,困了,在坑位上睡一会
-
线程执行同步代码块时,其他线程调用了该线程的suspend()方法,将该线程挂起,该线程不会释放锁
Lock锁
简介:Lock是一个接口,是java.util.concurrent.locks包下面的,它是控制对个线程对共享资源访问的工具。锁提供了对共享资源的独占访问,就是只允许一个线程去访问。每次只能由一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable {
//定义Lock锁
private final ReentrantLock lock = new ReentrantLock();
int ticketNum = 10;
@Override
public void run() {
while (true) {
try {
lock.lock();//这里就是加锁
if (ticketNum > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() +" | "+ ticketNum--);
}else {
break;
}
} finally {
lock.unlock();//释放锁
}
}
}
}
synchronized 与 Lock 的对比
-
Lock是显式锁(手动开启和关闭锁) synchronized是隐式的锁,出了作用域自动释放
-
Lock只有代码块,synchronized有代码块锁和方法锁
-
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且有更好的拓展性(提供更多的子类)
-
优先顺序
Lock > 同步代码块 > 同步方法
线程通讯
问题引入:生产者和消费者问题,生产者生产完毕之后,要提醒消费者来取,消费者取完之后要通知生产者生产,仅仅用synchronized锁是无法实现的,所以需要了解线程通讯的机制。
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
wait(long time) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度 |
/**
* 测试生产者,消费者模型
* 利用缓冲区解决:管程法
* //生产者,消费者,产品,缓冲区
*/
public class TestPC {
public static void main(String[] args) {
SyncContainer container = new SyncContainer();
new Producer(container).start();
new Custom(container).start();
}
}
//生产者
class Producer extends Thread {
SyncContainer container;
public Producer(SyncContainer container){
this.container = container;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了"+i+"只鸡");
}
}
}
//消费者
class Custom extends Thread {
SyncContainer container;
public Custom(SyncContainer container){
this.container = container;
}
//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Chicken pop = container.pop();
System.out.println("消费了Id为"+pop.id+"的鸡");
}
}
}
//产品
class Chicken{
int id;//产品编号
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class SyncContainer{
//需要一个容器大小,能放十只鸡
Chicken[] chickens = new Chicken[10];
int count = 0;
//生产者放入产品
public synchronized void push(Chicken chicken){
//如果容器满了,等待消费者消费
if (count == chickens.length){
//通知消费者消费,生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,则丢入产品
chickens[count++] = chicken;
//通知消费者来消费
this.notifyAll();
}
//需要消费者消费产品
public synchronized Chicken pop(){
if (count == 0) {
//等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Chicken chicken = chickens[count];
//通知生产者
this.notifyAll();
return chicken;
}
}
线程池简介
背景:经常创建销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。
好处:
-
提高响应速度
-
降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
-
便于线程管理
-
corePoolSize:核心池的大小
-
maximumPoolSize:最大线程数
-
keepAliveTime:线程没有任务时候最多保持多长的时间后终止
-
ExecutorService:真正的线程池接口,常见子类TreadPoolExecutor
void execute(Runnable com):执行任务、命令,没有返回值,一般用来执行Rannable
<T>Future<T>submit(Callable task):执行任务,有返回值,一般用来执行Callable
void shutdown():关闭连接池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);//参数为线程池大小
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//关闭连接
service.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
代码结果:
线程基础的知识先讲到这里,后续还会继续更新高级线程相关的知识点!!