Java线程

基本概念

线程可以看作是程序中的执行路径,多线程指的就是程序中有多个执行路径同时执行。
线程是一个程序内部的顺序控制流。

线程和进程的区别
每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大开销。
线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,每个线程都有独立的运行栈和程序计数器(PC),线程切换的开销较小。
进程是一个静态概念,在程序中实际执行的,都是线程。
多进程:操作系统中同时运行多个任务(程序)。
多线程:在同一应用程序中有多个顺序流同时执行。
在同一个时间点,cpu只会执行一个线程,多个线程轮流得到cpu的时间片,只不过cpu的运行速度过于迅速,看起来像同时执行多个线程一样。但多核的cpu能够实实在在的同时执行多个线程。
VM启动时会有一个主方法(main方法)所定义的线程,该线程叫做主线程。

线程的创建和启动

每个线程都是通过某个特定Thread对象所定义的run()方法来完成其操作,方法run()称为线程体。
通过调用Thread类的start()方法来启动线程。
方式一
定义一个类,让其继承自Thread类,重写Thread类的run()方法,然后创建一个该类(线程类)对象(线程对象)。
实例

public class Thread1 extens Thread {
	//重写run()方法
	public void run() {
		for(int i = 0;i < 100;i++) {
			System.out.println("Thread1:" + i);
		}
	}
}
//测试类
public class ThreadTest {
	public static void main(String[] args) {
		//创建线程对象
		Thread1 t1 = new Thread1();
		//启动线程
		t1.start();
		for(int i = 0;i < 100;i++) {
			System.out.println("MainThread:" + i);
		}
	}
}

输出结果为
在这里插入图片描述
从打印结果可以分析,main方法中执行t1.start()方法之后,主线程中就分离出了一个执行路径,t1线程和主线程同时执行(实际上是两个线程轮流使用cpu,但获取cpu时间片时间的长短不确定)。
方式二
定义一个类,让其实现Runnable接口。然后创建一个该类(线程类)的对象,然后创建一个Thread类对象,以上面的线程对象为参数。
Runnable接口中只有一个方法run(),该方法用于定义线程运行体。
使用Runnable接口可以为多个线程提供共享的数据。
在实现Runnable接口的类的run()方法定义中可以使用Thread类的静态方法:
public static Thread currentThread()获取当前线程的引用。
实际上,Thread类也实现了Runnable接口。
实例

public class Runnable1 implements Runnable {
	//实现run()方法
	public void run() {
		for(int i = 0;i < 100;i++) {
			System.out.println("Runnable1:" + i);
		}
	}
}
//测试类
public class RunnableTest {
	public static void main(String[] args) {
		Runnable1 r1 = new Runnable1();
		Thread t1 = new Thread(r1);
		t1.start();
		for(int i = 0;i < 100;i++) {
			System.out.println("MainThread:" + i);
		}
	}
}

打印结果
在这里插入图片描述

线程控制的基本方法

在这里插入图片描述
sleep方法
可以调用Thread类的静态方法:
public static void sleep(long millis) throws InterruptedException;
使得当前线程休眠(暂时停止执行millis毫秒)。
实例

