多线程实现的两种方式
(1) 创建线程类
- 继承Thread类
- 或 实现Runnable接口
(2) 通过Thread类构造器来创建线程对象
- Thread( )
- Thread(Runnable target)
(3) 通过start()方法激活线程对象
• run( )方法 — 线程运行体
要将一段代码(线程体)在一个新的线程上运行,该代码应该在一 个线程类的run( )函数中
- 写一个类implements Runnable接口,且必须覆盖Runnable接口 中的run( )方法
- 写一个类extends Thread类,且必须重写Thread类的run( )方法
run( ) 与 start( )的区别
1、start()方法
用start方法来启动线程,是真正实现了多线程,通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法。但要注意的是,此时无需等待run()方法执行完毕,即可继续执行下面的代码。所以run()方法并没有实现多线程。
2、run()方法
run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码。
继承Thread类和实现Runnable接口两种方式的比较
- 使用Runnable接口可以避免由于JAVA的单继承性带来的局限
- 适合多个相同的程序代码的线程去处理同一资源情况,把线程同 程序的代码、数据有效的分离(推荐使用实现Runnable接口)
线程状态及其生命周期
一个 Thread 对象在它的整个生存期中能以几种不同的状态存在
start( ) — 方法使线程处于可以运行的状态,但 不一定意味着该线程立即开始运行
线程常用方法
方法 | 功能 |
---|---|
isAlive() | 判断线程是否还活着 |
getPriority() | 获取线程的优先级 |
setPriority() | 设置线程的优先级 |
Thread.sleep() | 将当前线程睡眠指定毫秒数 |
jion() | 调用某线程的该方法,将当前线程与该线程合并,即等待该线程结束,再恢复当前线程的运行 |
yield() | 让出cpu,当前线程进入就绪队列等待调度 |
线程的优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有 线程,线程调度器按照线程的优先级来决定应调度哪个线程来执行
- Java线程的优先级用1~10的整数来表示,越小则优先级越低
- 但是Java的优先级是高度依赖于操作系统的实现的
Thread类的三个常量,表示常用的线程优先级:
- Thread.MIN_PRIORITY //1
- Thread.NORM_PRIORITY // 5
- Thread.MAX_PRIORITY // 10
线程优先级Demo
package com.neuedu.chapter03._多线程_常用方法;
import com.neuedu.chapter03._多线程.RunnableDemo;
public class ThreadState {
public static void main(String[] args) {
Thread t1 = new Thread(new RunnableDemo("线程1"));
Thread t2 = new Thread(new RunnableDemo("线程2"));
Thread t3 = new Thread(new RunnableDemo("线程3"));
//打印优先级数
System.out.println("t1优先级数(默认值): "+t1.getPriority());
System.out.println("t2优先级数(默认值): "+t2.getPriority());
//System.out.println("t3优先级数(默认值): "+t3.getPriority());
//设置优先级
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
//t3.setPriority(Thread.MIN_PRIORITY);
//打印优先级数
System.out.println("t1优先级数(默认值): "+t1.getPriority());
System.out.println("t2优先级数(默认值): "+t2.getPriority());
System.out.println("t3优先级数(默认值): "+t3.getPriority());
//启动线程
t1.start();
t2.start();
//t3.start();
}
}
运行结果:
线程的休眠
sleep( )
- 让线程中止一段时间的静态方法
- Thread.sleep(long millis) — 暂时停止执行millis毫秒
- 在睡眠期满的瞬间,再次调用该线程不一定会恢复它的执行
Demo如下:
package com.neuedu.chapter03._多线程_常用方法;
import java.text.SimpleDateFormat;
import java.util.Date;
class DateThread extends Thread{
public void run() {
while(true) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd");
System.out.println("北京时间"+sdf.format(new Date()));
try {
Thread.sleep(3000); //参数已毫秒为单位,(此处表示让线程暂停3秒)
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class SleepDemo {
public static void main(String[] args) {
DateThread t1 = new DateThread();
t1.start(); //启动线程
}
}
join()
导致当前线程等待,直到调用这个 join 方法的线程终止 • join( ) join(long millis) join(long millis,int nanos)
Demo如下:
package com.neuedu.chapter03._多线程_常用方法;
public class jionDemo {
public static void main(String[] args) {
//线程1
Thread3 t1 = new Thread3(6,10);
t1.start();
try {
t1.join(); //终止线程
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//线程2
Thread3 t2 = new Thread3(20,30);
t2.start();
for(int i = 1;i<5;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
class Thread3 extends Thread{
private int x;
private int y;
public Thread3(int x,int y) {
this.x = x;
this.y = y;
}
public void run() { //重写run()方法
for(int i=x;i<y;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
yield()
- 为其他可运行的线程提供执行机会
- 静态方法 — Thread.yield( )
package com.neuedu.chapter03._多线程_常用方法;
public class yielDemo {
public static void main(String[] args) {
Thread5 runnable2 = new Thread5();
/*
* 通过实现Runnable接口实现的类只是一个方法,需要将它转化为Thread对象才能使用start()方法
* 这也是继承Thread类和实现Runnable两种方法在语法上的区别
*/
Thread t1 = new Thread(runnable2);
Thread4 runnable = new Thread4();
Thread t2 = new Thread(runnable);
t1.start();
t1.yield();
t2.start();
//t2.yield();
}
}
class Thread4 implements Runnable{ //实现Runable接口的方法实现多线程
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<50;i++) {
if(i<50) {
Thread.yield();
System.out.println(Thread.currentThread().getName()+" : "+"执行了"+(i+1)+"次");
}else {
System.out.println(Thread.currentThread().getName()+"------------执行完毕------------");
}
}
}
}
class Thread5 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<=50;i++) {
if(i<50) {
System.out.println(Thread.currentThread().getName()+" : "+"谦让了第"+(i+1)+"次");
}else {
System.out.println(Thread.currentThread().getName()+"------------执行完毕------------");
}
}
}
}
线程同步
有时两个或多个线程可能会试图同时访问一个资源
例如,一个线程可能尝试从一个文件中读取数据,而另一个线程 则尝试在同一文件中修改数据 在此情况下,数据可能会变得不一致
为了确保在任何时间点一个共享的资源只被一个线程 使用,使用了“同步”
- 当一个线程运行到需要同步的语句后,CPU不去执行其他线程中的、 可能影响当前线程中的下一句代码的执行结果的代码块,必须等 到下一句执行完后才能去执行其他线程中的相关代码块,这就是 线程同步.
实现同步的两种方式及其优缺点
1、synchronized方法
优点:
- 可以显示的知道哪些方法是 被synchronized关键字保 护的
缺点:
- 方法中有些内容是不需要同步 的,如果该方法执行会花很长 时间,那么其他人就要花较多 时间等待锁被归还;
- 只能取得自己对象的锁,有时 候程序设计的需求,可能会需 要取得其他对象的锁;
2、synchronized代码块
优点:
- 可以针对某段程序代码同步, 不需要浪费时间在别的程序代 码上;
- 可以取得不同对象的锁;
缺点:
- 无法显示的得知哪些方法 是被synchronized关键字 保护的;
synchronized()代码块的Demo
以两个售票员同时买票的例子为例:
写一个事项Runnable接口的类
package com.neuedu.chapter03._同步;
public class TicketRunnable implements Runnable{
private int num = 50; //票数
@Override
public void run() { //票数大于零,运行代码
// TODO Auto-generated method stub
while(num >0) {
synchronized(this) { //需要同步的代码块
System.out.println("售票员: "+Thread.currentThread().getName()+"售出了票号"+num+"的票");
num--; //打印一次票数减 1
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
创建线程
package com.neuedu.chapter03._同步;
public class TestTicketRunnable {
public static void main(String[] args) {
TicketRunnable runnable = new TicketRunnable();
Thread t1 = new Thread(runnable); //创建线程1
Thread t2 = new Thread(runnable); //创建线程2
t1.start();
t2.start();
}
}