多线程
单任务和多任务
单任务处理:在一个任务没有完成时,不会执行别的任务。
多任务处理:在一个任务没有完成时,同时进行别的任务,多个任务同时执行。
多任务处理分类
基于进程:进程是自包容的应用程序,由操作系统直接管理,直接运行。每个进程一开启,都会消耗内存空间。
基于线程:线程是应用程序中的顺序控制流,一个进程中可以包含很多线程,多个线程共享一个进程的内存空间。
线程
主线程
main方法一个运行,就开启一个主线程,每个进程都有一个主线程
**特点:**1.最先开始
2.最后结束
3.产生其他的子线程
4.子线程结束后,回收子线程占用的资源。
创建子线程
1、继承Thread类,重写run方法
class Thread1 extends Thread{
@Override
public void run() {
}
启动线程
Thread1 thread1=new Thread1();
//启动线程
thread1.start();
调用线程类start()和run()方法的区别:
- 调用start(),会在主线程之外,开启子线程,多个任务同时执行。
- 调用run(),是普通方法调用,不会在主线程之外,开启子线程,是单任务处理。
2、实现Runnable接口,实现run方法
class Thread2 implements Runnable {
@Override
public void run() {
}
启动线程
//创建线程对象,传入Runnable实现类对象,线程启动后,会执行Runnable实现类的run()
Thread thread2 = new Thread(new Thread2());
thread2.start();
3、实现Callable接口,实现calll方法
继承Thead类,重写run,实现Runnable接口,实现run方法。在线程结束后,都不能那个得到返回值。
实现Callable接口,重写call方法,在线程结束后可以得到返回值。
//创建FutureTask对象,该类为runnable实现类。
//传入callable实现类对象,线程启动时,会执行callable实现类的call方法
FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 50; i++) {
sum += i;
}
return sum;
}
});
//启动线程
new Thread(task).start();
try {
//得到线程执行完毕后的返回值
Integer sum = task.get();
System.out.println(sum);
} catch (Exception e) {
e.printStackTrace();
}
4、采用线程池创建线程
应用大量通过new Thead()方法创建执行时间短的线程,较大消耗系统资源并且系统的响应速度变慢。
线程池首先创建一些线程,他们的集合称为线程池。线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务。执行任务结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
线程池的工作机制
1、在线程池的编程模式下,任务是提交給整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就是内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。
2、一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
线程池的种类
1、创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。
public static void main(String[] args) {
//创建无限扩展的线程池
ExecutorService service = Executors.newCachedThreadPool();
//向线程池提交任务
service.execute(new Mythead());
service.execute(new Mythead());
//关闭线程池
service.shutdown();
}
class Mythead implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//Thread.currentThread()得到当前线程
System.out.println(Thread.currentThread() + " " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2、创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于负载较重的场景,对当前线程数量进行限制。
//创建大小为2的线程池
ExecutorService service = Executors.newFixedThreadPool(2);
//向线程池提交任务
service.execute(new Mythead());
service.execute(new Mythead());
service.execute(new Mythead());
//关闭线程池
service.shutdown();
开始时,线程池中两个线程交替执行两个任务。第三个任务进行等待。当线程池中其中一个线程任务执行完毕,就执行第三个任务。
3、创建一个单线程的线程池,适用于需要保证顺序执行各种任务。
//创建单线程线程池
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Mythead());
service.execute(new Mythead());
service.shutdown();
任务一个一个按顺序执行
线程状态
- 新建 创建Thread对象
- 就绪 调用start()方法,启动线程
- 运行 执行run()方法
- 死亡 run()执行完毕
- 睡眠 调用Thread.sleep(30)
- 等待 调用Object类中的wait()
- 挂起 调用yield()方法,当前线程让出CPU使用权
- 阻塞 等待IO事件输入
线程优先级
线程优先级是指,当两个或两个以上的线程都处于就绪状态。优先级高的线程,会优先得到执行。
优先级分为10级,1-10,默认为5,数字越大,优先级越高。
优先级的设置和获取
Thread thread = new Thread();
//设置线程的优先级
thread.setPriority(10);
//得到线程的优先级
System.out.println(thread.getPriority());
线程同步
当两个或两个以上的线程,同时访问同一个资源时,为了保证数据的安全,只允许同一事件一个线程进行访问。
线程同步的后果
1、数据安全
2、效率低
死锁
死锁是指在多线程情况下,多个线程同步竞争相互依赖的资源,从而造成多线程无法继续执行的情况。
public class DieLok {
public static void main(String[] args) {
LockObj o1 = new LockObj("乔丹");
LockObj o2 = new LockObj("皮蓬");
new Thread(new Runnable() {
@Override
public void run() {
synchronized (o1) {
try {
Thread.sleep(200);
o2.speak();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (o2) {
try {
Thread.sleep(200);
o1.speak();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
class LockObj {
private String name;
public LockObj(String name) {
this.name = name;
}
public synchronized void speak() {
System.out.println("我叫:" + this.name);
}
}
线程同步的实现
使用synchronized
-
同步方法
public synchronized void getMoney() {}
-
同步块
public void run() { synchronized(one){ one.display(); } }
使用Lock接口
- Lock是一个接口,而synchronized是Java内置的语言实现
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生,而Lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象,因此使用lock时需要在finally块中释放锁。
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够相应中断
- 通过Lock可以知道有没有成功获取锁,而synchronized无法办到
- Lock可以提高多个线程进行读操作的效率
- Lock接口常用方法:lock()方法获取锁时,如果锁已经被其它线程获取,则等待
public class LockTest {
public static void main(String[] args) {
LockObject lockObject = new LockObject();
new Thread(new Runnable() {
@Override
public void run() {
lockObject.speak();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
lockObject.speak();
}
}).start();
}
}
class LockObject {
private Lock lockObject = new ReentrantLock();
public void speak() {
//加锁
lockObject.lock();
try {
for (int i = 0; i < 10; i++) {
System.out.print(i);
Thread.sleep(200);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lockObject.unlock();
}
}
}
2.trylock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回ture,如果获取失败(即锁已被其它线程获取),则返回false,也就是说这个方法无论如何都会立即返回,在拿不到锁时不会一直在那等待。
class LockObject {
private Lock lockObject = new ReentrantLock();
public void speak() {
if (lockObject.tryLock()) {
try {
for (int i = 0; i < 10; i++) {
System.out.print(i);
Thread.sleep(200);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lockObject.unlock();
}
}else {
System.out.println("获取锁失败");
}
}
}
3.读写锁
- 当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
- 读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁
- 如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其它线程可以同时进行读操作。申请写操作的线程只能等待。
- 如果有一个线程已经占用了写锁,则此时其它线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
线程通讯
java使用wait()、notify()和notifyAll()方法,完成线程间的通信。
wait()、notify()和notifyAll()都在Object中定义。都只能在同步方法或同步块中使用。
public class WaitTest {
public static void main(String[] args) {
TestObj obj = new TestObj();
new ObjA(obj).start();
new ObjB(obj).start();
}
}
class TestObj {
public boolean isRun = true;
}
class ObjA extends Thread {
private TestObj obj;
public ObjA(TestObj obj) {
this.obj = obj;
}
public void run() {
while (true) {
synchronized (obj) {
if (obj.isRun == true) {
System.out.print("A");
obj.isRun = false;
try {
Thread.sleep(200);
//唤醒等待线程访问obj对象
obj.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
try {
//当前线程对obj对象等待
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class ObjB extends Thread {
private TestObj obj;
public ObjB(TestObj obj) {
this.obj = obj;
}
public void run() {
while (true) {
synchronized (obj) {
if (obj.isRun == false) {
System.out.print("B");
obj.isRun = true;
try {
Thread.sleep(200);
obj.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
wait和sleep的区别
wait和sleep都可以让线程暂时停止运行,但是使用时机是不同的。
- sleep在Thread类中定义,而wait在Object中定义
- wait只能放在同步方法或同步块中,表示针对某个对象进行等待。而sleep可以放在方法中任何位置,表示当前线程休眠。
- wait会释放对象锁,而sleep不会
- sleep在休眠结束后,自动恢复线程运行。而wait需唤醒才能恢复线程运行。
Volatile
变量定义为volatile之后将具备两种特性:
- 保证此变量对所有的线程的可见性,当一个线程修改了这个变量的值,volatile保证了新值能立即同步到主内存
- 禁止指令重排序优化