public class Thread2 extends Thread {
	public void run() {
		while(true) {
			//打印当前系统日期
			System.out.println("======" + new Date() + "======");
			try {
				//线程休眠1秒钟
				sleep(1000);
			} catch (InterruptedException e) {
				//当前线程的休眠被打断,while循环结束
				return;
			}
		}
	}
}
//测试类
public class SleepTest {
	public static void main(String[] args) {
		Thread2 t2 = new Thread2();
		t2.start();
		try {
			//主线程休眠10秒钟
			Thread.sleep();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//打断t2线程的休眠
		t2.interrupt();
	}
}

打印结果
在这里插入图片描述
上面代码的执行过程为,主线程执行到t2.start()时,t2线程开始执行,每个1秒打印当前系统时间,主线程开始休眠10秒钟,10秒过后,主线程执行t2.interrupt()方法,线程t2中的循环结束,停止打印。
join方法
该方法用于线程合并。
实例

public class Thread3 extends Thread {
	Thread3(String name) {
		super(name);
	}
	public void run() {
		for(int i = 0;i < 10;i++) {
			//打印线程名称
			System.out.println("I am " + getName());
		}
		System.out.println("======" + new Date() + "======");
		try {
			sleep(5000);
		} catch (InterruptedException e) {
			return;
		}
	}
}
//测试类
public class JoinTest {
	public static void main(String[] args) {
		Thread3 t3 = new Thread3();
		t3.start();
		try {
			//将t3线程与主线程合并
			t3.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("======" + new Date() + "======"):
		for(int i = 0;i < 10;i++) {
			System.out.println("I am MainThread");
		}
	}
}

打印结果
在这里插入图片描述
从打印结果可以看到,线程合并之后主线程要等到t3线程代码执行完之后才会执行。

yield方法
让出cpu,让其他线程有执行的机会。
实例

public class Thread4 extends Thread {
	Thread4(String name) {
		super(name);
	}
	public void run() {
		for(int i = 1;i <= 100;i++) {
			System.out.println(getName() + ": " + i);
			//当前线程打印到10、20、30...后让与其他线程执行
			if(i % 10 == 0) {
				yield();
			}
		}
	}
}
//测试类
public class YieldTest {
	public static void main(String[] args) {
		Thread4 t1 = new Thread4();
		Thread4 t2 = new Thread4();
		t1.start();
		t2.start();
	}
}

打印结果
在这里插入图片描述
线程状态转换
在这里插入图片描述
线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定先调度哪一个线程来执行。
线程优先级用数字表示,从低到高用数字1~10来表示,默认优先级为5.
Thread.MIN_PRIORITY=1;
Thread.NORM_PRIORITY=5;
Thread.MAX_PRIORITY=10;
获取当前线程优先级方法int getPriority().
设置线程对象优先级void setPriority(int newPriority);
如t1.setPriority(Thread.NORM_PRIORITY + 3)就是将t1线程的优先级设为8.
实例

public class Thread5 extends Thread {
	public void run() {
		for(int i = 0;i < 1000;i++) {
			System.out.println("T5======" + i):
		}
	}
}
public class Thread6 extends Thread {
	public void run() {
		for(int i = 0;i < 1000;i++) {
			System.out.println("T6======" + i):
		}
	}
}
//测试类
public class ThreadTest {
	public static void main(String[] args) {
		Thread5 t5 = new Thread5();
		Thread6 t6 = new Thread6();
		t5.start();
		t6.start();
	}
}

打印结果
在这里插入图片描述
可以看到,两个线程获取cpu时间片的使用权几乎相同。
然后稍微改一下测试类:

Thread5 t5 = new Thread5();
Thread6 t6 = new Thread6();
//为线程t5设置更大的优先级
t5.setPriority(Thread.NORM_PRIORITY + 3);
t5.start();
t6.start();

再次执行,打印结果为
在这里插入图片描述
可以看到,前面执行的线程几乎全是t5,t6几乎要等到t5执行完之后才能执行。

线程同步

多个线程访问同一资源可能会出现多种不可预知的结果。
实例

public class Timer {
	private static int num = 0;
	public void add(String name) {
		num++;
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(name + "你是第" + num + "个使用timer的线程");
	}
}
//测试类
public class SyncTest implements Runnable {
	Timer timer = new Timer();
	public void run() {
		timer.add(Thread.currentThread().getName());
	}
	public static void main(String[] args) {
		SyncTest syncTest = new SyncTest();
		Thread t1 = new Thread(syncTest);
		Thread t2 = new Thread(syncTest);
		t1.setName("t1");
		t2.setName("t2");
		t1.start();
		t2.start();
	}
}

打印结果
在这里插入图片描述
与想象中的"t1你是第1个使用timer的线程,t2你是第2个使用timer的线程"不一致。
这是因为t1线程执行调用add方法之后,num变成1,接着t1线程休眠1毫秒,t2线程执行接着调用add方法,num变成2,接着t1和t2线程相继醒过来,执行打印语句。其实不用写sleep()方法也可能会发生这种问题,只不过这样是将问题放大。
解决办法
加synchronized关键字
重新定义Timer类中的add方法

public void add(String name) {
	//锁定当前对象(被synchronized关键字包裹的代码执行过程中,不能被其他线程打断)
	synchronized(this) {
		num++;
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} 
		System.out.println(name + "你是第" + num + "个使用tImer的线程");
	}
}

或者直接在方法定义时在void前面加synchronized关键字,表示该方法为同步方法

public synchronized void add() {}

这样做是在当前方法的执行过程中,锁定当前对象。
再次执行测试方法,打印结果变成
在这里插入图片描述
synchronized关键字,又被称为对象互斥锁,保证了共享数据操作的完整性。每个对象都对应于一个可称为"互斥锁"的标记。该标记保证在任意时刻,只能有一个线程访问该对象。

死锁

当两个或两个以上的线程都再等待对方执行完毕才能往下执行时,就会发生死锁。
实例

public class DeadLockTest implements Runnable {
	public int flag = 1;
	static Object o1 = new Object,o2 = new Object();
	public void run() {
		System.out.println("flag:" + flag);
		if(flag == 1) {
			synchronized(o1) {
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized(o2) {
					System.out.println("1");
				}
			}
		}
		if(flag == 0) {
			synchronized(o2) {
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized(o1) {
					System.out.println("2");
				}
			}
		}
	}
	public static void main(String[] args) {
        DeadLockTest d1 = new DeadLockTest();
        DeadLockTest d2 = new DeadLockTest();
        d1.flag = 1;
        d2.flag = 0;
        Thread t1 = new Thread(d1);
        Thread t2 = new Thread(d2);
        t1.start();
        t2.start();
    }
}

输出结果
在这里插入图片描述
可以看到,程序在打印完flag=0,flag=1之后一直等待,进入死锁状态。
执行过程为:
线程t1启动,打印flag = 1,走第一个if分支,对o1对象加锁,然后线程进入休眠状态500毫秒;线程t2启动,打印flag = 0,走第二个if分支,对o2对象加锁,然后线程进入休眠状态500毫秒;500毫秒过后,线程t1只要再获得o2的对象锁就可以打印字符串1结束,线程t2只要再获得o1的对象锁就可以打印字符串2结束,于是就发生了死锁。
解决死锁的方法:只锁定一个对象,将锁的粒度加粗(比如锁定整个方法,而不是只锁定方法内的部分代码)。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值