多线程
1.操作系统中进程和线程的区别:
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
2.多线程运行机制:
抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
分时调度: 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
3.创建线程方式
方式一
1.定义一个类 继承Thread类
2.重写run()方法 在run()方法中写入自己想要写入的东西
3.创建子类对象,就是创建线程对象
4.调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法。
方式二(常用)
1.定义一个类实现Runnable接口
2.重写run()方法 在run()方法中写入自己想要写入的东西
3.创建Thread类的对象
4.将Runnable接口的子类对象作为参数传递给Thread类的构造函数
5.调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法。
好处
免了单继承的局限性
(因为实现的是接口)降低了耦合性
方式三(基本不用)
1.定义一个类实现Callable接口
2.重写call()方法 在call()方法中写入自己想要写入的东西
public class MyCallable implements Callable<String> {
//call() 方法将作为线程执行体,并且有返回值
@Override
public String call() throws Exception {
System.out.println("执行自己的业务");
return "返回想要返回的数据";
}
}
public class bbb {
public static void main(String[] args) {
//创建 Callable 实现类的实例
MyCallable myCallable = new MyCallable();
//使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值
FutureTask<String> futureTask = new FutureTask<String>(myCallable);
String res = null;
try {
//使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程
//没这句,下句代码获取不到结果,会一直等待执行结果
new Thread(futureTask,"线程1").start();
//调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值
res = futureTask.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(res);
}
}
4.线程的方法
currentThread() :返回对当前正在执行的线程对象的引用。
getName() :返回该线程的名称。
start():使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
sleep(long millis) :在指定的毫秒数内让当前正在执行的线程休眠
Thread(Runnable target) :分配新的 Thread 对象。。一边将target作为运行对象
Thread(Runnable target, String name) :分配新的 Thread 对象。一边将target作为运行对象,name当做线程名字
wait():是线程等待必须要唤醒
join():可以用来临时加入线程执行,也就是获取当前线程的执行权
当A 线程执行到B线程的join()方法时,B就会获取A的执行权,B执行,A就会等待,知道B线程执行完毕,A才执行
interrupt() (中断线程):结束线程的冻结状态,使线程线程回到运行状态
setPriority(int newPriority) :更改线程的优先级。有0-10个优先级,默认是5,最低是0,最大是10
yield() :暂停当前正在执行的线程对象,并执行其他线程,一般在循环中使用
5. 线程安全:
5.1:原因: 如果有多个线程在同时运行,而这些线程可能会同时操作操作一些共享数据,这是就会出现线程不安全
解决方案(线程同步)
6.线程的状态
7.死锁
例子:
//首先我们先定义两个final的对象锁.可以看做是共有的资源.
final Object lockA = new Object();
final Object lockB = new Object();
//生产者A
class ProductThreadA implements Runnable{
@Override
public void run() {
//这里一定要让线程睡一会儿来模拟处理数据 ,要不然的话死锁的现象不会那么的明显.这里就是同步语句块里面,首先获得对象锁lockA,然后执行一些代码,随后我们需要对象锁lockB去执行另外一些代码.
synchronized (lockA){
//这里一个log日志
Log.e("CHAO","ThreadA lock lockA");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
//这里一个log日志
Log.e("CHAO","ThreadA lock lockB");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//生产者B
class ProductThreadB implements Runnable{
//我们生产的顺序真好好生产者A相反,我们首先需要对象锁lockB,然后需要对象锁lockA.
@Override
public void run() {
synchronized (lockB){
//这里一个log日志
Log.e("CHAO","ThreadB lock lockB");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockA){
//这里一个log日志
Log.e("CHAO","ThreadB lock lockA");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//这里运行线程
ProductThreadA productThreadA = new ProductThreadA();
ProductThreadB productThreadB = new ProductThreadB();
Thread threadA = new Thread(productThreadA);
Thread threadB = new Thread(productThreadB);
threadA.start();
threadB.start();
分析一下,当threadA开始执行run方法的时候,它会先持有对象锁localA,然后睡眠2秒,这时候threadB也开始执行run方法,它持有的是localB对象锁.当threadA运行到第二个同步方法的时候,发现localB的对象锁不能使用(threadB未释放localB锁),threadA就停在这里等待localB锁.随后threadB也执行到第二个同步方法,去访问localA对象锁的时候发现localA还没有被释放(threadA未释放localA锁),threadB也停在这里等待localA锁释放.就这样两个线程都没办法继续执行下去,进入死锁的状态. 看下运行结果:
8.守护线程
9.结束线程的4种方式
1,run()方法执行完
2.使用退出标志终止线程
例如:最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出,代码示例:
public class ThreadSafe extends Thread {
public volatile boolean exit = false;
public void run() {
while (!exit){
//do something
}}
}
3.使用interrupt()终止线程
在Thread类中有两个方法可以判断线程是否通过interrupt方法被终止。
一个是静态的方法interrupted(),一个是非静态的方法isInterrupted(),
这两个方法的区别是interrupted用来判断当前线是否被中断,而isInterrupted可以用来判断其他线程是否被中断。
因此,while (!isInterrupted())也可以换成while (!Thread.interrupted())。
4. 使用stop方法强制终止线程
使用stop方法可以强行终止正在运行或挂起的线程。我们可以使用如下的代码来终止线程:
thread.stop();
虽然这样可以终止线程,但使用stop方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,因此,并不推荐使用stop方法来终止线程。9.造成线程阻塞的几种方法
(1)线程睡眠:Thread.sleep (long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
(2)线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait() 一样。wait() 和 notify() 方法:两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用.
(3)线程礼让,Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程.
(4)线程自闭,join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,就是别的线程加入进来,当先线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态