文章目录
多线程
1.程序、进程、线程区分:
程序:为了完成特定任务,用某种语言编写的一组指令的集合,之一段静态的代码
进程:正在运行的一个程序。(他是作为资源分配的单位,系统会为不同的进程分配不同的内存空间)
线程:一个程序内部的一条执行路径。
2.jvm内存结构
进程可以细化为多个线程:
每个线程,拥有自己独立的:栈、程序计数器。
多个线程,共享同一个进程中的结构:方法区、堆。
3.一个java程序最少有3个线程:mian()主线程、gc()垃圾回收线程、异常处理线程。
4.并行与并发(理解就好)
5.创建多线程的方式(4种)
方式一:继承Thread类
1>创建一个继承Thread的子类
2>重写run方法
3>创建Thread类的子类对象
4>通过此对象调用start()方法:①启动当前线程 ②调用run()方法【因此不能通过run启动线程,只能通过start()方法】
方式二:通过实现Runnable接口的类
1>创建一个Runnable接口类。
2>实现Runnable中的抽象方法run()
3>创建此实现类的对象
4>将此对象作为Thread类的参数传递到Thread类的构造器中,创建Thread类的对象
5>通过Thread类的对象调用start()方法
此方法中是怎样调用到实现类中的run方法的呢?
针对此问题,可以阅读一下Thread中相应的源码
Thread类源码中run()的源码是这样的:
public void run() {
if (target != null) {
target.run();
}
}
可以看到,运行run方法时先判断target,若不为空执行target中的run()
target在源码中是Runnable类型,其对应我们创建的实现类
private Runnable target;
同时Thread中还有以下构造方法
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
如此这般,运行了实现类中的run()也就说得通了
上述两种方式对比
开发中:优先选择实现Runnable接口类的方法
原因:1.实现的方式没有单继承的局限性
2.能更好的处理多个线程数据共享的情况。
联系:Thread类也实现了Runnable接口
相同点:都要重写Run(),将线程要执行的逻辑声明在run()中
启动线程都需要调用Thread的start()方法。
方式三:实现callable接口——>JDK5.0新增
实现代码:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class NumberThread implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i%2==0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
sum += i;
}
}
return sum;
}
}
public class CallableTest {
public static void main(String[] args) {
NumberThread number = new NumberThread();
//若要创建多个线程,也需要声明多个FutureTask
FutureTask futureTask = new FutureTask(number);
FutureTask futureTask2 = new FutureTask(number);
FutureTask futureTask3 = new FutureTask(number);
Thread t1 = new Thread(futureTask,"线程一");
Thread t2 = new Thread(futureTask2,"线程二");
Thread t3 = new Thread(futureTask3,"线程三");
t2.start();
t3.start();
t1.start();
Object sum = null;
try {
sum = futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("总和为" + sum);
}
}
若要创建多个线程,也需要声明多个FutureTask,作为参数传入不同线程中。futureTask相当于一个特殊的runnable,作为参数传入Thread。
Future接口,可以对具体的Runnable、Callable任务的执行结果进行取消、查询是否完成、返回结果等
FutureTask是Future的唯一实现类,他同时实现了Runnable和Future接口,既可以作为Runnable被线程调用,又可以作为Future返回Callable的返回值
面试题:如何理解Callable比Runnable强大
1.call()可以有返回值
2.call()可以抛出异常
3.Callable支持泛型
方式四:使用线程池
先上代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class OddNum implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class EvenNum implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(15);
service.execute(new OddNum());//接受runnable,犯过的错误:接收的参数应该为实例,不是类
service.execute(new EvenNum());
// service.submit();接收callable
service.shutdown();
}
}
使用线程池的好处:
1.提高响应速度(减少了创建线程的时间)
2.降低了资源消耗(重复利用线程池资源不用每次都创建)
3.便于线程管理(通过设置如下属性)
corePoolSize:核心池大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多维持多长时间后会终止
线程管理的方式:
ExecutorService service = Executors.newFixedThreadPool(15);
ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
service1.setCorePoolSize(10);
service1.setMaximumPoolSize(5);
多态的应用,管理设置线程池的方法要对前面的service进行强转,变成service1才能用子类特有的方法
6.Thread中常用的方法:
start():启动当前线程,调用当前线程中的run()
run():通常需要重写,将线程所需执行的操作声明再次方法内
currentThread():静态方法,返回当前执行代码的线程
getName()
setName()
yield():释放当前cpu的执行权
join():在a中调用b.join(),a陷入阻塞状态,待b完全执行完毕,a结束阻塞状态
stop():该方法已过时。用于终止线程
sleep(long millitime):在指定的时间(单位毫秒)内,线程程阻塞状态
isAlive():判断线程是否存活
线程的优先级:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
getPriority():获取线程优先级
setPriority():设置优先级
优先级只表示概率不意味着高优先级一定比低优先级先执行
**线程的通信:**wait(),notify(),notifyAll():这三个方法动议在Object类中
7.线程的分类
守护线程和用户线程(前者服务后者),垃圾回收就是以恶搞守护线程,若JVM中都是守护线程,JVM退出
8.线程的生命周期
线程最终状态都是死亡,阻塞只是一个临时状态,不可做为最终状态。
9.线程的同步机制(三个方式)
方式一:同步代码块
操作共享数据的代码
同步监视器:俗称锁,任何类都可以充当
在实现Runnable接口创建的多线程中,可以考虑使用this充当锁
在继承创建的多线程中,慎用this,可以考虑使用当前类
synchronized(同步监视器){
需要被同步的代码
}
方式二:同步方法
不需要显式声明同步监视器
对于非静态同步方法:同步监视器式this
对于静态的同步方法:锁为当前本身
方式三:lock锁 ——> JDK5.0新增
private Reentrantlock lock= new Reentrantlock();
lock.lock();
.
.
.
lock.unlock();
面试题:synchronized和lock的异同
同:都可解决线程安全问题
异:synchronized在执行完同步代码后,自动释放锁;lock需要手动lock(),unlock()
操作同步代码时,相当于单线程
10.线程安全的单例模式(懒汉式):
懒汉式单例模型:
public class ThreadSafeSingletonModle {
private ThreadSafeSingletonModle() {
}
private static ThreadSafeSingletonModle instance = null;
public static ThreadSafeSingletonModle getInstance() {
if (instance == null) {
instance = new ThreadSafeSingletonModle();
}
return instance;
}
}
改进为线程安全的,两种方式的分析都在代码注释中
public class ThreadSafeSingletonModle {
private ThreadSafeSingletonModle() {
}
private static ThreadSafeSingletonModle instance = null;
public static ThreadSafeSingletonModle getInstance() {
//第一种方式效率低,原因第一个线程创建完实例后,后面线程其实都无需等待
// synchronized (ThreadSafeSingletonModle.class) {
// if (instance == null) {
// instance = new ThreadSafeSingletonModle();
// }
// }
// return instance;
// }
//第二种方式就是在第一种方式前面在包一层if判断,这样除了前面极少数进入的线程外,后续其余的线程均无需等待,直接取用实例,提高效率
synchronized (ThreadSafeSingletonModle.class) {
if (instance == null) {
synchronized (ThreadSafeSingletonModle.class) {
if (instance == null) {
instance = new ThreadSafeSingletonModle();
}
}
}
return instance;
}
}
}
11.死锁
不同资源分别占用对方同步资源不放,都在等待对方放弃自己需要的同步资源
不会释放锁的操作:sleep(),yield(),suspend()挂起,尽量避免用suspend()和resume()来控制线程
12.线程通信
涉及到的三个方法:wait(),notify(),notifyAll()
这三个方法都定义在Object类中,都必须使用在同步方法或同步代码块当中
三个方法调用者都必须是同步代码块或同步方法中的同步监视器,否则,会出现IllegalMonitorStateException异常
++面试题:sleep()和wait()的异同++
同:都能是线程变成阻塞状态
异:1.两个方法声明的位置不同,sleep()声明在Thread中,wait()声明在Object类中
2.sleep可以在任何需要的场景中调用,wait需要使用在同步代码块或同步方法中
3.如果两个方法都使用在同步代码块和同步方法中,sleep()不会释放锁,wait()会释放锁。