java进阶7-多线程
意义
提高cpu的使用率
进程与线程
进程是一个程序,例如 qq,线程是进程任务之一
进程引入线程作用
- 提高进程使用率
- 一个线程一个栈,共享堆区和方法区
java程序执行原理
java命令启动jvm,jvm启动一个程序,就是启动一个进程,进程启动主线程,主线程调用main方法,main方法中启动分支线程,主线程与分支线程,相互独立,主方法结束,分支线程不一定结束。注意,主方法结束,分支线程不一定结束,但主线程结束,分支线程一定结束。
分支的run方法与main方法平级。
线程是一个任务,一组操作的集合,怎样把要执行的操作放到线程中呢?把执行的操作放到run方法即可。
线程的创建
继承Thread
public class ThreadTest {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
class MyThread extends Thread {
@Override
public void run() {
// 编写程序,这段程序运行在分支线程中。
for(int i = 0; i < 1000; i++){
System.out.println("分支线程--->" + i);
}
}
}
实现Runnable接口
implements Runnable
public class ThreadTest03 {
public static void main(String[] args) {
// 创建一个可运行的对象
MyRunnable r = new MyRunnable();
// 将可运行的对象封装成一个线程对象
//Thread t = new Thread(r);
Thread t = new Thread(new MyRunnable()); // 合并代码
// 启动线程
t.start();
// 这并不是一个线程类,是一个可运行的类。它还不是一个线程。
class MyRunnable implements Runnable {
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("分支线程--->" + i);
}
}
}
注意 实现Runnable接口是作为线程目标使用的。
线程启动
one.start();
start之后,系统进入线程执行机制,自动调用run()方法。
获取当前线程,设置名字,获取名字
//currentThread就是当前线程对象
Thread currentThread = Thread.currentThread();
// 创建线程对象
MyThread2 t = new MyThread2();
// 设置线程的名字
t.setName("t1");
// 获取线程的名字
String tName = t.getName();
线程生命周期
新建 new之后
就绪 start之后
运行
阻塞 运行了一段程序,有被安排到就绪状态,java语言可以控制的部分,控制线程的执行
死亡 运行完毕
注意:线程到了运行转态,就交个CPU安排执行那个线程,此时程序将不起作用。
**
线程的调度与控制
Thread.sleep() 休眠线程
作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
睡眠,进入阻塞转态,如果睡眠被中断,抛出interruptedException异常。
t. interrupt()唤醒线程
比如线程再sleep()之后在阻塞转态,interrupt()之后,中断阻塞,抛出异常interruptException,把线程从阻塞中解救出来,解救出来线程该怎样执行,看获取到异常后的代码怎样写的,就怎样执行
sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么叫醒一个正在睡眠的线程??
注意:这个不是终断线程的执行,是终止线程的睡眠。
public class ThreadTest08 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
t.setName("t");
t.start();
t.interrupt();
class MyRunnable2 implements Runnable {
// 重点:run()当中的异常不能throws,只能try catch
// 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---> begin");
try {
// 睡眠1年
Thread.sleep(1000 * 60 * 60 * 24 * 365);
} catch (InterruptedException e) {
// 打印异常信息
// e.printStackTrace();
System.out.println("========================hhhhhh");
}
//1年之后才会执行这里
System.out.println(Thread.currentThread().getName() + "---> end");
}
Thread.stop() 停止线程 已启用
正确停止线程
设置私有成员变量flag(举例)boolean类型,在run()方法中,设置在flag为true情况下在代码后 break;然后声明setFlag()方法,供实例调用,调用后停止。
public class ThreadTest10 {
public static void main(String[] args) {
MyRunable4 r = new MyRunable4();
Thread t = new Thread(r);
t.setName("t");
t.start();
r.run = false;
}
}
class MyRunable4 implements Runnable {
// 打一个布尔标记
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++){
if(run){
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
return;
}
}
}
}
调度模式
- 分时
- 抢占式
java属于抢占式
线程优先级
void setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程优先级
最低优先级1
默认优先级是5
最高优先级10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
**start之前,setPriority();
public class ThreadTest11 {
public static void main(String[] args) {
// 设置主线程的优先级为1
Thread.currentThread().setPriority(1);
/*System.out.println("最高优先级" + Thread.MAX_PRIORITY);
System.out.println("最低优先级" + Thread.MIN_PRIORITY);
System.out.println("默认优先级" + Thread.NORM_PRIORITY);*/
// 获取当前线程对象,获取当前线程的优先级
Thread currentThread = Thread.currentThread();
// main线程的默认优先级是:5
//System.out.println(currentThread.getName() + "线程的默认优先级是:" + currentThread.getPriority());
Thread t = new Thread(new MyRunnable5());
t.setPriority(10);
t.setName("t");
t.start();
// 优先级较高的,只是抢到的CPU时间片相对多一些。
// 大概率方向更偏向于优先级比较高的。
for(int i = 0; i < 10000; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
class MyRunnable5 implements Runnable {
@Override
public void run() {
// 获取线程优先级
//System.out.println(Thread.currentThread().getName() + "线程的默认优先级:" + Thread.currentThread().getPriority());
for(int i = 0; i < 10000; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
Thread.yield() 静态方法
让其他同优先级的优先执行,但不代表自己一定不是最先执行。yield()方法之后,回到,就绪转态,而非阻塞转态
t.join()
自己先执行,执行完之后,当前线程再执行。
线程同步
同步就是排队。多个线程共享一个资源,因为实际需要,需一个时间段只允许完整的允许完一个线程或线程的一段代码,可以用加锁的方式实现。
实现方法加锁,关键字synchronize----锁
异步就是并发。
public class Twoxianchengoneduixiang {
public static void main(String[] args) {
Zhong zhong1 = new Zhong();
Thread th1 = new Thread(zhong1, "zhong1");
Zhong zhong2 = new Zhong();
Thread th2 = new Thread(zhong1, "zhong2");
th1.start();
th2.start();
}
}
class Zhong implements Runnable {
int j = 0;
// @Override
public void run() {
synchronized (this) {
System.out.println("**************"+this);
System.out.println("&&&&&&&&&&&&&&&&&&"+this.getClass().toString());
for (int i = 0; i < 5; i++) {
j += i;
System.out.println(Thread.currentThread().getName() + ":" + j);
}
System.out.println(Thread.currentThread().getName() + ":" + j);
j=0;
}
}
}
锁定对象,注意:锁定的是对象,只能由一个线程对对象先执行完,再执行另一个线程对对象进行操作。
- 线程类run方法前修饰词 synchronize修饰方法紧锁定该方法,就是先由一个线程执行完该方法,再由别的线程调用,如果别的线程调用别的方法,与该被锁定的方法无关,别的方法正常执行。用synchonize修饰方法,效率低,一般不用。
public synchronize void run(){ } - 代码块
synchonize(this){ }
一个线程一个对象
解决线程安全问题,就是不共享资源
public class TwoxianchengTwoduixiang {
public static void main(String[] args) {
Zhi zhi1 = new Zhi();
Thread th1 = new Thread(zhi1,"zhi1");
Zhi zhi2 = new Zhi();
Thread th2 = new Thread(zhi2,"zhi2");
th1.start();
th2.start();
}
}
class Zhi implements Runnable {
int j = 0;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
j += i;
System.out.println(Thread.currentThread().getName() + ":" + j);
}
System.out.println(Thread.currentThread().getName() + ":" + j);
}
}
守护线程
以上讲的都是用户线程。
用户线程结束,守护线程才会结束。比如垃圾回收线程就是守护线程
设置守护线程
t.setDaemon(true)
public class ThreadTest14 {
public static void main(String[] args) {
Thread t = new BakDataThread();
t.setName("备份数据的线程");
// 启动线程之前,将线程设置为守护线程
t.setDaemon(true);
t.start();
// 主线程:主线程是用户线程
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class BakDataThread extends Thread {
public void run(){
int i = 0;
// 即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。
while(true){
System.out.println(Thread.currentThread().getName() + "--->" + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Timer 定时器
定时器
Timer timer = new Timer();
执行定时器的方法
方法 schedule() timer.schedule(new LogTimerTask() , firstTime, 1000 * 10);
执行的任务
class LogTimerTask extends TimerTask
public class TimerTest {
public static void main(String[] args) throws Exception {
// 创建定时器对象
Timer timer = new Timer();
// 指定定时任务
//timer.schedule(定时任务, 第一次执行时间, 间隔多久执行一次);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = sdf.parse("2020-03-14 09:34:30");
timer.schedule(new LogTimerTask() , firstTime, 1000 * 10);
}
}
// 编写一个定时任务类
class LogTimerTask extends TimerTask {
@Override
public void run() {
System.out.println(strTime + ":成功完成了一次数据备份!");
}
}
object与线程
wait()让正在对象上活动的线程进入等待状态,无期限等待 等待notify或者等待的时间完成后进入就绪状态
notify()唤醒wait()的对象,进入就绪转态。
调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程。
调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程。
wait()与sleep()使用区别
项目 | wait | sleep |
---|---|---|
同步 | 只能在同步上下文中调用wait方法,否则或抛出IllegalMonitorStateException异常 | 不需要在同步方法或同步块中调用 |
作用对象 | wait方法定义在Object类中,作用于对象本身 sleep方法定义在java.lang.Thread中 | 作用于当前线程 |
释放锁资源 | 是 | 否 |
唤醒条件 | 其他线程调用对象的notify()或者notifyAll()方法 | 超时或者调用interrupt()方法体 |
方法属性 | wait是实例方法 | sleep是静态方法 |
A线程占用了对象的锁,其他线程进入synchronized块要获取这个这个对象的锁时,由于对象锁被占用,所以让其他线程进入阻塞状态,进入同步队列等锁的释放。
当占有锁的A线程调用某对象的wait方法,线程会进入等待队列,同时释放锁。等到其他其他线程调用这个对象的notify/notifyAll方法时,A线程才重新进入synchronized同步队列去等待锁,此时状态也是阻塞状态
sleep,wait使用场景的区别:
Sleep一般用于使当前线程进入阻塞状态并且继续持有锁,一般是让线程等待。
wait一般用于多线程之间的通信,需要配合notify或者notifyall来进行使用,例如:生产者,消费者模式就需要使用wait,notify方法
public class Wait_notifyAll {
public static void main(String[] args) {
List list = new ArrayList();
Thread t1 = new Thread(new Shengchanzhe(list), "shenchan");
Thread t2 = new Thread(new Xiaofeizhe(list), "xiaofei");
t1.start();
t2.start();
}
}
class Shengchanzhe implements Runnable {
private List list;
public Shengchanzhe(List list) {
this.list = list;
}
@Override
public void run() {
while (true) {
synchronized (list) {
if (list.size() > 0) {
try {
list.wait();
System.out.println("仓库已有,可以消费");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object o = new Object();
list.add(o);
System.out.println("已生产,交给库房");
list.notifyAll();
}
}
}
}
class Xiaofeizhe implements Runnable {
private List list;
public Xiaofeizhe(List list) {
this.list = list;
}
@Override
public void run() {
while (true) {
synchronized (list) {
if (list.size() == 0) {
try {
list.wait();
System.out.println("仓库无,要生产了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.remove(0);
System.out.println("消费完,请生产");
list.notifyAll();
}
}
}
}
Thread.currentThread
获取当前类的实例对象。
实现线程的第三种方式:
实现Callable接口
public class ThreadTest15 {
public static void main(String[] args) throws Exception {
// 第一步:创建一个“未来任务类”对象。
// 参数非常重要,需要给一个Callable接口实现类对象。
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
// 线程执行一个任务,执行之后可能会有一个执行结果
// 模拟执行
System.out.println("call method begin");
Thread.sleep(1000 * 10);
System.out.println("call method end!");
int a = 100;
int b = 200;
return a + b; //自动装箱(300结果变成Integer)
}
});
// 创建线程对象
Thread t = new Thread(task);
// 启动线程
t.start();
// 这里是main方法,这是在主线程中。
// 在主线程中,怎么获取t线程的返回结果?
// get()方法的执行会导致“当前线程阻塞”
Object obj = task.get();
System.out.println("线程执行结果:" + obj);
// main方法这里的程序要想执行必须等待get()方法的结束
// 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
// 另一个线程执行是需要时间的。
System.out.println("hello world!");
}
}
总结:
在jvm中多个线程,并行运行,同时执行多个线程。也可以多个线程操作一个对象,如果需要对对象操作,有先后顺序,就需要同步,即排队,给需要锁定的对象,加锁,这样就可以排队操作此对象,注意:此对象可以是需要操作的变量,方法,或当前线程。
多线程运行,从启动,到就绪,到运行。到消亡,这是一整套的流程,其中程序员可以控制线程运行的状态,可以让其sleep,wait,退回到阻塞状态,再退回到就绪装填。也可以中断线程,也可以从sleep中唤醒线程。
线程可以设置运行的优先级,也可以设置让自己优先运行,join,也可以让其他线程先运行yield