目录
线程的创建的两种方式
线程的生命周期及其状态转换
线程调度
多线程同步
多线程通信
1.继承java.lang包下的Thread类,覆写Thread类的run方法,在run方法中实现运行在线程上的代码
public class Example {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
while(true) {
System.out.println("main方法在运行");
}
}
}
class MyThread extends Thread {
public void run() {
while(true) {
System.out.println("MyThread类的run方法在运行");
}
}
}
/*
运行结果:main方法在运行、MyThread类的run方法在运行 都有输出
*/
2.继承java.lang包下的Runnable接口,在run方法中实现运行在线程上的代码
public class Example {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
while(true) {
System.out.println("main方法在运行");
}
}
}
class MyThread implements Runnable {
public void run() {
while(true) {
System.out.println("MyThread类的run方法在运行");
}
}
}
/*
运行结果:main方法在运行、MyThread类的run方法在运行 都有输出
*/
二者比较:
1)第一种方法有一定的局限性,因为java仅支持单一继承,一个类一旦继承了某个父类就无法再继承Thread类了。
2)实现Runnable接口 相对于 继承Thread类 来说,适合多个相同程序代码的线程去处理同一资源的情况(例如下面售票厅的四个售票窗口共同发售同一种的车票100张的代码:),把线程同程序代码、数据有效的分离,很好的体现了面向对象的设计思想。
public class Example {
public static void main(String[] args) {
TicketWindow tw = new TicketWindow();
new Thread(tw, "窗口1").start(); //创建线程对象并命名窗口1,开启进程
new Thread(tw, "窗口2").start();
new Thread(tw, "窗口3").start();
new Thread(tw, "窗口4").start();
}
}
class TicketWindow implements Runnable {
private int tickets = 100;
public void run() {
while(true) {
if(tickets > 0) {
Thread th = Thread.currentThread(); //获取当前进程
String th_name = th.getName();
System.out.println(th_name+"正在发售第"+tickets--"张票");
}
System.out.println("MyThread类的run方法在运行");
}
}
}
后台线程:
1)新创建的线程默认都是前台线程,如果某个线程对象在启动之前调用了setDaemon(true)语句(daemon:[计]守护进程),这个线程就变成一个后台线程。
2)只有后台线程时,进程就会结束。
3)要将某个线程置为后台线程(setDaemon),必须在该线程启动之前(start)。
当Thread对象创建完成时,线程的生命周期便开始了
分时调度、抢占式调度,后者根据优先级机制,是jvm默认调度模型。可以通过Thread类的setPriority(int newPriority)的方法对其进行设置,newPriority的值为1-10之间的数字或Thread类的三个静态常量(static int MAX_PRIORITY(10)/MIN_PRIORITY(1)/NORM_PRIORITY(5))。虽然java提供了10个线程优先级,但是这些优先级需要操作系统的支持,不同的操作系统并不一定可以和优先级一一对应,因此,在设计多线程程序时,其功能一定不能依赖于线程的优先级,而只能作为一种提高程序效率的手段。
线程休眠:
静态方法sleep(long millis),可以让当前在执行的线程暂停一段时间(millis参数毫秒)。休眠结束进入就绪状态。
线程让步:
通过yield()实现,和sleep方法有些类似,都可以让当前正在运行的线程暂停,区别在于yield方法不会阻塞线程,只是转换为就绪状态,让系统的调度器重新调度一次。当某个线程调用yield方法之后,只有与当前线程优先级相同或更高的线程才能获得执行机会。
线程插队:
当在某个线程中调用其他线程的join()方法时,调用的线程将被阻塞,直到被join()加入的线程执行完成后才会继续运行。
同步代码块:
限制某个资源在同一时刻只能被一个线程访问。
为了使处理共享资源的代码在任何时刻只能有一个线程访问,可将此块代码放置在用synchronized修饰的代码块中:
synchronized(lock) {
}
public class Example {
public static void main(String[] args) {
TicketWindow tw = new TicketWindow();
new Thread(tw, "窗口1").start(); //创建线程对象并命名窗口1,开启进程
new Thread(tw, "窗口2").start();
new Thread(tw, "窗口3").start();
new Thread(tw, "窗口4").start();
}
}
class TicketWindow implements Runnable {
private int tickets = 100;
Object lock = new Object(); //定义任意一个对象,作为同步代码块的锁
public void run() {
while(true) {
synchronized{
try {
Thread.sleep(10);
} catch (InterruptedException e){ //InterruptedException为中断异常,因为访问该代码的其他线程会发生阻塞
e.printStackTrace();
}
if(tickets > 0) {
Thread th = Thread.currentThread(); //获取当前进程
String th_name = th.getName();
System.out.println(th_name+"正在发售第"+tickets--"张票");
} else {
break;
}
}
}
}
}
同步方法:
synchronized 返回值类型 方法名 ([参数...]){
}
该方法某一时刻只允许一个线程访问,访问该方法的其他线程会发生阻塞。
同步方法的锁就是就是当前调用该方法的对象,即this指向的对象。静态方法的锁是该方法所在类的class对象,该对象可以直接用"类名.class"的方式获取。
在Object类中提供了wait()、notify()(通告)、notifyAll()用于解决线程间通信问题,由于java中所有类都是Object类的子类或间接子类,因此任何类的实例都可以直接使用该方法。
void wait():使当前线程放弃同步锁并进入等待,知道其他线程进入此同步锁,并调用notify()方法或notifyAll()方法唤醒该进程为止
void notify():唤醒此同步锁上等待的第一个调用wait()方法的线程
void notifyAll():唤醒此同步锁上等待的所有调用wait()方法的线程
三个方法的调用者都应是同步锁对象,否则会抛出IllegalMonitorStateException异常。
例:两个线程同时去操作同一个存储空间(数组),其中一个线程负责向存储空间存入数据,另一个进程负责取出数据