线程是相当于独立的,在线程中的也是句不变量,除非i将变量定义一在类中或者调用其他类中的方法,来实现公用。
多线程的创建:有两种方案进行创建多线程
Thread对象提供的多线程(无返回值结果void):
main方法默认是一条主线程
创建一个线程类,需要继承Thread类
必须要重写run方法,将要执行的放在run方法中。
不重写的话代表默认执行main方法
之后调用thread对象的start();来执行run方法。
功能较弱。(类是单继承)
在主线程和子线程在进行的时候是不冲突的,可以同时执行。
一般来说系统在进行执行时,是自上而下进行的。
但是在执行时系统对运行内存的分配调度有变化,导致方法体在进行执行的时候不一定谁进行的快,但main方法是主方法。如下图中System线程几是main方法中的线程执行就不一样。
注意事项:
事项一:
启动线程的时候一定还是要start方法才是多线程。
start会调用cpu令其给Thread对象分配内存
若是调用run实际上还是对象的方法。
事项二:需要把子线程放在主线程的执行体之前(即main方法的开头就要执行子线程)
继承Runnable接口实现多线程(无返回值结果void):
这个类是任务类对象。
如果需要执行该进程还是要创建Thread对象将该任务类对象放入Thread中。
使用匿名内部类创建(实际上是上述的简化形式):
先创建Runnable的匿名内部类对象
再交给Thread线程对象封装
再调用封装好的对象的start();方法启动线程
package 匿名内部类;public class test {/*** 实际上只是对Runnable创建的简化*/public static void main(String[] args) {Runnable target =new Runnable() {//实现匿名内部类的对象(匿名内部类创建时可以直接作为参数,即直接放在Thread创建时封装)@Overridepublic void run() {for (int i = 0; i <5 ; i++) {System.out.println("子线程执行中"+i);}}};new Thread(target).start();//使用lambda进行简化
new thread(() ->{fori(int i=0;i<5;i++){System.out.println("子线程输出");}
}
}).start();for (int i = 0; i <5 ; i++) {System.out.println("主线程main执行中"+i);}}
}
实现Callable接口,FutureTask类(线程任务对象),来创建多线程(可以返回执行值结果):
多线程的对象参数可以是自定义类
1.创建任务对象
定义一个Callable接口,重写call方法(线程执行体),封装要做的事情,和要返回的苏剧。
把Callable类型的对象封装称为FutureTask(线程任务对象)。
2.把线程任务对象交给Thread对象。
3.调用Thread对象的start方法启动线程。
4.线程执行完毕后,通过FutureTask(实现了Runnable)对象的get方法去获取线程任务执行的结果。
-------------
在实现Callable时要返回数据需要定义泛型中数据类型
并发,并行,线程的生命周期
前置:
进程:正在运行的程序
并发:
并行:
并发和并行是同时发生的。
线程的生命周期:
Thread提供的方法用于线程的操作
Thread可以直接在线程(包括main)中使用。
Thread是在lang包下的,Java默认导入了lang包
注意线程名字的输出;
重置线程默认的名称时应先设置,在输出
sleep,join方法注释:
sleep主要用于将某个过程在此处暂停多少毫秒。
join时为了确保执行顺序(先执行后执行最后执行....),上图确保按1,2,3进行执行
线程安全引入(出现了问题):
多个线程同时操作一个共享资源的时候,可能会出现业务安全的问题
如当操作统一个资源,且有修改流程
下面进行使用取钱案例来进行实现入门:
两个人操作同一个线程
可能会出现异常
线程同步(解决线程安全):
用来解决线程安全出现的问题
思想:让多个线程先后依次的去访问共享资源。
常见方案一:加悲观锁(指保证线程安全,但不能同时执行,使得性能较差)
先进行上锁,当一个执行完后再执行
锁是可以跨方法用同一个锁的
java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。
this锁的是当前实例对象进入该方法同步锁包括对象锁和 类锁(主要是锁那个静态方法):
类名.class锁用于的是静态方法中的(静态方法用类名可以直接访问)
更多见关键字synchronized的用法。
加锁方式一:同步代码块
锁的是代码块
但此时如果使用的是唯一量,那么线程会较慢,会只能进行一个,所以为了解决此问题应该一张卡一个锁,所以应该使用this使得 在进入的这个所是一个共享资源即对应的是他们一个人同一张卡。
加锁方式二:同步方法
直接锁的是方法将线程,逐个启动该方法
该方式也可以锁静态方法默认是this所以所得是该方法
如果锁了一个方法而另一个方法没锁则另一个方法可以正常都可以进行访问
加锁方式三:Lock锁
Lock锁是一个接口需要使用其下的实例或者多态形式及逆行创建。
锁为确保每个不同的共享资源同一种多个线程同时进行这时也应该要将Lock锁对象在进行创建对象的时候直接赋予该对象
Lock对象即要有锁方法lock也需要有解锁方法unlock
Lock锁的注意事项:
1.在线程进行后,锁后可能会有进入后执行发生bug导致跳出产生”死锁 ”,没有及进行解锁导致其他线程无法使用该方法问题。
所以一般在进行使用的时候需要放在try -catch -finally体系中。
乐观锁(即保证线程安全,又能同时进行,性能较好):
一开始并不进行上锁,当线程出现问题的时候才上锁。
如图再进行是,可能会由于线程执行时,同时++导致数据少加的现象
下面悲观锁:
乐观锁:
乐观锁对不同的数据处理提供了不同的原子类:
如int的原子类:
. incrementAndGet()默认方法是:++(但++是有条件的即上述是否被处理,是通过地址获取数据后比较的)
方法实质是:
CAS算法:拿数据时,判断数据拿出后,与未拿出时数据是否相同(相同表明没有被其他线程处理,相同代表被处理了);如果不相同执行操作,如果相同则作废再拿其他线程处理后数据再进行处理。
线程通信(操作系统,线程的操作处理):
需要了解线程操作类的常用方法
sleep不会释放锁,notifyAll会释放锁。
线程的操作方法。
模型案例:
包子的吃与做,
3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子上
2个消费者线程负责吃包子,每人每次只能从桌子.上拿1个包子吃。
package 线程通信;
/*** 主要是需要知道Thread提供的操作线程的方法。*/
Thread是在lang包下的。在java环境下可以直接进行使用
public class Threadtest {public static void main(String[] args) {Desk desk = new Desk();//创建3个生产者线程(3个厨师)new Thread(() -> {while (true) {desk.put() ;}}, "厨师1"). start();new Thread(() -> {while (true) {desk. put() ;}}, "厨师2") .start();new Thread(() -> {while (true) {desk. put();}}, "厨师3"). start();//创建2个消费者线程(2个吃货)new Thread(() -> {},"吃货1") . start();new Thread(() -> {},"吃货2") . start();}
}
package 线程通信;
import java.util.ArrayList;
import java.util.List;
public class Desk {List <String> list=new ArrayList<>();public synchronized void put() {try {String name =Thread.currentThread().getName();//判断是否有包子。if(list.size() == 0){list.add(name+"做的肉包子");System.out.println(name + "做了一个肉包子~~");Thread.sleep(2000);//唤醒别人,等待自己this . notifyAll();this. wait();}else {// 有包子了, 不做了。//唤醒别人,等待自己this.notifyAll();this. wait();}} catch (Exception e) {e. printStackTrace();}}//吃货1吃货2public synchronized void get() throws Exception {try {String name = Thread.currentThread().getName();if (list.size() == 1) {//有包子,吃了System.out.println(name + "吃了: " + list.get(0));list.clear();this.notifyAll();this.wait();}else{this.notifyAll();this.wait();}} catch (Exception e) {e. printStackTrace() ;}}}
基本原理:用一定数线程去处理任务
作用:复用线程。
谁代表线程池?
●jDK 5.0起提供了代表线程池的接口: ExecutorService。
最大限度表示最多能储存的线程:最大线程数减去核心(正式员工)线程数,是可创建的临时的线程数。
核心线程和任务队列是相对独立的。
在进行创建线程的时候需要用到threadFactory来进行创建:
线程池处理Runnable任务(无返回值类型):
处理Runnable任务
Runnable是一个无返回值得任务类,只有run方法
需要先创建一个实现Runable的任务类,之后使用线程池的execute方法调用Runnable对象即可
线程池:
创建临时线程的条件:
如果任务队列没满的情况下,则会复用上面的核心线程(等待使用将要完成的线程重新使用)。
当核心线程都被占用且任务队列满了的时候会创建临时线程。
核心线程和任务队列是相对独立的。
当超过的任务队列,所有线程满的时候,会报错。
默认拒绝任务的处理是:会将存在的任务和任务队列中的进行完,但超过任务队列和线程总数的总数的任务,不会添加到任务列表(即不会再进行)
线程池的关闭:
线程池如果没有使用关闭线程的方法,是不会自动关闭的。
需要用close进行。
任务拒绝策略:
上面是常用的,这些是在线程池创建的时候使用的。具体见线程池的创建。
线程池处理Callable任务(可以有返回值类型):
future实质是Futuretask的复写方法。
Callable在调用的时候是可以有设置有返回值类型的
线程池的工具类:
newFixedThreadPool创建时输入的n代表线程数(等于核心线程数==最大线程数)
newsingleThreadExecutor创建时,线程数(等于核心线程数==最大线程数)为1