Java多线程
线程和进程的区别
- 本质:由CPU进行调度的执行任务,多个任务被快速轮换执行,使得宏观上具有多个线程或者进程同时执行的效果。
- 进程:,也是拥有系统资源的基本单位。进程是系统中独立存在的实体,它可以拥有自己独立的资源,拥有自己私有的地址空间,进程之间不能直接访问其他进程的地址空间。
-
线程:线程是CPU调度的基本单位,也就是说在一个进程中可以有多个并发程序执行流,线程拓展了进程的概念,使得任务的执行得到更加的细分,所以Thread有时候也被称为Lightweight Process。
,它们共享所在进程的资源,包括共享内存,公有数据,全局变量,进程文件描述符,进程处理器,进程代码段,进程用户ID等等,线程独立拥有自己的线程ID,堆栈,程序计数器,局部变量,寄存器组值,优先级,信号屏蔽码,错误返回码等等,。 ,线程之间的通信要进程之间的通信来得容易得多。此外,线程的创建和销毁的开销也远远小于进程的系统开销。
线程和线程池
- 线程池:虽然线程的创建销毁的开销相对较小,但是频繁得创建和销毁也会消耗有限的资源,从而带来性能上的浪费,也不够高效。因此线程池的出,现就是为了解决这一问题,,当有需要执行的任务,就交付给线程中的一个线程,任务执行结束后,该线程也不会死亡,而是回到线程池中重新变为空闲状态。
线程创建的三种方法
1.继承Thread创建多线程
此时每次创建的Thread对象并不能共享线程类的实例变量,也就是下面程序中的i。
public class FirstThread extends Thread{
private int i;
@Override
public void run() {
for(i=0;i<10;i++)
System.out.println(getName()); // 继承自Thread
}
public static void main(String[] args) {
new FirstThread().start();
new FirstThread().start(); // 注意启动线程需要用Start
}
}
2.实现Runnable接口创建线程类
Runnable接口是一个函数式接口(可以使用Lambda表达式),通常做法是重写接口中的run方法,此时方法体即为线程执行体,使用Runnable接口实现类的实例作为Thread的target来创建Thread对象,此时因为使用一个共同的target线程执行体,多个线程可以共享一个实例变量。
public class SecondThread implements Runnable{
private int i;
@Override
public void run() {
for(;i<10;i++) {
System.out.println(Thread.currentThread().getName() + " "+ i);
}
}
public static void main(String[] args) {
SecondThread targetRunnable = new SecondThread();
new Thread(targetRunnable,"线程1").start();
new Thread(targetRunnable).start();
}
}
3.使用Callable和Future创建线程
Callable类似于Runnable,提供一个Call()方法作为线程执行体,并且可以有返回值,以及抛出异常,那么我们如何拿到返回值,java提供了future接口,在接口里定义了一些公共方法来控制关联它的Callable任务,然后java还贴心的给了FutureTask类,该类实现了Future接口和Runnable接口,所以FutureTask的实例就可以作为Thread的Target,所以通常做法是创建Callable接口实现类,并对该实现类的实例使用FutureTask来包装。
public class ThridThread {
public static void main(String[] args) {
// lambda 表达式 + functionInterface 类型转换
// Callbable: 有返回值
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{
int i =0;
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName() + " "+ i);
}
return i;
});
new Thread(task,"有返回值的线程").start();
try {
System.out.println("子线程的返回值"+task.get());
}catch(Exception e) {
e.printStackTrace();
}
}
}
线程的生命周期
线程控制
1.join()线程
让一个线程等待另一个线程,当在某个线程执行流中调用其他线程的join()方法,该线程将被阻塞,知道join线程执行完毕为止。
2.后台线程
后台线程又称为,守护线程,JVM的垃圾回收线程就是典型的后台线程。特征是:。调用Thread对象的setDaemon(true)可以将指定线程设置为后台线程,注意
3.yield():线程让步
Thread的静态方法,使得正在执行的线程暂停,但不会阻塞线程,只是交出CPU的控制权,将线程转为就绪状态,让系统调度器重新调度一次。当某个线程调用yield方法暂停后,只有优先级与当前线程相同,或者优先级比当前线程更高的线程才有可能获得执行机会。
4.setPriority(int newPriority)
改变线程优先级,高优先级的线程能获得更多的执行机会。
线程同步
线程的同步的意义在于线程安全,也就是说有多个线程并发访问同一个对象,而线程调度的不确定性可能带来潜在的安全问题
同步方法块:显式指定同步监视器
public class DrawThread extends Thread {
private Account account;
private double drawaccout;
public DrawThread(String name,Account account,double drawaccount) {
super(name);
this.account = account;
this.drawaccout= drawaccount;
}
public void run() {
synchronized(account) {
if(account.getBlance()>=drawaccount) {
System.out.println(getName()+"取钱成功");
try {
Thread.sleep(1);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
同步方法
public synchronized void draw(double amount) {
……
}
同步监视器的释放
- break,return等终止了代码块的执行
- 同步代码块或者方法中出现未处理的Error或者Exception,导致异常结束
- 当前线程执行同步代码块或者同步方法时,程序中执行了同步监视器的wait()方法,wait是object的方法,范围是该object实例所在的线程
同步锁
lock,更加强大的线程同步机制,通过显式定义锁对象来实现同步,也就是Lock对象,线程在访问共享资源之前,需要先获得锁对象。线程安全控制中比较常用的是ReetrantLock可重入锁。一个线程可以对已经加锁的ReetrantLock再度加锁。
class X{
private final ReentrantLock lock = new ReentrantLock();
//需要定义线程安全的方法
public void foo() {
lock.lock();//加锁
try {
// 需要保证线程安全的代码
}
finally {
lock.unlock();//使用finally块保证释放锁
}
}
}
线程通信
Object类提供的wait(),notify(),notifyAll()三个方法
- wait(): 当前线程等待或者等待若干ms,当前线程自动释放同步监视器,线程进入等待状态(阻塞),直到其他线程调用了该同步监视器的notify()或者notifyAll方法。
- notify():唤醒在同步监视器上等待的单个线程,若有多个线程等待,则任意选择其中一个。
- notifyAll():唤醒在此同步监视器上等待的所有线程。
Condition控制线程通信
使用Condition控制线程通信:使用Lock对象来保证同步问题时,我们可以使用Condition类来释放Lock以及唤醒其他等待线程。
阻塞队列(BlockingQueue)
当生产者线程试图向Blocking Queue中放入元素时,如果队列已满,则该线程被阻塞,当消费者线程试图从BlockingQueue中取出元素时,如果队列已空,则该线程阻塞。
线程池
使用线程池执行线程任务步骤:
- 调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。
- 创建Runnable实现类或者Callable实现类的实例,作为线程的执行任务。
- 调用ExecutorService对象的submit方法来提交Runnable或者Callable实例。
- 当没有任务时,使用shutdown()方法来关闭线程池。
public class Testjava{
public static void main(String[] args)
throws Exception{
ExecutorService pool = Executors.newFixedThreadPool(6);
Runnable target = ()->{
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()
+ "的i值为:"+ i);
}
};
// 向线程池中提交两个线程
pool.submit(target);
pool.submit(target);
pool.shutdown();
}
}