线程的认识
创建线程的几种方式
创建一个类继承Thread类,并重写run方法; 创建一个类实现Runnable接口 并重写run方法; 实现callable接口 重写call方法 这种方式可以通过futureTask获取任务执行的返回值;
为什么要重写run()方法,
//默认的run()方法不会做任何事情; //为了让线程执行一些实际任务 需要我们重写run()方法;
run()方法和start()方法有什么区别?
//run()封装线程执行的代码,直接调用相当于调用普通方法. //start 启动线程 然后由jvm调用此线程的run方法().
通过继承 Thread 的方法和实现 Runnable 接口的方式创建多线程,哪个好?
避免了java单继的局限性,java不支持多重继承 如果我们的类已经继承了另一个类,就不能继承Thread类了; 适合多个相同的程序代码去处理同一资源的情况,把线程 代码和数据有效的分离,更符合面向对象的设计思想.Callable接口与runnable非常相似,但可以返回一个结果
如何实现多个线程之间共享数据
//如果每个线程执行的代码相同; //可以使用同一个runnable对象,这个runnable对象中有那个共享数据; 例如 买票系统;
如果每个线程执行的代码不同;
//将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个runnable对象。 //--> 将数据封装到一个对象上: ShareData2 data1 = new ShareData2(); [100 对其+-操作] //每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信. //--> 在runnable的构造函数中直接传入去操作; //--> public MyRunnable1(ShareData2 data){ }new Thread(new MyRunnable1(data1)).start();
//设计思想:一个类提供和操作数据的同步方法,另外定义2个线程通过构造函数接收并操作数据,在主函数中直接创建线程对象, //既可完成操作(也可以实现两个内部类,不用构造方法传值,使用final定义data局部变量) //实现: 设计4个线程 ,其中2个线程每次对j增加1 另外两个线程每次对j减一
//要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现他们之间的同步互斥或通信。 //简单的方式,在任意一个类中定义一个static的变量, 这将被所有线程共享;
static 静态变量只存储一处 被所有对象使用;被类的所有实例共享. 无论一个类创建了多少个对象 它们都共享同一份静态变量;
对编程java的理解_ruiguang21的博客-优快云博客
永久代和元空间
[字符串常量和静态类型变量存储在普通的堆区中],这部分线程共享,只会加载一次
控制线程的其他方法有:
//sleep() 使当前正在执行的线程暂停制定的毫秒数,进入休眠状态;没有让出cpu的执行权; //join 等待这个线程执行完才会轮到后续线程等到cpu的执行权,需要捕获异常; //t1.start(); t1.join(); //等待t1执行完才会轮到t2,t3抢t2.start();t3.start(); //setDaemon() 将此线程标记为守护线程,服务其他线程,java中的垃圾回收线程;就是典型的守护线程; //如果其他线程都执行完毕,main方法也执行完毕,jvm 就会退出,也就是停止运行。如果jvm停止运行 守护线程自然就停止了。
//等待: wait(),wait(long timeout),wait(long timeout, int nanos),join() //通知: notify() notifyAll() //让出优先权:yiled() //中断:interrupt() //休眠:sleep()
sleep()和wait() 有什么区别?
所属类不同:对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
语法使用不同 wait 方法必须配合 synchronized 一起使用,不然在运行时就会抛出 IllegalMonitorStateException 的异常,如下代码所示:
而 sleep 可以单独使用,无需配合 synchronized 一起使用。
初看代码好像没啥问题,编译器也没报错,然而当我们运行以上程序时就会发生如下错误:
线程进入状态不同: 调用 sleep 方法线程会进入 TIMED_WAITING 有时限等待状态,而调用无参数的 wait 方法,线程会进入 WAITING 无时限等待状态。
public static void main(String[] args) throws InterruptedException {
Object lock=new Object();
Thread thread = new Thread(() -> {
获取obj对象的同步锁;
synchronized (lock) {
//休眠2s
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
Thread thread1 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});thread1.start();
Thread.sleep(200);
System.out.println("wait() 之后进入状态 "+thread.getState());
System.out.println("sleep() 之后进入状态 "+thread1.getState());
//wait() 之后进入状态 WAITING
//sleep() 之后进入状态 TIMED_WAITING
}
//唤醒方式不同 //sleep 方法必须要传递一个超时时间的参数,且过了超时时间之后,线程会自动唤醒。 // 而 wait 方法可以不传递任何参数,不传递任何参数时表示永久休眠,直到另一个线程调用了 notify 或 notifyAll 之后,休眠的线程才能被唤醒。 // 也就是说 sleep 方法具有主动唤醒功能,而不传递任何参数的 wait 方法只能被动的被唤醒。
总结:
sleep()和wait()都是线程暂停执行的方法。 1、这两个方法来自不同的类分别是Thread和Object,sleep方法属于Thread类中的静态方法,wait属于Object的成员方法。 2、sleep()是线程类(Thread)的方法,不涉及线程通信,调用时会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,用于线程间的通信,调用时会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才进入对象锁定池准备获得对象锁进入运行状态。 3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)。 4、sleep()方法必须捕获异常InterruptedException,而wait()\notify()以及notifyAll()不需要捕获异常。
- 类的不同:sleep() 来自 Thread,wait() 来自 Object。
- 释放锁:sleep() 不释放锁;wait() 释放锁。
- 用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。
解释说明: sleep:Thread类中定义的方法,表示线程休眠,会自动唤醒; wait:Object中定义的方法,需要手工调用notify()或者notifyAll()方法。 sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。 wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。 sleep就是正在执行的线程主动让出cpu,cpu去执行其他线程,在sleep指定的时间过后,cpu才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep方法并不会释放锁,即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住了的线程也无法得到执行。wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了notify方法(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果notify方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在notfiy方法后增加一个等待和一些代码,看看效果),调用wait方法的线程就会解除wait状态和程序可以再次得到锁后继续向下运行。
yeild 让出优先权 yield 底层是有wait实现的是 会释放掉锁
yield 让出cpu执行权 处于就绪状态,可能刚刚放弃 马上又获取CPU执行权限,重新开始执行 yield 底层是有wait实现的是 会释放掉锁
yield() :Thread类中的静态方法,当一个线程调用 yield 方法时,实际就是在暗 示线程调度器当前线程请求让出自己的CPU ,但是线程调度器可以无条件忽略这 个暗示。
/**
sleep让线程从运行状态进入阻塞状态,但是不放开手中的资源。
yield让线程从运行状态进入就绪状态,释放手中资源,再次等到CPU调度。
*/
public class SleepYiledStatus {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
Thread.yield();//yield 让出cpu执行权 处于就绪状态,可能刚刚放弃 马上又获取CPU执行权限,重新开始执行 yield 底层是有wait实现的是 会释放掉锁
System.out.println(Thread.currentThread().getName() +"子线程" + i);
}
});
thread.start();
System.out.println("yield() 之后进入状态 "+thread.getState());
//开启主线,每循环10次进行yield
for(int i=0;i<100;i++) {
//Thread.sleep(200);
System.out.println("主线程"+i);
}
}
}
yield() 之后进入状态 RUNNABLE
主线程0
主线程1
主线程2
主线程3
主线程4
Thread-1子线程0
Thread-0子线程0
Thread-0子线程1
yield的中文意思是放弃,投降的意思。当前线程调用yield的时候,是告诉虚拟机它愿意让其他的线程抢占自己的位置或者表明该线程没有紧急的事要做,但这只是一种暗示,并不能保证一定会发生。
yield()的作用
Thread.yield()方法的作用:暂停当前正在执行的线程,并执行其他同等优先级的线程。(可能没有效果哦)
yield()让当前正在运行的线程回到可运行状态(runnable),以允许具有相同优先级的其他线程获得运行的机会。因此,使用yield(的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield(达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
结论:大多数情况下,yield()将导致线程从运行状态转到可运行状态,但很有可能没有效果。
使用yield方法时要注意的几点内容:
1、yield是一个静态的本地方法 (native)
2、调用yield后,yield告诉当前线程把运行机会交给线程池中有相同优先级的线程.
3、yield不能保证,当前线程迅速从运行状态切换到就绪状态。
4、yield只能是将当前线程从运行状态转换到就绪状态,而不能是等待或者阻塞状态
public class YieldPriority {
public static final int MAX_TURN=10;//执行次数
public static AtomicInteger index=new AtomicInteger(0);//执行编码
//记录线程的执行次数
public static Map<String,AtomicInteger> metric=new HashMap<>();
private static void printMetric() {
System.out.println("metric--->"+metric);
}
static class YieldThread extends Thread{
static int threadSeqNumber =1;
public YieldThread() {
super("sleepThread-"+threadSeqNumber);
threadSeqNumber++;
//将线程加入执行次数统计map
metric.put(this.getName(), new AtomicInteger(0));
}
@Override
public void run() {
for(int i=0;i<MAX_TURN&&index.get()<MAX_TURN;i++) {
System.out.println(Thread.currentThread().getName()+"--num :--"+i+" 线程优先级: "+getPriority());
index.incrementAndGet();
//统计一次
metric.get(this.getName()).incrementAndGet();
if(i%2 ==0) {
System.out.println("每次是2的倍数 就进行让出 yield "+i);
//让步: 出让执行的权限
Thread.yield();
}
}
//输出所有线程的执行次数
printMetric();
System.out.println(getName()+" 运行结束");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1= new YieldThread();
//设置为最高的优先级
thread1.setPriority(Thread.MAX_PRIORITY);
Thread thread2 = new YieldThread();
thread2.setPriority(Thread.MIN_PRIORITY);
System.out.println("---启动线程 --");
thread1.start();
thread2.start();
Thread.sleep(100);
}
}
join 等待这个线程执行完才会轮到后续线程等到cpu的执行权,需要捕获异常;
t1.start(); t1.join(); //等待t1执行完才会轮到t2,t3抢t2.start();t3.start(); 由于join的内部实现是wait(),所以使用join()方法是会释放锁的
setDaemon() 将此线程标记为守护线程,服务其他线程,java中的垃圾回收线程;就是典型的守护线程;
interrupt
Java 中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。 void interrupt() :中断线程,例如,当线程A运行时,线程B可以调用钱程 interrupt() 方法来设置线程的中断标志为true 并立即返回。设置标志仅仅是设置标 志, 线程A实际并没有被中断, 会继续往下执行。 boolean isInterrupted() 方法: 检测当前线程是否被中断。 boolean interrupted() 方法: 检测当前线程是否被中断,与 isInterrupted 不同的 是,该方法如果发现当前线程被中断,则会清除中断标志。
sleep和yiled的区别
sleep让线程从运行状态进入阻塞状态,但是不放开手中的资源。 yield让线程从运行状态进入就绪状态,释放手中资源,再次等到CPU调度。
调用yield()的线程会被返回到可执行状态,让其他同等优先级或者更高优先级的线程有执行的机会 yield 让出cpu执行权 处于就绪状态,可能刚刚放弃 马上又获取CPU执行权限,重新开始执行 yield让线程从运行状态进入就绪状态,释放手中资源,再次等到CPU调度。 yield()将导致线程从运行状态转到可运行状态 yield()让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。 因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。 但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
sleep join wait
Thread.sleep();带时限的抢占对象的monitor锁 Thread.join();带时限的线程合并 LockSupport.parkNanos();让线程等待,时间以纳秒为单位 LockSupport.parkUntil();让线程等待,时间可以灵活设置
sleep()让当前线程进入阻塞状态,不会释放“锁” sleep让线程从运行状态进入阻塞状态,但是不放开手中的资源
wait() 会释放掉锁,让其他线程能可以竞争该锁。 线程的通信可以被定义为 [当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态;以避免无效的资源争夺;]
join() 调用join()的线程执行完以后,其他线程才能执行 -- 由于join的内部实现是wait(),所以使用join()方法是会释放锁的
线程的优先级:
1、在没有指定线程的优先级的时候,线程都带有普通的优先级。
2、线程的优先级可以分为1到10;10代表最高的优先级,1代表最低的优先级,普通优先级是5。
3、优先级最高的线程在运行时给予优先,但不能保证线程启动后立刻就进入运行状态。
4、与线程池中等待的线程相比,正在运行的线程拥有更高的优先级。
5、由调度程序来决定执行哪一个线程。
6.setProperty()来设定用线程的优先级。
7.在线程调用start()方法调用之前,应该指定线程的优先级。
java线程调度是抢占式的调度, 如果我们想让系统能给某些线程多分配一些时间 给一下线程少分配一些时间,可以设置线程的优先级来完成;
java语音一种10个级别的线程优先级(Thread.Min_priority至Thread.Max_priority),在两个线程同时处于ready状态时,优先级越高的线程越容易被系统选择执行(注意: 不是一定)
但优先级并不是很靠谱,因为java线程是通过映射到系统的原生线程上来实现的,所以线程调度最终是取决于操作系统;
线程有几种状态
在操作系统层面,Java 线程中的 BLOCKED、WAITING、TIMED_WAITING 是一种状态,即前面我们提到的休眠状态。
也就是说只要 Java 线程处于这三种状态之一,那么这个线程就永远没有 CPU 的使用权!!!
yield() 之后进入状态 RUNNABLE
wait() 之后进入状态 WAITING
sleep() 之后进入状态 TIMED_WAITING
//sleep(2000)会让当前线程从running进入timed_waiting状态,把执行机会给其他线程,监控状态依然保存,到时会自动恢复。不会释放对象锁; //yield[释放时间片]会释放cpu资源,让当前线程从running进入runnable状态,让优先级更高(至少是相同)的线程获得执行机会,不会释放对象锁; //等待调用join()方法的线程结束之后(terminated,之前是runnable),程序在继续执行,一般用于等待异步线程执行完成结果之后才能继续运行的场景;主线程等待线程t执行完成 //join(2000)进入TIMED_WAITING;当join的时间大于执行时间进入timed_waiting状态; //wait() 之后进入状态 WAITING/wait(2000)之后会从runnbale 进入timed_waiting状态; //stop()方法已经被jdk废弃,原因就是stop()方法台过于暴力,强行吧执行到一半的线程终止;
WAITING
WAITING状态是线程在获取锁对象之后,由于某些原因需要等待一些事件的完成才能继续执行,这时线程调用Object.wait()、Thread.sleep()、Thread.join()等方法进入WAITING状态.
wait和notify方法要在同步块中调用(防止wait的线程没有接收到其他线程发送的notify消息),wait会释放锁,但notify方法并不会真正释放锁,必须等到它所在的同步代码块执行完毕才能完成锁对象的释放。
sleep方法不必在同步块中调用,而且sleep方法不会释放锁。(个人理解,sleep方法需要时间参数,到达指定时间后线程自动苏醒,所以不需要类似wait那样的严格要求)
join方法底层使用wait方法,并且join方法本身就是同步方法(锁对象是调用join方法的线程本身),所以和sleep一样不必在同步块调用,并且jvm在关闭线程之前会检测线阻塞在线程锁对象上的其它线程,然后执行notifyAll(),这样就实现了唤醒阻塞线程的功能 。
BLOCKED
而BLOCKED状态则是线程在准备进入某个同步代码块时,发现锁对象已经被其它线程占用了,这时线程就会进入BLOCKED状态