什么是并行和并发?
并发和并行是即相似又有区别:(微观)
并行:指两个或多个事件在同一时刻发生; 强调的是时间点.
并发:指两个或多个事件在同一时间段内发生; 强调的是时间段.
进程和线程的区别?
进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的,又称为轻型进程或进程元。 因为一个进程中的多个线程是并发运行的,那么从微观角度上考虑也是有先后顺序的,那么哪个线程执行完全取决于CPU调度器,程序员是控制不了的。我们可以把多线程并发性看作是多个线程在瞬间抢CPU资源,谁抢到资源谁就运行,这也造就了多线程的随机性。
怎么创建进程?
创建进程的方式有两种,我以windows上的记事本为例:
package com.StadyJava.day14;
import java.io.IOException;
public class ProcessDemo {
public static void main(String[] args) throws Exception {
//方法1:使用Runtime
Runtime runtime=Runtime.getRuntime();
runtime.exec("notepad");
//方法2:ProcessBuild
ProcessBuilder processBuilder=new ProcessBuilder("notepad");
processBuilder.start();
}
}
运行代码,此时会生成两个记事本。
我也尝试过启动其他的程序,但是计算器不认识,只有notepad这种计算机自带的才认识。
如果想要启动其他的程序,只能写上绝对路径
ProcessBuilder processBuilder=new ProcessBuilder("E:\\shuyunquan\\TIM\\Bin\\TIM.exe");
processBuilder.start();
这样我测试了,是可以的,但是没意思,这样你写了程序发布了也搞怪不了,别人电脑上的路径和你不一样。。。
接下来讲解线程。
下图是线程的一些常用的方法
创建进程的两个方法已经知道了,接下来看看创建线程的两个方法
创建线程方法1:使用继承Thread类
package com.StadyJava.day14Thread;
import java.lang.Thread;
class Music extends Thread{
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("听音乐"+i);
}
}
}
public class MusicThread {
public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
System.out.println("打游戏"+i);
if (i == 10) {
Music music=new Music();
music.start();
}
}
}
}
注意,继承了Thread类之后要重写run方法,而且在调用的时候,只能使用start方法,不能调用run方法,切记!
输出的结果是在打游戏10之后,下面的打游戏和听音乐都是随机出现,因为主线程main和线程music在抢占资源,谁抢到谁执行,所以输出的结果是随机的
(注意!我使用Idea输出的结果是顺序的,不是随机的。我同学和我一样的代码使用Eclipse结果是随机的,我自己复制到Eclipse之后偶尔随机,Idea是死都不随机,这个我解决不了,希望以后知道原因或者有人告诉我)
创建线程的方法2:实现Runnable接口
其实Thread类也是实现了Runnable接口的,所以这个方法我感觉是本源??
package com.StadyJava.day14Thread;
import java.lang.Runnable;
class MusicRun implements Runnable{
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("听音乐"+i);
}
}
}
public class MusicRunnable {
public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
System.out.println("打游戏"+i);
if (i == 10) {
Runnable music=new MusicRun();
Thread thread=new Thread(music);
thread.start();
}
}
}
}
没什么特别的,就是生成一个runnable的对象,然后Thread对象加载进这个Runnable对象,最后start方法调用一下就完事了。这个结果我的Idea依然是显示的不符合我的期望的。
上面两种创建线程的方式是最常用的方式,一般也就足够了,下面介绍一下不怎么常用的
创建线程的方法3:匿名内部类创建线程
匿名内部类的格式:new 接口(){} 应该是这样的,待补充
其实匿名内部类创建线程还是使用上面的两种方式,只不过那个Music类我不需要去定义了,这就是匿名内部类,看代码吧
package com.StadyJava.day14Thread;
import java.lang.Runnable;
class MusicRun implements Runnable{
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("听音乐"+i);
}
}
}
public class MusicRunnable {
public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
System.out.println("打游戏"+i);
if (i == 10) {
//匿名内部类的形式1,使用接口
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("听音乐"+i);
}
}
}).start();
//匿名内部类的形式2,使用继承类
new Thread(){
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("听音乐"+i);
}
}
}.start();
}
}
}
}
这回,输出的结果总算是符合我的预期了,可能是线程变成了3个,抢占资源激烈了些。。。
学了两种常见的创建线程的方法之后,他们之间有什么区别呢?
Thread创建线程和Runnable创建线程的区别
我写个例子,吃苹果大赛,3个人参加比赛,先使用继承Thread类创建线程的方式,代码如下:
package com.StadyJava.day14;
class Person extends java.lang.Thread{
private int num=50;
public Person(String name){
super(name);
}
public void run() {
for (int i = 0; i < 50; i++) {
if (num >0) {
System.out.println(super.getName()+"吃了编号为"+num--+"的苹果");
}
}
}
}
public class EatAppleThread {
public static void main(String[] args) {
//创建3个人,去参加吃苹果大赛
new Person("许嵩").start();
new Person("林俊杰").start();
new Person("蜀云泉").start();
}
}
这个输出的结果,就是许嵩,林俊杰,蜀云泉每个人都吃了50 个?。原因是因为我new了三个对象,没个对象都有num变量,他们之间互不干扰,如下图所示:
这样很不好,我的吃苹果大赛是总共50个?,你们3个人来吃就完事了。我们看看实现Runnable接口创建线程的方式是怎么样的,代码如下:
package com.StadyJava.day14;
class Apple implements Runnable{
private int num=50;
public void run() {
for (int i = 0; i < 50; i++) {
if (num >0) {
//返回当前线程的引用Thread.currentThread(),再获取名字
System.out.println(Thread.currentThread().getName()+"吃了编号为"+num--+"的苹果");
}
}
}
}
public class EatAppleRunnable {
public static void main(String[] args) {
//创建3个人,去参加吃苹果大赛
Apple apple=new Apple();
new Thread(apple,"许嵩").start();
new Thread(apple,"林俊杰").start();
new Thread(apple,"蜀云泉").start();
}
}
这次因为我传入的都是apple这个对象,这一个对象有50个?,创建了3个线程,这次输出的结果是OK的,原因如下图所示,3个线程共用了一个苹果对象。
从这个吃苹果比赛的例子中可以总结一下继承Thread类创建线程的方式和实现Runnable接口创建线程的方式的区别:
继承Thread类创建线程方式:
- Java中类是单继承的,如果使用了继承Thread类创建线程,那么就 不能再有其他父类了,这是一个限制
- 从操作上来说,继承Thread类的方式更简单,获取线程名称也简单,直接getName就好了。操作简单,这是优点
- 从多线程共享资源的方面分析,继承方式不行,直接3个人,每个人50个苹果,没有实现共享,这是缺点
实现Runnable接口创建线程方式:
- 设计优雅,这是优点
- 从操作上分析,实现接口方式有点复杂,获取线程名称的时候,必须使用Thread.currentThread()来获取当前线程的引用
- 从多线程共享资源的方面上,实现接口方式可以做到共享资源,3个人去吃50个苹果,共享资源。这是优点
综合上面的区别对比,我们的这个比赛。看来只能使用实现Runnable接口创建线程的方式来实现了。推荐以后创建线程,都使用实现Runnable接口的方式。
线程安全问题
拿上面写的实现接口创建线程的吃苹果比赛为例,这个是存在线程安全的,我们可以写一个线程休眠来看看,这样更容易观察。
需要说明,Thread.sleep线程休眠,需要使用try catch来扑捉异常,不能使用throw抛出,因为Runnable接口里面的run方法本身就没有throw的写法
package com.day14;
class Apple implements Runnable{
private int num=50;
public void run() {
for (int i = 0; i < 50; i++) {
if (num >0) {
//线程休眠,必须使用try catch扑捉异常
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//返回当前线程的引用Thread.currentThread(),再获取名字
System.out.println(Thread.currentThread().getName()+"吃了编号为"+num--+"的苹果");
}
}
}
}
public class EatAppleRunnable {
public static void main(String[] args) {
//创建3个人,去参加吃苹果大赛
Apple apple=new Apple();
new Thread(apple,"许嵩").start();
new Thread(apple,"林俊杰").start();
new Thread(apple,"蜀云泉").start();
}
}
我就加了一个线程休眠,我们现在来看看输出的结果是什么样的
居然有0,还有-1 不是已经写了if(num>0)吗?出现这种情况的原因是,许嵩,林俊杰,蜀云泉这三个线程都在num>0的时候,例如num=1的时候,他们仨都拿到了资源,都可以去执行run方法
这个时候就会出现这种情况。通俗一点讲许嵩拿到了最后一个苹果,但是没吃,林俊杰和蜀云泉来抢。由于这个苹果不具备独占性,所以最后一个苹果被许嵩,林俊杰,蜀云泉都咬了一口。他们都宣称自己吃了苹果,所以就会出现0和-1的情况。这样显然是不允许的。这就是
多线程并发的访问一个资源产生的安全问题
要想解决这个问题,就必须要保证苹果的数量减少必须保证同步,许嵩拿了最后一个苹果,这个时候苹果数量同步为0,剩下的人不能再抢了。
许嵩这个线程在操作的时候,林俊杰和蜀云泉只能等着。等许嵩操作完了。许嵩,林俊杰和蜀云泉才有机会去重新抢资源。
意思是这样,方法有3种
方法1.同步代码块
方法2.同步方法
方法3.锁机制(Lock)
方法1:同步代码块
语法:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:为了保证每个线程都能单独的执行操作,java线程同步的机制。同步锁也叫
同步监听对象/同步锁/同步监听器/互斥锁
这些都是别名,就像茴香豆的“茴”字有几种写法一样,都是别名。
Java程序中的任何对象都可以作为同步监听对象,但是我们一般把多个线程同时访问的共享资源作为同步监听对象。监听其它的单独的对象有啥意义。注意,在任何时候,最多运行一个线程拥有同步锁。
代码如下:
package com.day14;
class Apple implements Runnable{
private int num=50;
public void run() {
for (int i = 0; i < 50; i++) {
//方法1:同步代码块
//由于我多个线程共享的是Apple对象,所以同步锁就是this,当前类的对象。不能使用num变量,因为num变量一直在变化
synchronized (this) {
if (num > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//返回当前线程的引用Thread.currentThread(),再获取名字
System.out.println(Thread.currentThread().getName() + "吃了编号为" + num-- + "的苹果");
}
}
}
}
}
public class EatAppleRunnable {
public static void main(String[] args) {
//创建3个人,去参加吃苹果大赛
Apple apple = new Apple();
new Thread(apple, "许嵩").start();
new Thread(apple, "林俊杰").start();
new Thread(apple, "蜀云泉").start();
}
}
运行结果如下:
这下不会出现抢苹果事件了,也不会出现数量为0和-1的情况了。
方法2:同步方法
synchronize修饰的方法就是同步方法,保证当前线程执行的时候,其它线程只能等待
语法:
synchronize public void Dowork(){
执行操作
}
同步锁:对于非static方法,同步锁就是this,对于静态方法,同步锁就是当前方法所在类的字节码对象(类.class)
注意!不能使用synchronize修饰run方法。
package com.day14;
class Apple implements Runnable{
private int num=50;
public void run() {
for (int i = 0; i < 50; i++) {
eat();
}
}
//方法2:同步方法,直接用synchronized修饰方法
synchronized private void eat(){
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//返回当前线程的引用Thread.currentThread(),再获取名字
System.out.println(Thread.currentThread().getName() + "吃了编号为" + num-- + "的苹果");
}
}
}
public class EatAppleRunnable {
public static void main(String[] args) {
//创建3个人,去参加吃苹果大赛
Apple apple = new Apple();
new Thread(apple, "许嵩").start();
new Thread(apple, "林俊杰").start();
new Thread(apple, "蜀云泉").start();
}
}
运行结果也是OK的
或许有人会想,既然方法加了个synchronize就线程安全了,那我把所有的方法都加上synchronize不就得了。答案是不行滴
synchronize的优缺点:
优点:保证了多线程并发访问时的同步操作,避免了多线程操作的安全问题。
缺点:使用synchronize同步方法/同步代码块会导致性能降低。
方法3:锁机制(Lock)
锁机制用到的是Lock这个接口,当然我们在写代码的时候使用的是Lock接口的一个实现子类,叫ReentrantLock
语法:
private final Lock lock=new ReentrantLock();
try{
线程操作代码
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
还是上面的吃苹果比赛,使用锁机制的代码如下:
package com.day14;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Apple implements Runnable{
//创建一个Lock接口的实现子类对象。ReentrantLock是Lock接口的一个子类的实现。
private final Lock lock=new ReentrantLock();
private int num=50;
public void run() {
for (int i = 0; i < 50; i++) {
eat();
}
}
//方法3:同步锁(Lock)的方式,这个方式和方法2的同步方式很类似。
private void eat(){
//进入方法首先上锁
lock.lock();
if (num > 0) {
try {
//返回当前线程的引用Thread.currentThread(),再获取名字
System.out.println(Thread.currentThread().getName() + "吃了编号为" + num-- + "的苹果");
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
//结束后,记得释放锁
lock.unlock();
}
}
}
}
public class EatAppleRunnable {
public static void main(String[] args) {
//创建3个人,去参加吃苹果大赛
Apple apple = new Apple();
new Thread(apple, "许嵩").start();
new Thread(apple, "林俊杰").start();
new Thread(apple, "蜀云泉").start();
}
}
这个锁机制是不是和synchronize同步方法很相像,只不过同步方法是synchronize修饰的方法,而锁机制是在方法里面上锁,释放锁。
锁机制和同步代码块/同步方法比,范围更广泛。也就是说锁机制包括了同步代码块和方法,而且范围更大,更加面向对象。上锁,释放锁都自己来写,还有创建实现Lock接口的对象。
用线程实现生产者消费者问题
现在来讲一个生产者和消费者的问题,讲定生产者生产一些东西,放到分享池中,然后消费者去分享池中消费东西,大概就是下图那样的展示:
这就是我们的生产者和消费者的模型,我们要根据这个写代码。记得加上上面学习的同步锁知识。
分享池代码:
package com.day15;
public class ShareResource {
private String name;
private String sex;
private int num;
private boolean isEmpty=true;
synchronized public void push(String name,String sex,int num) {
try {
while (!isEmpty) {//如果不为空的时候,生产者线程就等待
this.wait();
}
//开始生产,生产过后,要isEmpty变成为空,然后唤醒其它的线程
this.name=name;
this.sex=sex;
this.num=num;
isEmpty=false;
this.notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void popup(){
try {
while (isEmpty) {
this.wait();
}
System.out.println(this.name + this.sex + this.num);
isEmpty=true;
this.notifyAll();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
生产者代码:
package com.day15;
//生产者线程
public class Producer implements Runnable{
//分享池对象
public ShareResource shareResource=null;
public Producer(ShareResource shareResource) {
this.shareResource=shareResource;
}
@Override
synchronized public void run() {
for (int i = 0; i <50 ; i++) {
if (i % 2==0) {
shareResource.push("许嵩","男",i);
}
else{
shareResource.push("梦中人","女",i);
}
}
}
}
消费者代码:
package com.day15;
public class Consumer implements Runnable {
//分享池对象
public ShareResource shareResource=null;
public Consumer(ShareResource shareResource) {
this.shareResource=shareResource;
}
@Override
synchronized public void run() {
for (int i = 0; i <50 ; i++) {
shareResource.popup();
}
}
}
执行测试代码:
package com.day15;
//测试类
public class RunTest {
public static void main(String[] args) {
//创建生产者和消费者共同的资源对象
ShareResource resource=new ShareResource();
//启动生产者线程
new Thread(new Producer(resource)).start();
new Thread(new Producer(resource)).start();
//启动消费者线程
new Thread(new Consumer(resource)).start();
new Thread(new Consumer(resource)).start();
}
}
代码就是这些,我实行的是生产者生产一个东西,消费者就去消费,我这里是直接打印出来了。生产者生产之后就去wait休息,等到东西没了才开始干活。这里我们学到了两个新的方法
1.wait()方法:线程休眠,除了被唤醒,否则就会一直睡觉休息,和睡美人是差不多了,没有人唤醒是不会苏醒的。wait方法里面可以加参数,毫秒,就是没人唤醒的话就自己醒(好惨啊...)
2.notifyAll()方法:唤醒除自己以外所有的线程,还有一个方法是notify(),唤醒随机的一个线程
最后我们看看输出的结果:
通过上面同步方法synchronized和线程的wait方法,notify方法很好的完成了生产者和消费者的问题。这里需要说明的是,wait方法和notify方法都必须需要同步锁,那么,我现在想用Lock锁机制去完成生产者消费者问题,那该怎么办呢?
锁机制Lock完成生产者和消费者问题
锁机制Lock是无法使用wait和notify方法的,那使用锁机制的线程之间怎么进行通信呢?
从Java5开始就为锁机制的线程提供了Condition接口,用于线程直接的通信,主要使用的方法有:
1.await()方法,相当于wait()方法,线程睡眠
2.signal()方法,相当于notify()方法,随机的唤醒任意一个线程
3.signalAll()方法,相当于notifyAll()方法,唤醒除了自己以外的所有的线程
然后代码其实就修改了分享池的代码,放出来看一下:
package com.StadyJava.day15;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ShareResource {
private String name;
private String sex;
private int num;
private boolean isEmpty=true;
private final Lock lock=new ReentrantLock();
//创建lock锁机制的Condition对象
private Condition condition=lock.newCondition();
public void push(String name,String sex,int num) {
lock.lock();
try {
while (!isEmpty) {
condition.await();
}
//开始生产,生产过后,要isEmpty变成为空,然后唤醒其它的线程
this.name=name;
this.sex=sex;
this.num=num;
isEmpty=false;
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
synchronized public void popup(){
lock.lock();
try {
while (isEmpty) {
condition.await();
}
System.out.println(this.name + this.sex + this.num);
isEmpty=true;
condition.signalAll();
}
catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
其实没什么大变化,就是方法换了名字而已。
线程的死锁
死锁就是两个线程互相在等待对方释放锁,死锁的现象一旦出现,是解决不了的,所以死锁现象只能避免,不能解决。
关于死锁,有一个超级经典的例子,就是哲学家就餐问题
线程的6种状态
如下图所示,Java线程有6种状态,现在来介绍一下各种状态。
线程的核心内库(几个重要的方法)
线程有几个很重要的方法需要讲一下
1.线程睡眠,sleep方法
这个方法是不是和上面讲的wait方法很像?其实不一样,wait睡觉之后,同步锁就释放了。sleep睡觉的时候,同步锁是紧紧的抓住不松手的
这个方法大部分用来模拟网络延迟,因为你刷新网页的时候不是会转圈圈吗,可以模拟这个。代码中也有很多地方用这个来写东西,例如我想模拟一个定时炸弹,我可以这样写代码:
package com.StadyJava.day15;
public class ThreadDemo {
public static void main(String[] args) {
for (int i = 10; i > 0; i--) {
System.out.println("离爆炸还有"+i+"秒");
}
System.out.println("嘣,爆炸啦");
}
}
这样写,没问题吧,有问题的,我想定时,但是这个一运行,结果全出来了,不是想要的结果,所以我们可以加一个睡眠1秒来实现,代码如下:
package com.StadyJava.day15;
public class ThreadDemo {
public static void main(String[] args) throws Exception {
for (int i = 10; i > 0; i--) {
System.out.println("离爆炸还有"+i+"秒");
Thread.sleep(1000);
}
System.out.println("嘣,爆炸啦");
}
}
记得,sleep方法是要抛出异常的
这样就可以了,实现了倒计时的效果。还有几个例子,例如坦克发射子弹,那这个子弹肯定是不断位移的,我们设置好之后,子弹可能瞬间就打出去了,你根本看不到子弹的运行轨迹。为了仔细的观察,或者实现子弹慢速射击的一个要求,我们也可以去sleep一下。还有NBA投篮游戏也是,都可以去试试。
2.联合线程,join方法
线程的join方法表示一个线程等待另一个线程完成后才执行。就是说把当前线程和当前线程所在的线程联合成一个线程。join方法被调用之后,线程对象处于阻塞状态。
适用于A线程需要等到B线程执行完毕,再拿B线程的结果再继续运行A线程。写个代码
package com.StadyJava.day15;
class Join extends Thread{
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("join"+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
Join joinThread=new Join();
for (int i = 0; i < 50; i++) {
System.out.println("main"+i);
if (i == 1) {
joinThread.start();
}
else if (i == 20) {
joinThread.join();
}
}
}
}
运行结果如下:
Idea副线程想和主线程抢资源真难。。。。运行了好几次才抢到。。。我们可以看到在主线程等于1的时候,两个线程开始抢占资源打印输出。在主线程为20的时候,joinThread线程就调用了join方法,这个时候他们俩就变成了联合线程,主线程main开始进入阻塞状态,必须等到joinThread线程执行完毕才可以执行。
3.后台线程
在后台运行,其目的是为其他线程提供服务,也称为“守护线程。
JVM的垃圾回收器就是典型的后台线程。
特点:若所有的前台线程都死亡,后台线程自动死亡。
测试线程对象是否为后台线程:使用thread.isDaemon()。
前台线程创建的线程默认是前台线程,并且当且仅当创建线程是后台线程时,新线程才是后台线程。
设置后台线程:thread.setDaemon(true),该方法必须在start方法调用前,否则出现IllegalThreadStateException异常。
package com.StadyJava.day15;
class Daemon extends Thread{
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(super.getName()+"-"+super.isDaemon()+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
for (int i = 0; i < 50; i++) {
System.out.println("main"+i);
if (i == 1) {
Daemon daemon=new Daemon();
daemon.setDaemon(true);//设置线程为后台线程,必须先设置后台线程才能start开启,先start开启再设置会报错
daemon.start();
Thread.sleep(10);
}
}
}
}
就一个判断是否是后台线程的方法 isDaemon() 和一个设置线程为后台线程的方法 setDaemon(true)
如果没有了前台线程,后台线程会死亡。
4.线程的优先级
优先级有两个方法
1.setPriority() 获取当前线程的优先级,main线程的优先级是5,默认的都是5
2.setPriority() 设置线程的优先级,不同的操作系统是不一样的,Linux和Windows都不一样。但是有3个数字是统一的,分别是1,5,10 1是最低,10是最高。所以用这3个数字就可以了。
注意:并不是优先级高的线程一定先执行,而是说这个线程有更多的机会去执行。执行的几率大了一些。
下面看一个代码
package com.StadyJava.day15;
class PriorityThread extends Thread{
public PriorityThread (String name){
super(name);
}
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("我是"+getName()+i);
}
}
}
public class Priority {
public static void main(String[] args) {
PriorityThread priorityThread1=new PriorityThread("优先级高的线程");
PriorityThread priorityThread2=new PriorityThread("优先级低的线程");
//priorityThread1.setPriority(Thread.MIN_PRIORITY);也可以使用这个,但是数字更简单
priorityThread1.setPriority(10);
priorityThread2.setPriority(5);
priorityThread2.start();
priorityThread1.start();
}
}
看结果也知道,优先级高并不是一定先执行。
5.线程的礼让
古有孔融让梨,那么线程也有一个礼让的方法叫 yield方法。这个方法比较特别,他把自己执行的机会让给那些优先级高的线程,提出这个请求给调度器CPU,但是CPU可以同意这个请求也可以无视这个请求
这个yield方法和sleep方法的区别如下:
1.都可以使得当前处于运行状态的线程放弃执行的机会,让给其它线程
2.sleep方法会让给其它线程,随机的让。yield方法会让给那些优先级高的线程。
3.调用sleep方法后,线程会进入计时等待状态。调用yield方法后,线程会进入就绪状态。
这个yield方法一般是不用的。不使用。。。。。在调试和测试线程的时候,可能会重现多线程的错误,可能。。。。所以还是了解一下就好吧
6.线程的定时器和线程组
定时器,就是定时去执行啦,直接看代码
package com.StadyJava.day15;
import java.util.Timer;
import java.util.TimerTask;
class Vae extends TimerTask {
public void run() {
System.out.println("大家好,我是Vae");
}
}
public class TimerDemo {
public static void main(String[] args) {
new Timer().schedule(new Vae(),3000,1000);
}
}
注意,我的Vae类是继承的TimeTask类,不是Thread类。定时器的方法就是schedule方法,第一个参数就是TimeTask对象,第二个参数就是第几秒出现执行,第三个参数就是间隔多少秒执行一次。
线程组,就是多个相同的线程在一个组里面,就像老师讲课,给A同学讲一遍,再给B同学讲一遍,再给C同学讲一遍。。。。这样太麻烦。直接让ABC三个同学都过来,一起听就完事了。这就是线程组的意义
线程组的特点:
1.如果线程A创建了线程B,那么B和A一定是一组的。
2.一个线程一旦加入了线程组,一辈子就是这个组的,一天是不良人,一辈子都是不良人。
当Java程序运行时,JVM会创建一个main线程组,默认所有的线程都是main线程组的
线程的知识差不多就这些了,下面来问几个问题,看看都能不能回答
1.synchronized和Lock的区别是什么?
synchronized是一个修饰符,Lock是一个类。这是最本质的区别。
2.为什么wait方法和notify方法/notifyAll方法不在Thread类中,而在Object类中?
简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中,因为锁属于对象。