1.线程中常用的方法
(1)static Thread currentThread() 得到当前正在运行的线程对象
(2)void start() 启动线程
(3)String getName()返回该线程的名称。
1.当没有设置线程名称的时候,系统会赋予线程一个默认的名称“Thread-0,Thread-1......”
2.主线程【主方法的执行线程】的名称默认是“main”
(4)void setName(String name)设置线程名称
例:
public class MyThread implements Runnable{
public void run() {
for(int i=1;i<=10;i++){
String name = Thread.currentThread().getName();
System.out.println(name+",i=="+i);
}
}
}
public class main {
public static void main(String[] args) {
//主方法执行线程的名称
String name = Thread.currentThread().getName();
System.out.println("主方法线程名称"+name);
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread);
Thread thread2 = new Thread(myThread);
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
}
}
2.线程的优先级
线程的优先级---就是线程的执行先后,默认情况下所有线程的优先级都是一样,都是等级5。
通过void setPriority(int newPriority) 更改线程的优先级。
(1)线程的优先级有10个级别,分别使用整数1~~10来表示,数字越大优先级越高。
(2)为了方便操作,java将10个级别规定成3个级别,分别是最低的优先级,中等优先级,最高的优先级,并且将这3个级别封装成了静态常量。
- static int MAX_PRIORITY 线程可以具有的最高优先级---10
- static int NORM_PRIORITY分配给线程的默认优先级---5
- static int MIN_PRIORITY线程可以具有的最低优先级---1
- int getPriority() 返回线程的优先级
(3)设置线程的优先级的时候,数字越大优先级越高,数字越小优先级越低。优先级越高并不代表一定会优先执行,只是被优先执行的几率增大,因此不要试图通过控制线程的优先级,来保证某一个线程,总是第一个执行。
例:
public class main {
public static void main(String[] args) {
//主方法执行线程的名称
String name = Thread.currentThread().getName();
int priority = Thread.currentThread().getPriority();
System.out.println("主方法线程名称"+name+",主方法优先级=="+priority);
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread);
Thread thread2 = new Thread(myThread);
thread1.setName("线程1");
thread2.setName("线程2");
System.out.println("线程1优先级=="+thread1.getPriority());
System.out.println("线程2优先级=="+thread2.getPriority());
thread1.setPriority(10);
thread2.setPriority(Thread.MIN_PRIORITY);
System.out.println("设置后线程1优先级=="+thread1.getPriority());
System.out.println("设置后线程2优先级=="+thread2.getPriority());
thread1.start();
thread2.start();
}
}
3.用户线程和守护线程
用户线程----通常情况之下我们所创建的线程都是普通线程,非守护线程,也叫用户线程。
守护线程----也叫精灵线程,当所有用户线程都执行完毕以后,自动结束运行的线程就是守护线程。【共死】特征:当所有用户线程都执行完毕以后,无论守护线程能否可以继续运行,都要立刻停止运行。
方法:
1.boolean isDaemon() 测试该线程是否为守护线程。
2.void setDaemon(boolean on) 将该线程标记为守护线程。
public class main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread,"用户线程1");
Thread thread2 = new Thread(myThread,"用户线程2");
Thread thread3 = new Thread(myThread,"守护线程");
thread3.setDaemon(true);
thread1.start();
thread2.start();
thread3.start();
}
}
4.线程休眠、中断、强制执行
(1)static void sleep(long millis) 设置线程休眠【暂停】指定的时间【毫秒】
例:
public class MyThread implements Runnable{
public void run() {
//得到当前线程的名称
String name = Thread.currentThread().getName();
System.out.println(name+"开始听讲");
//static void sleep(long millis) 设置线程休眠【暂停】指定的时间【毫秒】
try {
Thread.sleep(3000);//休眠可能会被打断,会进入异常,try...catch
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"开始睡着");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println(name+"被老师一巴掌扇醒");
System.out.println(name+"又开始听讲");
}
try {
Thread.sleep(5000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("下课了");
}
}
public class main {
public static void main(String[] args) {
System.out.println("上课铃响");
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread,"小明");
thread1.start();
System.out.println("老师开始上课");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("老师发现小明睡着了");
System.out.println("老师走到小明面前,扇了一巴掌");
thread1.interrupt();
}
}
上课铃响
老师开始上课
小明开始听讲
小明开始睡着
老师发现小明睡着了
老师走到小明面前,扇了一巴掌
小明被老师一巴掌扇醒
小明又开始听讲
下课了
(2)void interrupt() 中断线程休眠【暂停】。会进入异常【InterruptedException】
例:闹钟程序
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.Date;
public class NaoZhong {
public static void main(String[] args) throws Exception {
System.out.println("设置一个闹钟时间:");
BufferedReader input=new BufferedReader(new InputStreamReader(System.in));
String naozhongtime = input.readLine();
SimpleDateFormat sdf=new SimpleDateFormat("HH:mm:ss");
boolean flag=true;
while(flag){
String nowtime=sdf.format(new Date());
if(nowtime.equals(naozhongtime)){
flag=false;
}
System.out.println(nowtime);
Thread.sleep(1000);
}
System.out.println("闹钟响起");
}
}
输出:
设置一个闹钟时间:
00:32:20
00:32:14
00:32:15
00:32:16
00:32:17
00:32:18
00:32:19
00:32:20
闹钟响起
(3)void join(long millis)【强制线程执行】强制该线程执行millis 毫秒。如果不写参数时间,就是强制该线程执行完毕。
例:
public class MyThread implements Runnable{
public void run() {
for(int i=1;i<=10;i++){
String name = Thread.currentThread().getName();
System.out.println(name+",i=="+i);
}
}
}
public class main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread);
thread1.setName("线程1");
thread1.start();
for(int i=1;i<=10;i++){
String name = Thread.currentThread().getName();
System.out.println(name+",i=="+i);
if(i==3){
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
5.线程的生命周期
线程的生命周期就是线程从一开始创建,到run方法执行完毕以后的状态变化。【状态之间的切换】
线程的生命周期5种状态【新建状态 、就绪状态 、运行状态 、阻塞状态 、死亡状态】
5.1 新建状态
通过new的方式创建出线程对象,此时线程就进入到创建状态【新建状态】。新建状态的线程是不能运行。新建状态的线程调用start方法,就进入就绪状态。
5.2 就绪状态
线程具备运行能力,只差操作系统【CPU】分配给他运行时间片【万事具备,只欠时间片】,得到操作系统【CPU】分配给他运行时间片,此时开始执行run方法,进入运行状态。
5.3 运行状态
线程运行run方法。
(1)回到就绪状态
操作系统【CPU】分配给他运行时间片使用完毕,回到就绪状态
(2)进入阻塞状态
- 运行状态的线程执行了sleep方法,进入阻塞状态
- 运行状态的线程执行了wait方法,进入阻塞状态
- 运行状态的线程执行输入/输出动作,进入阻塞状态
(3)进入死亡状态
运行状态的线程run方法执行完毕,进入死亡状态
运行状态的线程调用stop()/destroy() ,进入死亡状态
5.4 阻塞状态
线程暂停运行。
阻塞状态结束后【结束了造成阻塞的原因】,此时线程进入就绪状态,得到操作系统【CPU】分配给他运行时间片就可以进入运行状态。
结束阻塞状态:
- 运行状态的线程执行了sleep方法,进入阻塞状态,休眠时间结束interrupt,进入就绪状态。
- 运行状态的线程执行了wait方法,进入阻塞状态,调用notify/notifyAll,进入就绪状态。
- 运行状态的线程执行输入/输出动作,进入阻塞状态,输入/输出结束,进入就绪状态。
5.5 死亡状态
线程运行结束,释放运行资源。
死亡状态的线程是不能运行,除非再一次使用start方法重新启动运行。
6. 线程同步【线程安全】
public class MyThread implements Runnable{
private int piao=5;
public void run() {
//得到线程名称
String name = Thread.currentThread().getName();
//持续买票
boolean flag=true;
while(flag){
//判断有没有可以卖的票
if(piao>0){
//收钱-找钱-打印票,用休眠时间代替
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+",卖出1张票,还剩"+(--piao)+"张");
}else{
flag=false;
}
}
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread,"窗口1");
Thread thread2 = new Thread(myThread,"窗口2");
Thread thread3 = new Thread(myThread,"窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
窗口1,卖出1张票,还剩4张
窗口2,卖出1张票,还剩3张
窗口3,卖出1张票,还剩4张
窗口3,卖出1张票,还剩1张
窗口1,卖出1张票,还剩2张
窗口2,卖出1张票,还剩2张
窗口3,卖出1张票,还剩0张
窗口1,卖出1张票,还剩-2张
窗口2,卖出1张票,还剩-1张
为什么会出现负数呢?分析:当窗口3卖最后一张票的时候,在收钱打印票的时候,还没来得及对票数进行减1,线程就切换给了窗口1,此时窗口1认为还有1张票,窗口1就收钱打印票,但也是还没来得及对票数进行减1,线程又切换给了窗口2,此时窗口2依然认为还有1张票,窗口2就收钱打印票,线程又切给了窗口3,减票剩0,切给了窗口2,减票剩-1,切给了窗口1,减票剩-2。
经过上面运行程序的分析,我得到的结果是:当多条线程,同时访问同一个资源的时候,会产生数据不一致的错误情况。
为了解决这种数据不一致的错误情况,引入线程同步。
6.1 线程同步定义
线程同步也叫线程安全,当多条线程,同时访问同一个资源的时候,每一次只能由多条线程中的其中一条访问公共资源,当这一条线程访问公共资源的时候,其他的线程都处于等待状态,不能访问公共资源,当这一条线程访问完了公共资源以后,其他线程中的一条线程才能访问公共资源,剩下的线程继续等待,等待当前线程访问结束,实现这个过程就是线程同步。【排队访问资源】
6.2 线程同步实现方式
6.2.1 Synchronized关键字---同步代码块
格式:synchronized(同步对象){
}
synchronized (this) {
// 判断有没有可以卖的票
if (piao > 0) {
// 收钱-找钱-打印票,用休眠时间代替
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ",卖出1张票,还剩" + (--piao) + "张");
} else {
flag = false;
}
}
使用synchronized (this)将买票代码包裹住。
窗口1,卖出1张票,还剩4张
窗口1,卖出1张票,还剩3张
窗口1,卖出1张票,还剩2张
窗口3,卖出1张票,还剩1张
窗口3,卖出1张票,还剩0张
注意:同步代码块虽然可以实现买票的效果,但是它在使用的时候,需要设置一个同步对象,由于我们很多时候都不知道这个同步对象应该是谁,容易写错,造成死锁的情况。正是应为这个缺点,我们很少使用同步代码块来实现线程同步。
6.2.2 Synchronized关键字---同步方法
同步方法的定义格式: 访问限制修饰符 synchronized 方法返回值类型 方法名称(){
}
public class MyThread2 implements Runnable {
private int piao =5;
// 持续买票
boolean flag = true;
public void run() {
// 得到线程名称
String name = Thread.currentThread().getName();
while (flag) {
sellpiao(name);
}
}
//同步方法
public synchronized void sellpiao(String name){
// 判断有没有可以卖的票
if (piao > 0) {
// 收钱-找钱-打印票,用休眠时间代替
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ",卖出1张票,还剩" + (--piao) + "张");
} else {
flag = false;
}
}
}
6.2.3 通过Lock接口
public interface Lock 是一个接口
常用的接口方法:
void lock() 获得锁。
void unlock() 释放锁。
由于上面的锁方法是Lock接口,我们要使用就得先创建出Lock接口对象,由于Lock是个接口不能new ,我们就得使用它的子类ReentrantLock来创建对象。
例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread3 implements Runnable {
private int piao = 5;
private Lock mylock=new ReentrantLock();
public void run() {
// 得到线程名称
String name = Thread.currentThread().getName();
// 持续买票
boolean flag = true;
while (flag) {
synchronized (this) {
mylock.lock();
// 判断有没有可以卖的票
if (piao > 0) {
// 收钱-找钱-打印票,用休眠时间代替
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ",卖出1张票,还剩" + (--piao) + "张");
} else {
flag = false;
}
mylock.unlock();
}
}
}
}
6.3 Synchronized关键字与Lock接口的区别
Synchronized关键字 | Lock接口 |
关键字 | 接口 |
自动锁定资源,不灵活 | 手动锁定资源,灵活 |
异常时会自动释放锁 | 异常时不会自动释放锁,所以需要在finally中实现释放锁 |
不能中断锁,必须等待线程执行完成释放锁 | 可以中断锁 |