什么是程序,进程和线程?
程序:是为了实现某些目的使用计算机语言编写的指令集。
进程:一个程序运行起来就会形成一个进程,程序退出后进程也就结束了。进程有独立的内存和数据空间。
线程:轻量级的进程。线程是进程里面不同的路径,或独立的任务。
多线程的实现
1.继承Thread类
继承Thread类,并且重写Thread类里面的run方法,再创建对象并且调用start方法。
public class TestThread extends Thread {
public void run(){ //重写run方法
for(int i=0;i<10;i++){
System.out.println("Thread线程—->"+i);
}
}
public static void main(String[] args) {
TestThread thread = new TestThread();
thread.start();
for(int i=0;i<10;i++){
System.out.println("mian方法-->"+i);
}
}
}
运行结果:
mian方法-->0
Thread线程—->0
mian方法-->1
Thread线程—->1
mian方法-->2
Thread线程—->2
mian方法-->3
Thread线程—->3
mian方法-->4
Thread线程—->4
mian方法-->5
Thread线程—->5
mian方法-->6
Thread线程—->6
mian方法-->7
Thread线程—->7
mian方法-->8
mian方法-->9
Thread线程—->8
Thread线程—->9
实现Runnable接口里面的run方法,创建对象,并传给Thread类,创建一个对象,使用Thread类调用start方法。
public class TestThread implements Runnable {
public static void main(String[] args) {
TestThread t=new TestThread();
Thread thread = new Thread(t);
thread.start();
for(int i=0;i<10;i++){
System.out.println("mian方法-->"+i);
}
}
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("TestThread线程-->"+i);
}
}
}
运行结果:
mian方法-->0
TestThread线程-->0
mian方法-->1
TestThread线程-->1
mian方法-->2
TestThread线程-->2
mian方法-->3
TestThread线程-->3
mian方法-->4
TestThread线程-->4
mian方法-->5
TestThread线程-->5
mian方法-->6
TestThread线程-->6
mian方法-->7
TestThread线程-->7
mian方法-->8
TestThread线程-->8
mian方法-->9
TestThread线程-->9
可以观察到使用线程的时候程序此时的运行并不是以往的一个方法运行完之后才运行另一个方法,而是开辟了一条独立的路径出来运行。
注意:
1.启动线程只能调用Thread创建对象,并使用start方法启动线程。如果调用run方法的话,这个时候就不会是启动一个线程了,而是我们以往的调用方法,等run方法结束之后其他的代码才可以运行。
2.当main方法运行的时候jvm(java虚拟机)就会给我们创建一个线程,我们把这个纤尘称为主线程。
TestThread线程-->0
TestThread线程-->1
TestThread线程-->2
TestThread线程-->3
TestThread线程-->4
TestThread线程-->5
TestThread线程-->6
TestThread线程-->7
TestThread线程-->8
TestThread线程-->9
mian方法-->0
mian方法-->1
mian方法-->2
mian方法-->3
mian方法-->4
mian方法-->5
mian方法-->6
mian方法-->7
mian方法-->8
mian方法-->9
Runnbale接口和Thread类的比较
下面来模拟一下抢票系统来对比一下两者的区别:
Runable接口:
public class TestThread implements Runnable {
public static void main(String[] args) {
TestThread t=new TestThread();
Thread thread1 = new Thread(t,"甲");
Thread thread2 = new Thread(t,"乙");
thread1.start();
thread2.start();
}
private int num=10;
@Override
public void run() {
while(true){
if(num<0){
break;
}
System.out.println(Thread.currentThread().getName()+"抢到了一张票,剩余的票数:"+num--);
}
}
}
运行结果:
甲抢到了一张票,剩余的票数:10
乙抢到了一张票,剩余的票数:9
甲抢到了一张票,剩余的票数:8
乙抢到了一张票,剩余的票数:7
甲抢到了一张票,剩余的票数:6
乙抢到了一张票,剩余的票数:5
甲抢到了一张票,剩余的票数:4
乙抢到了一张票,剩余的票数:3
甲抢到了一张票,剩余的票数:2
乙抢到了一张票,剩余的票数:1
甲抢到了一张票,剩余的票数:0
继承Thread类:
public class TestThread extends Thread {
public static void main(String[] args) {
TestThread thread1 = new TestThread("甲");
TestThread thread2 = new TestThread("乙");
thread1.start();
thread2.start();
}
private int num=10;
private String name;
public TestThread(String name) {
this.name = name;
}
@Override
public void run() {
while(true){
if(num<0){
break;
}
System.out.println(name+"抢到了一张票,剩余的票数:"+num--);
}
}
}
运行结果:
甲抢到了一张票,剩余的票数:10
乙抢到了一张票,剩余的票数:10
甲抢到了一张票,剩余的票数:9
乙抢到了一张票,剩余的票数:9
甲抢到了一张票,剩余的票数:8
乙抢到了一张票,剩余的票数:8
甲抢到了一张票,剩余的票数:7
乙抢到了一张票,剩余的票数:7
甲抢到了一张票,剩余的票数:6
乙抢到了一张票,剩余的票数:6
甲抢到了一张票,剩余的票数:5
乙抢到了一张票,剩余的票数:5
甲抢到了一张票,剩余的票数:4
乙抢到了一张票,剩余的票数:4
甲抢到了一张票,剩余的票数:3
乙抢到了一张票,剩余的票数:3
甲抢到了一张票,剩余的票数:2
乙抢到了一张票,剩余的票数:2
甲抢到了一张票,剩余的票数:1
乙抢到了一张票,剩余的票数:1
甲抢到了一张票,剩余的票数:0
乙抢到了一张票,剩余的票数:0
对比运行结果我们可以明显的看到使用Runnable接口可以实现数据共享。
所以在实现线程的时候推荐使用Runable结构有以下优点:
1.避免单继承的局限性;
2.更加便于数据共享。
线程的运行状态
1.创建:new一个对象的时候。
2.就绪状态:当我们创对象之后调用它start方法的时候。
3.运行状态:当cpu调用我们调用线程的时候。
4.阻塞状态:由于某些原因线程放弃了cpu的使用权,暂停使用。直到进入就绪状态之后才可能继续执行。
5.死亡:线程执行完之后。Thread类里有一个isAlive方法可以检测线程是否还活着。
注意:并不是我们调用start方法之后线程就立马执行,这个时候还要看cpu,cpu什么时候调用线程,那么线程就处于运行状态了。
sleep,jion,yield方法
sleep:为了不让线程独占cpu,Thread类里面有一个静态方法sleep,可以让线程进入休眠状态。它被重写了,一个参数为毫秒,另一个参数为毫秒和纳秒。但是一般的虚拟机都精确不到纳秒,所以一般使用前者。他还抛出InterruptedException
- 如果任何线程中断了当前线程。当抛出该异常时,当前线程的中断状态 被清除。
jion:等线程结束。使用jion方法之后,一个线程就会进入阻塞状态,并且等待一个线程结束之后才会运行。
下面是一个例子:
public class TestThread implements Runnable {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"线程开始");
TestThread t =new TestThread();
Thread thread =new Thread(t);
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程开始");
}
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println("子线程-->"+i);
}
}
}
运行结果:main线程开始
子线程-->0
子线程-->1
子线程-->2
子线程-->3
子线程-->4
main线程开始
大家可以想一下不加jion方法会是一种什么情况。
yield:Thread类里面的静态方法。当线程执行到某一个条件的时候,让出cpu的使用权,线程进入就绪状态。
下面是一个例子:
public class TestThread implements Runnable {
public static void main(String[] args) {
TestThread t =new TestThread();
Thread thread =new Thread(t);
thread.start();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
@Override
public void run() {
for(int i=0;i<10;i++){
if(i%2==0){
Thread.yield();
}
System.out.println("子线程-->"+i);
}
}
}
运行结果:
子线程-->0
main-->0
子线程-->1
子线程-->2
子线程-->3
main-->1
子线程-->4
main-->2
子线程-->5
main-->3
子线程-->6
main-->4
子线程-->7
main-->5
子线程-->8
main-->6
子线程-->9
main-->7
main-->8
main-->9
注意:当执行到某一个条件的时候该线程让出了cpu的占有权,进入就绪状态之后,cpu可能又会继续调用这个线程。线程的优先级
调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
static int MAX_PRIORITY
线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
分配给线程的默认优先级,取值为5。
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
需要注意的就是,线程的优先级变大了之后,并不是优先级小的线程要等优先级大的线程运行完之后在运行,而是优先级大的线程运行的概率会变大,优先级小的线程还是会允许但是运行的概率变小了。
线程的同步
下面我们看这样一个例子:
public class TestThread implements Runnable {
public static void main(String[] args) {
TestThread t=new TestThread();
Thread thread1 = new Thread(t,"甲");
Thread thread2 = new Thread(t,"乙");
thread1.start();
thread2.start();
}
private int num=10;
@Override
public void run() {
while(true){
if(num<0){
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了一张票,剩余的票数:"+num--);
}
}
}
运行结果为:
乙抢到了一张票,剩余的票数:9
甲抢到了一张票,剩余的票数:10
甲抢到了一张票,剩余的票数:8
乙抢到了一张票,剩余的票数:7
甲抢到了一张票,剩余的票数:6
乙抢到了一张票,剩余的票数:5
甲抢到了一张票,剩余的票数:4
乙抢到了一张票,剩余的票数:3
甲抢到了一张票,剩余的票数:2
乙抢到了一张票,剩余的票数:1
甲抢到了一张票,剩余的票数:0
乙抢到了一张票,剩余的票数:-1
我们在上面模拟的抢票系统里面加了休眠之后我们可以看到线程发生了错误剩余的票数为负数。
这是什么原因呢?
是因为当票数为一张的时候,甲先抢到了一张票,但是之后进入了休眠状态,此时因为票数还没减1,所以之后乙执行了这个线程,当甲抢完票之后,票数就会变成0,所以乙再抢票数就会变成负数。
那么怎么解决呢?
使用synchronized关键字进行同步。
synchronized有两种使用方法:
1.在方法上面使用后其格式为:
权限修饰符 synchronized 类型 methodName{
}
在某个对象实例中,使用同步方法可以避免多个线程同时访问这个对象的方法。
2.使用synchronized锁住对象,其格式为:
权限修饰符 类型 methodName{
synchronized(要锁住的对象){
}
}
下面我们就是用锁住对象的方法对下面的例子进行更改:
public class TestThread implements Runnable {
public static void main(String[] args) {
TestThread t=new TestThread();
Thread thread1 = new Thread(t,"甲");
Thread thread2 = new Thread(t,"乙");
thread1.start();
thread2.start();
}
private int num=10;
@Override
public void run() {
while(true){
synchronized(this){
if(num<0){
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了一张票,剩余的票数:"+num--);
}
}
}
}
运行结果:
甲抢到了一张票,剩余的票数:10
甲抢到了一张票,剩余的票数:9
甲抢到了一张票,剩余的票数:8
甲抢到了一张票,剩余的票数:7
乙抢到了一张票,剩余的票数:6
甲抢到了一张票,剩余的票数:5
乙抢到了一张票,剩余的票数:4
乙抢到了一张票,剩余的票数:3
乙抢到了一张票,剩余的票数:2
乙抢到了一张票,剩余的票数:1
甲抢到了一张票,剩余的票数:0
有兴趣的人可以试试第一种方法进行同步。