文章目录
前言
例如:随着计算机的不断发展,多线程这门技术也越来越重要,并且对于java来说多线程也是必须掌握的一门技术,对于我们来说是必不可少的一、线程与进程的区别
进程:指内存中的一个应用程序,每个进程都有一个独立的内存空间,它是由多个线程组成的。 线程:指进程中的一个执行路径,共享一个内存空间,并发执行。java中使用的大多数都是抢占式调度。二、并发和并行
1、同步和异步
同步:排队执行 , 效率低但是安全。
异步:同时执行 , 效率高但是数据不安全。
2、并发和并行
并发:指的是两个或两个以上的线程在一段时间内完成的数量。
并行:指的是两个及两个以上的线程在某一时刻同时完成的数量。
三、多线程的三个使用方法
1、Thread
继承从父类中得来的Thread,但用的不多。
new Thread(new MyThread()).start();//创建一个新线程并开始。
for (int i = 0; i <5 ; i++) {//主线程开始运行
System.out.println(Thread.currentThread().getName()+" "+i);
}
//静态内部类
static class MyThread extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我爱Java" + i);
}
}
}
2、Thread常用的方法
方法 | 描述 |
---|---|
sleep(long millis) | 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。 |
setName(String name) | 将此线程的名称更改为等于参数 name 。 |
start() | 导致此线程开始执行; Java虚拟机调用此线程的run方法。 |
run() | 如果此线程是使用单独的Runnable运行对象构造的,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。 |
interrupt() | 中断此线程。 相当于做一个标记符。 |
isDaemon() | 测试此线程是否为守护程序线程。 |
getName() | 返回此线程的名称。 |
//如何获取线程的名称
System.out.println(Thread.currentThread().getName());
//两种设置线程名称的方式
Thread t = new Thread(new MyRunnable());
t.setName("wwww");
t.start();
new Thread(new MyRunnable(),"锄禾日当午").start();
//不设置的有默认的名字
new Thread(new MyRunnable()).start();
//如何中断线程
Thread t1 = new Thread(new MyRunnable());//线程运行
t1.setDaemon(true);
t1.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Thread.interrupted();
e.printStackTrace();
}
}
t1.interrupt();
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//return;
e.printStackTrace();
}
}
}
}
//线程分为守护线程和用户线程
//用户线程:当一个进程不包含任何的存活的用户线程时,进行结束
//守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
Thread t1 = new Thread(new MyRunnable());
//设置守护线程
t1.setDaemon(true);
t1.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
3、Runnable
实现接口Runnable接口,用的最多的方式
实现Runnable与继承Thread相比有如下优势
1、通过创建任务,然后给线程分配任务的方式实现多线程,更适合多个线程同时执行任务的情况
2、可以避免单继承所带来的局限性
3、任务与线程是分离的,提高了程序的健壮性
4、线程池技术,接受Runnable类型的任务,不接受Thread类型的线程
//和上面的差不多
new Thread(new MyRunnable()).start();
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
//静态内部类
static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我爱Java" + i);
}
}
}
4、Callable
它带返回值,Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
public interface Callable<V> {
V call() throws Exception
}//需要扔出异常,用call方法实现
class XXX implements Callable<T>{
@Override
public <T> call() throws Exception {
return T;
}}
//1.创建FutureTask对象 , 并传入第一步编写的Callable类对象FutureTask<Integer> future = new FutureTask<>(callable);
//2.通过Thread,启动线程newThread(future).start();
四、多线程的不安全的解决办法
1、同步代码块
data = pd.read_csv(
Runnable rn = new MyRun();
new Thread(rn).start();
new Thread(rn).start();
new Thread(rn).start();
}
static class MyRun implements Runnable{
public MyRun() {
}
private int count = 10;
private Object o = new Object();//添加一个锁,标记不能是各自有各自的锁,要不然会出现事情,
@Override//意思就是不能再run里面加标记,加的话那么没个线程都会有自己的锁了,会按照自己的锁来走
public void run() {
synchronized (o){
while (count>=0){
System.out.println(Thread.currentThread().getName() +"\n ");
try {
Thread.sleep(1000);
System.out.println("剩余票数:"+count);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
}
}
}
2、同步方法
即增加一个方法来做标记
Object o = new Object();
//线程不安全
//解决方案2 同步方法
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//总票数
private int count = 10;
@Override
public void run() {
while (true) {
boolean flag = sale();
if(!flag){
break;
}
}
}
public synchronized boolean sale(){
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);
return true;
}
return false;
}
3、显示锁
Lock l = new ReentrantLock(true);即增加一个锁住的方法和解锁的方法
public static void main(String[] args) {
Runnable rn = new MyRun();
new Thread(rn).start();
new Thread(rn).start();
new Thread(rn).start();
}
static class MyRun implements Runnable{
public MyRun() {
}
private int count = 10;
private Object o = new Object();//添加一个锁,标记不能是各自有各自的锁,要不然会出现事情,
//意思就是不能再run里面加标记,加的话那么没个线程都会有自己的锁了,会按照自己的锁来走
Lock l = new ReentrantLock(true); //true是公平锁,即锁住的时候只能有一个线程来做。
public void run() {
//synchronized (o){
while (count>=0){
l.lock();
System.out.println(Thread.currentThread().getName() +"\n ");
try {
Thread.sleep(1000);
System.out.println("剩余票数:"+count);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
l.unlock();
五、线程死锁
指两个线程及两个线程以上相互调用对方所持有的资源,由于Synchronized的特点,一个线程获得一个锁,在该线程释放这个锁之前其他的线程是获取不到中国所的,所以会一直死等下去。
如何避免
按照线程相同的顺序加锁。
或者在取锁的过程当中加一定的时限。
六、线程的六种状态
状态 | 概述 |
---|---|
Blocked | 线程的状态被堵塞 |
New | 创建未启动的线程状态 |
Runnable | 可运行的线程状态 |
Terminated | 终止的线程状态 |
Timed_Waiting | 具有等待时间的线程的状态 |
Wating | 等待线程的状态 |
七、等待与唤醒
在java.lang.Object包上,主要的几个方法
方法 | 概述 |
---|---|
wait() | 导致当前线程等待它被唤醒,通常是 通知或 中断 。 |
notify() | 唤醒正在此对象监视器上等待的单个线程。 |
notifyAll() | 唤醒等待此对象监视器的所有线程。 |
wait(long timeoutMillis) | 导致当前线程等待它被唤醒,通常是 通知或 中断 ,或者直到经过一定量的实时。 |
当一个线程调用wait()方法的时候,就会进入等待状态,直到被其他的线程用notify唤醒才开始进行工作。
八、线程池
即储存线程的容器,普通线程创建的流程:创建线程→创建任务→执行任务→关闭。
而线程池节省了创建和关闭线程池的任务。
四种线程池
(1)缓存线程池
//缓存线程池
//无限制长度
//任务加入后的执行流程
//1判断线程池是否存在空闲线程 2存在则使用 3不存在则创建线程并使用
//向线程池中加入新的任务
ExecutorService service = Executors.newCachedThreadPool();
//指挥线程池执行新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
(2)定长线程池
/*定长线程池
长度是指定的线程池
加入任务后的执行流程
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
**/
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
(3)单线程线程池
/*单线程线程池
执行流程
1 判断线程池的那个线程是否空闲
2 空闲则使用
3 不空闲则等待它空闲后再使用
**/
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
(4)周期定长线程池
/*周期任务 定长线程池
执行流程
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
周期性任务执行时
定时执行 当某个任务触发时 自动执行某任务
**/
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
//定时执行一次
//参数1:定时执行的任务
//参数2:时长数字
//参数3:2的时间单位 Timeunit的常量指定
/* scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},5, TimeUnit.SECONDS); //5秒钟后执行*/
/*
周期性执行任务
参数1:任务
参数2:延迟时长数字(第一次在执行上面时间以后)
参数3:周期时长数字(没隔多久执行一次)
参数4:时长数字的单位
* **/
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},5,1,TimeUnit.SECONDS);