1.实现多线程的方法
1.1 继承Thread类
步骤:1.自定义类继承Thread类 2.重写run方法,run方法中是需要执行的多线程程序 3.创建自定义类的对象 4.对象.start()
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.start();
m2.start();
}
线程方法:
start():开启线程
sleep():线程休眠指定时间
join():等待此线程执行完毕后再执行其他线程
interupt():中断线程,把线程状态终止,并抛出异常。
1.2 实现Runable接口
步骤:1.自定义类实现Runable接口 2.重写run方法,run方法中是需要执行的多线程程序 3.创建自定义类的对象 4.创建Thread类对象,并把创建的对象作为构造参数传递。
1.3 实现Callable接口+FutureTask(泛型可以拿到返回结果,处理异常)
步骤:1.自定义类实现Callable接口 2.重写call方法,call方法中是需要执行的多线程程序 3.创建自定义类的对象 4.创建FutureTask类对象,并把创建的对象作为构造参数传递。
若get()后还有执行的语句,将在获得结果后才会执行相应语句,执行get()为阻塞等待,在返回结果之前下面的语句不会执行
1.4 线程池
在业务代码中,以上三种启动线程的方法都不用。
将所有的多线程异步任务都直接给线程池执行。
一个系统中池只有一两个,每个异步任务都交给他自己去执行
//固定数量为10个的线程池
public static ExecutorService service=Executors.new FixedThreadPool(10);
service.execute(new Runable01());
线程池详解
线程池中有七大参数,详解如下:
int corePoolSize,核心线程数:一直存在,除非设置allowCoreThreadTimeOut,线程池创建好以后就准备就绪的线程数量,等待接收异步任务执行
int maximumPoolSize,最大线程数量:控制线程资源
long keepAliveTime,存活时间:如果当前的线程数量大于核心数量,只要线程空闲大于指定的keepAliveTime就释放空闲的线程(maximumPoolSize-corePoolSize的这一部分),核心线程数一直存在
TimeUnit unit:存活时间的时间单位
BlockingQueue workQueue,阻塞队列:如果任务有很多,就会将目前多的任务放在队列里,只要有线程空闲,就会去队列里取出新的任务进行执行
ThreadFactory threadFactory:线程的创建工厂
RejectedExecutionHandler handler,拒绝策略:如果workQueue队列满了,按照我们指定的拒绝策略,拒绝执行任务
题目
一个线程池, core 7 ,max 20 ,queue:50, 100并发进来怎么分配?
7个立即得到执行,50个进入队列,最大为20,可以再开13个线程执行,剩下30个使用拒绝策略。
如果不想抛弃,还要执行,拒绝策略使用 CallerRunsPolicy,同步执行。
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
200,
10,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}
常用线程池
//core为0,所有都可回收
Executors.newCachedThreadPool();
//固定大小,core=max,所有都可回收
Executors.newFixedThreadPool(10);
1.5 四种方式区别
前两种区别:第二种方式避免了Java单继承带来的局限性(如一个子类想实现多线程,就不能继承父类了,而父类并不想实现多线程)。
四种区别:1、2不能得到返回值,3可以获取返回值。1、2、3都不能控制资源。4可以控制资源,性能稳定。
2.解决线程同步问题
2.1 同步代码块
synchronized(锁对象){
需要同步的代码块
}
同步代码块的锁对象是谁?
可以是任意对象。
同步方法的格式及锁对象?
把同步关键字加在方法上。锁对象为this。
静态方法的锁对象?
类的字节码文件对象。
2.2 锁
Lock lock = new ReentrantLock();
lock.lock();
//需要同步的代码块
lock.unlock();
死锁问题
2.Java高并发
2.1 问题
1.在一个synchronized方法执行时,另一个普通方法能否执行?
可以执行,因为只有同步方法执行时才需要申请锁对象。普通方法直接执行即可,不需申请锁对象就可以执行。
2.一个同步方法是否可以调用另外一个同步方法?一个线程已经拥有某个对象的锁,再次申请是否仍然会得到锁?
可以调用。再次申请仍可以得到锁。也就是说synchronized获得的锁是可重入(已经获得的锁仍然可以再获得)的。子类的synchronized方法也可以调用父类的synchronized方法,不会造成死锁。
3.synchronized线程抛出异常会释放锁吗?
会,如果不想释放,进行try catch。
4.解释volatile
程序执行结果为:当不加volatile关键字时,不会打印end。加上后,打印end。
假设两个cpu在运行两个线程,每个cpu都有一部分为缓冲区,主内存有一变量,在线程运行时,一个cpu会将这一变量copy到它自己的缓冲区,这个cpu在运行非常忙时就不再去主内存读取这个变量(当cpu比较空闲时就可能会去cpu读取修改后的值),而是直接使用它缓存中的内容。当另一个cpu执行另一个线程时,修改这个变量的值,修改后直接写回主内存。此时主内存中与第一部分的cpu中的变量值已经不同,但第一个cpu不会去读取这个变量的值。加上volatile关键字后,当第二个cpu对这个变量值进行修改后,会通知第一个线程,重新读取这个变量值。
volatile的意思就是,当某个线程修改了一个变量的值时,会通知其他线程,需要重新读取变量值。此题中为main线程和t1线程。
5.synchronized和volatile区别
前者保证可见性和原子性,后者只保证可见性。后者的执行效率远远高于前者。
Java中提供了Atomic类,实现较为简单的同步操作,可以保证原子性,而且运行效率比synchronized要高不少。