多线程
1.线程相关的概念
1.程序(program):是一个静态的状态,一般存储在硬盘中
2.进程(process):一个正在运行的程序是一个动态的概念,一般存储在内存中。
3.线程(thread):一条独立的执行路径。多线程,在执行某个程序的时候,该程序可以有多个子任务,每个线程都可以独立的完成其中一个任务。在各个子任务之间,没有什么依赖关系,可以单独执行。
2.进程和线程的关系
一个进程中至少有一个线程,可以有多个线程,一个进程中的所有线程,共享同一个进程中的资源
3.并行和并发
并行(parallel):多个任务(进程、线程)同时运行。在某个确定的时刻,有多个任务在执行,条件:有多个cpu,多核编程
并发(concurrent):多个任务(进程、线程)同时发起。不能同时执行的(只有一个cpu),只能是同时要求执行。就只能在某个时间片内,将多个任务都有过执行。一个cpu在不同的任务之间,来回切换,只不过每个任务耗费的时间比较短,cpu的切换速度比较快,所以可以让用户感觉就像多个任务在同时执行。(实际开发中的多线程程序,都是并发运行机制)
4.多线程的实现方式
第一种:继承Thread类
1) 定义一个类,继承Thread类
2) 重写自定义类中的run方法,用于定义新线程要运行的内容
3) 创建自定义类型的对象
4) 调用线程启动的方法:start方法
代码实现
//自定义类
package com.tohka.pojo;
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
//测试类
package com.tohka.junit;
import com.tohka.pojo.MyThread;
public class Junit1 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
//使用匿名内部类的方式来实现
package com.tohka.junit;
import com.tohka.pojo.MyThread;
public class Junit2 {
public static void main(String[] args) {
new MyThread(){
@Override
public void run() {
for (int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}.start();
for (int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
第二种:实现实现Runnable接口
1)定义一个任务类,实现Runnable接口
2)重写任务类中的run方法,用于定义任务的内容
3)创建任务类对象,表示任务
4)创建一个Thread类型的对象,将任务对象作为构造参数传递,用于执行任务类对象
Thread(Runnable able);
5)调用线程对象的start方法,开启新线程
调用的就是Thread类构造方法中传递的able线程任务中的run方法
代码实现:
//任务类
package com.tohka.pojo;
public class TaskThread implements Runnable {
public void run() {
for (int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
//测试类
package com.tohka.junit;
import com.tohka.pojo.TaskThread;
public class Junit3 {
public static void main(String[] args) {
TaskThread ts = new TaskThread();
Thread thread = new Thread(ts);
thread.start();
for (int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
//使用匿名内部类实现
package com.tohka.junit;
import com.tohka.pojo.TaskThread;
public class Junit3 {
public static void main(String[] args) {
new Thread(new TaskThread(){
@Override
public void run() {
for (int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}).start();
for (int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
第三种:实现Callable接口
实现思路:
如果创建Thread对象,执行Runnable任务,需要Runnable对象
Runnable是一个接口,有一个特殊的实现类
FutureTask是Runnable的一个实现类
FutureTask在创建对象的时候,需要传递一个Callable的对象
Callable是一个接口,所以我们需要一个Callable的实现类
1)定义一个自定义类实现Callable接口
2)在自定义类中重写call()方法
3)创建自定义类的对象
4)创建Future的实现类FutureTask对象,把自定义类的对象作为构造方法的参数
5)创建Thread类的对象,把FutureTask对象作为构造方法的参数
6)使用Thread类的对象调用start方法启动线程
7)FutureTask对象用get方法,就可以获取线程结束之后的结果。
代码实现
//自定义类
package com.tohka.pojo;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
public Integer call() throws Exception {
int i ;
for (i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
return i;
}
}
//测试类
package com.tohka.junit;
import com.tohka.pojo.MyCallable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Junit4 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
Thread thread = new Thread(futureTask);
thread.start();
Integer i = futureTask.get();
System.out.println(thread.currentThread().getName()+i);
}
}
第四种:通过线程池实现
1.创建一个指定线程数量的线程池 Executors.newFixedThreadPool(int nThreads)
2.创建任务类对象:Runnable的实现类对象,用于定义任务内容
3.将一个任务类对象,提交到线程池中,如果有空闲的线程,就可以马上运行这个任务,如果没有空闲线程,那么这个任务就需要等待 submit(Runnable r)
4.结束线程池 shutDown()、shutDownNow()
代码实现
package com.tohka.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo1 {
public static void main(String[] args) {
ExecutorService ex = Executors.newFixedThreadPool(3);
Runnable r1 = new Runnable() {
public void run() {
for (int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
};
Runnable r2 = new Runnable() {
public void run() {
for (int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
};
Runnable r3 = new Runnable() {
public void run() {
for (int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
};
ex.submit(r1);
ex.submit(r2);
ex.submit(r3);
ex.shutdown();
}
}
5.线程优先级
线程默认优先级是5;线程优先级的范围是:1-10
Thread类提供的优先级常量(静态常量)
MIN_PRIORITY = 1; 最低
NORM_PRIORITY = 5; 默认
MAX_PRIORITY = 10; 最高
注意 : 哪怕给线程设定了优先级,没有办法保证,哪些线程一定最先运行; 线程优先级别高的, 获取CPU资源的概率大, 但线程的执行仍然具有很大的随机性
6.线程礼让
static void yield()
线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程若队列中没有同优先级的线程,忽略此方法
测试代码
package com.tohka.junit;
public class Junit5 {
public static void main(String[] args) {
Thread thread1 = new Thread("礼让线程"){
@Override
public void run() {
System.out.println(getName());
}
};
thread1.yield();
thread1.setPriority(Thread.MAX_PRIORITY);
Thread thread2 = new Thread("不让线程1"){
@Override
public void run() {
System.out.println(getName());
}
};
thread2.setPriority(Thread.MAX_PRIORITY);
Thread thread3 = new Thread("不让线程2"){
@Override
public void run() {
System.out.println(getName());
}
};
thread3.setPriority(Thread.MAX_PRIORITY);
thread1.start();
thread2.start();
thread3.start();
}
}
执行结果:
不让线程1
礼让线程
不让线程2
出现问题原因:
无法100%礼让,只能说礼让会尽量后执行
7.线程中断
中断这个线程休眠,等待状态,只中断一次。
想中断那个线程,必须到另一个线程中取中断,因为自己不能中断自己
代码案例
package com.tohka.junit;
public class Junit5 {
public static void main(String[] args) {
Thread t3 = new Thread("不让线程2"){
@Override
public void run(){
for(int i = 1; i <= 10; i++){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "--" +i);
}
}
};
t3.start();
t3.interrupt();
}
}
8.守护线程
如果一个程序的运行中,没有非守护线程了,只有守护线程了,那么守护线程会过一段时间后自动停止
final void setDaemon(boolean on) : 参数设置为true,将线程设置为守护线程
将此线程标记为daemon线程或用户线程。当运行的唯一线程都是守护进程线程时,Java虚拟机将退出
package com.tohka.junit;
public class Junit5 {
public static void main(String[] args) {
Thread thread = new Thread("守护线程") {
@Override
public void run() {
for(int i = 1; true; i++){
System.out.println(getName() + "--" +i);
}
}
};
thread.setDaemon(true);
Thread thread1 = new Thread("普通线程") {
@Override
public void run(){
for(int i = 1; i <= 10; i++){
System.out.println(getName() + "--" +i);
}
}
};
thread.start();
thread1.start();
}
}
9.使用同步代码块保证线程安全的原理
当cpu想去执行同步代码块的时候,需要先获取到锁对象,获取之后就可以运行代码块中的内容;当cpu正在执行当前代码块的内容时,cpu可以切换到其他线程,但是不能切换到需要相同锁对象的线程上。
当cpu执行完当前代码块中的代码之后,就会释放锁对象,cpu就可以运行其他需要当前锁对象的同步代码块了
10.同步方法的锁对象
如果是非静态的方法,同步方法的锁对象就是this,当前对象,哪个对象调用这个同步方法,这个同步方法使用的锁就是哪个对象
如果是静态的方法,同步方法的锁对象就是当前类的字节码对象,类名.class(在方法区的一个对象),哪个类在调用这个同步方法,这个同步方法使用的锁就是哪个类的字节码对象
11.线程的生命周期
新建态:刚创建好对象的时候,刚new出来的时候
就绪态:线程准备好了所有运行的资源,只差cpu来临
运行态:cpu正在执行的线程的状态
阻塞态:线程主动休息、或者缺少一些运行的资源,即使cpu来临,也无法运行
死亡态:线程运行完成、出现异常、调用方法结束
**12.wait()与 notify()方法 **
wait() 让线程等待
notify()唤醒等待的线程
notify()和 notifyAll()
一般来说,所有等待的线程 会按照顺序进行排列,如果现在使用了 notify()方法的话,则会唤醒第一个等 待的线程执行,而如果使用了 notifyAll()方法,则会唤醒所有的等待线程,那 个线程的优先级高,那个线程就有可能先执行。
13.sleep() 和 wait()的区别
1、这两个方法来自不同的类分别是,sleep来自Thread类,wait来自Object类。
2、最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
Thread.Sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。
3、使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
synchronized(x){
x.notify()
//或者wait()
}
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
14.线程同步的几种方式
1、synchronized 可以用来同步方法和同步代码块
同步方法:
给一个方法增加synchronized关键字,可以是静态方法(锁住整个类),也可以是非静态方法(不能是抽象方法)
同步代码块:通过锁定一个指定的对象,来对同步代码块进行同步
同步是高开销操作,尽量少使用同步方法,同步关键代码的代码块即可
2、Lock锁
ReentrantLock 代码通过lock()方法获取锁,但必须调用unlock()方法释放锁
15.如何理解线程池思想?Java中的线程池有哪些?
创建线程每次都要和操作系统进行交互,线程执行完任务后就会销毁,如果频繁的大量去创建线程肯定会造成系统资源开销很大,降低程序的运行效率。
线程池思想就很好的解决了频繁创建线程的问题,我们可以预先创建好一些线程,把他们放在一个容器中,需要线程执行任务的时候,从容器中取出线程,任务执行完毕后将线程在放回容器。
一:newCachedThreadPool(缓存线程型池)
二:newFixedThreadPool (缓存线程池和上面的差不多)
三:ScheduledThreadPool(调度型线程池)
四:SingleThreadExecutor(单例线程)
详细解析网址:https://zhidao.baidu.com/question/1116515319242598059.html
16.开发中哪些地方用到了多线程?
你可以说我们的做的项目几乎没有用到多线程 ,
涉及到的地方几乎都用 搭建集群的方式来解决
17.什么是线程同步、异步?
1.线程同步:是多个线程同时访问同一资源,等待资源访问结束,浪费时间,效率不高
2.线程异步:访问资源时,如果有空闲时间,则可在空闲等待同时访问其他资源,实现多线程机制
18.说一下 Runnable和 Callable有什么区别?
1)Runnable接口中的方法没有返回值;Callable接口中的方法有返回值
2)Runnable接口中的方法没有抛出异常;Callable接口中的方法抛出了异常
3)Runnable接口中的落地方法是call方法;Callable接口中的落地方法是run方法
详细链接:https://blog.csdn.net/syc0616/article/details/109734464
19.线程的run()和start()的区别
1.调用 start() 方法是用来启动线程的,轮到该线程执行时,会自动调用 run();直接调用 run() 方法,无法达到启动多线程的目的,相当于主线程线性执行 Thread 对象的 run() 方法。
2.一个线程对线的 start() 方法只能调用一次,多次调用会抛出 java.lang.IllegalThreadStateException 异常;run() 方法没有限制。
20.synchronized和lock的区别
作用位置不同:
synchronized可以给方法、代码块加锁
lock只能给代码块加锁
锁的获取和释放机制不同:
synchronized无需手动获取锁和释放锁,发生异常会自动解锁,不会出现死锁
修饰成员方法时,默认的锁对象,就是当前对象
修饰静态方法时,默认的锁对象,是当前类的Class对象 比如:Person.class
修饰代码块时,可以自己来设置锁对象,比如:
synchronized(this){
//线程进入,自动获取到锁
//线程执行结束,自动释放锁
}
lock需要自己加锁和释放锁,如lock()和unlock(),如果忘记使用unlock(),则会出现死锁,所以一般会在finally里使用unlock()
21.乐观锁和悲观锁的区别
1.悲观锁假定会发生冲突,访问的时候都要先获得锁,保证同一个时刻只有线程获得锁,读读也会阻塞
就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了
2、乐观锁假定不会发生冲突,只有在提交操作的时候检查是否有冲突
主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新
实现方式:
(1)取出记录时,获取当前version
(2)更新时,带上这个version
(3)执行更新时, set version = newVersion where version = oldVersion
(4)如果version不对,就更新失败
详情网址:https://www.cnblogs.com/qlqwjy/p/7798266.html
22.多线程怎么解决高并发?
1.synchronized 关键字主要解决多线程共享数据同步问题。
2.ThreadLocal 使用场合主要解决多线程中数据因并发产生不一致问题。
3.ThreadLocal 和 Synchonized 都用于解决多线程并发访问但是 ThreadLocal 与 synchronized 有本质的区别: (
(1)synchronized 是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。
(2) ThreadLocal 是为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并 不是同一个对象,这样就隔离了多个线程对数据的数据共享。而 Synchronized 却正好相反, 它用于在多个线程间通信时能够获得数据共享。
23.Java 关键字 volatile 与 synchronized 作用与区别?
1.volatile
它所修饰的变量不保留拷贝,直接访问主内存中的。
在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄存器)。为了性能,一个线程会在自己的memory中保持要访问的变量的副本。这样就会出现同一个变 量在某个瞬间,在一个线程的memory中的值可能与另外一个线程memory中的值,或者main memory中的值不一致的情况。 一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中。
2.synchronized
当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
区别:
一、volatile是变量修饰符,而synchronized则作用于一段代码或方法。
二、volatile只是在线程内存和“主”内存间同步某个变量的值;而synchronized通过锁定和解锁某个监视器同步所有变量的值。显然synchronized要比volatile消耗更多资源。
24.什么是 Java Timer 类?如何创建一个有特定时间间隔的任务?
java.util.Timer 是一个工具类.可以用于安排一个线程在未来的某个特定时间 执行。Timer 类可以用安排一次性任务或者周期任务。 java.util.TimerTask 是一个实现了 Runnable 接口的抽象类.我们需要去继承 这个类来创建我们自己的定时任务并使用 Timer 去安排它的执行。
25.怎么检测一个线程是否拥有锁?
在 java.lang.Thread 中有一个方法叫 holdsLock(),它返回 true 如果当且仅当当 前线程拥有某个具体对象的锁
**26.Java 中的锁有几种方式 **
两种: Synchronized Lock
1.Synchronized的局限性:
1.如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但 是又没有释放锁,其他线程便只能干巴巴地等待。(不能主动释放锁)
2.当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突 现象,但是读操作和读操作不会发生冲突现象如果多个线程都只是进行读操作,所以当一个 线程在进行读操作时,其他线程只能等待无法进行读操作。(不分情况,一律锁死)
2.Lock 的几个实现类 ReentrantLock是一个可重入的互斥锁,又被称为“独占锁” ReadWriteLock,顾名思义,是读写锁。它维护了一对相关的锁 — — “读取锁”和“写入 锁”,一个用于读取操作,另一个用于写入操作。他的两个实现类读锁readerLock和写锁 writerLock。

浙公网安备 33010602011771号