JavaSE 线程(下)
三、Thread类的常用方法
1 线程一般方法
方法名称 | 功能 |
---|---|
void start() | 启动线程 |
run() | 线程在被调度时执行的操作 |
String getName() | 取出线程名称 |
void setName(String name) | 设置线程名称 |
static currentThread() | 返回当前线程 |
int getPriority() | 获取线程优先级 |
void setPriority() | 设置线程优先级(默认为5) |
补充:线程优先级就是哪个线程有较大的概率被执行。优先级用数字1-10表示,数字越大,优先级越高。若未设置优先级,默认为5。
以上方法案例展示:
package com.thread;
public class Demo01 {
public static void main(String[] args) {
RunnalbalImpl run0 = new RunnalbalImpl();
RunnalbalImpl run1 = new RunnalbalImpl();
Thread t0 = new Thread(run0);
Thread t1 = new Thread(run1);
t1.setName("线程t1");
/*
线程优先级就是哪个线程有较大的概率被执行。
优先级用数字1-10表示,数字越大,优先级越高。
若为设置,默认为5。
*/
t0.setPriority(1); //设置优先级为1,若为设置默认为5
t0.start();
t1.start();
System.out.println("t0的线程名称: "+ t0.getName()); //若在创建线程时没有指定名称,则系统默认给出"Thread-0/1/..."
System.out.println("t1的线程名称: "+ t1.getName());
System.out.println("t0的优先级: "+t0.getPriority()); //获取线程优先级
}
}
class RunnalbalImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"Runnable多线程运行的代码");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"这是Runnable多线程的逻辑代码" + i);
}
}
}
/*运行结果:
Thread-0Runnable多线程运行的代码
线程t1Runnable多线程运行的代码
线程t1这是Runnable多线程的逻辑代码0
线程t1这是Runnable多线程的逻辑代码1
t0的线程名称: Thread-0
线程t1这是Runnable多线程的逻辑代码2
Thread-0这是Runnable多线程的逻辑代码0
Thread-0这是Runnable多线程的逻辑代码1
线程t1这是Runnable多线程的逻辑代码3
Thread-0这是Runnable多线程的逻辑代码2
线程t1这是Runnable多线程的逻辑代码4
Thread-0这是Runnable多线程的逻辑代码3
Thread-0这是Runnable多线程的逻辑代码4
t1的线程名称: 线程t1
t0的优先级: 1
*/
2 线程控制方法
方法名称 | 功能 |
---|---|
static void yield() | 线程让步: 1. 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程 2. 若队列中没有同优先级的线程,忽略此方法 |
join() | 当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到·join()方法加入的join线程执行完为止。 |
static void sleep(long millis) | 1. 另当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重新排队。 2. 需要抛出InterruptedException异常 |
stop() | 强制线程结束 |
boolean isAlive() | 返回boolean值,判断线程是否还存活 |
以上方法案例展示:
package com.thread;
public class Demo02 {
public static void main(String[] args) {
RunnalbalImpl2 run0 = new RunnalbalImpl2();
RunnalbalImpl2 run1 = new RunnalbalImpl2();
Thread t0 = new Thread(run0);
Thread t1 = new Thread(run1);
t0.start();
t1.start();
System.out.println("-----------------------------------------1");
System.out.println("-----------------------------------------2");
t1.stop(); //强制停止线程
try {
t0.join(); //相当于在这里将t0的run方法插入到这个位置执行!
/* 专业说法
阻塞当前main方法,先不执行System.out.println("-----------------------------------------3");代码
优先执行join进来的线程的代码。
但是较为不明显。
*/
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----------------------------------------3");
System.out.println(t0.isAlive()); //判断线程是否存活
System.out.println(t1.isAlive());
}
}
class RunnalbalImpl2 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"Runnable多线程运行的代码");
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);//每次循环睡眠1000ms
//相当于当前循环每隔1000ms执行一次
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i % 2 == 0){
Thread.yield(); //线程让步 <-- 当i是偶数时
}
System.out.println(Thread.currentThread().getName()+"这是Runnable多线程的逻辑代码" + i);
}
}
}
/* 运行结果:
Thread-0Runnable多线程运行的代码
Thread-1Runnable多线程运行的代码
-----------------------------------------1
-----------------------------------------2
Thread-0这是Runnable多线程的逻辑代码0
Thread-0这是Runnable多线程的逻辑代码1
Thread-0这是Runnable多线程的逻辑代码2
Thread-0这是Runnable多线程的逻辑代码3
Thread-0这是Runnable多线程的逻辑代码4
-----------------------------------------3
false
false
*/
四、线程的生命周期
线程在一个完整的生命周期中通常要经历如下五种状态:
- 新建:当一个Thread类或其子类的对象被声明应创建时,新生的线程对象处于新建状态。
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件。
- 运行:当就绪的线程被嗲度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能。
- 阻塞:如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。
- 死亡:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
生命周期图示:
五、线程的同步
1 案例
例子:同一个账户,支付宝转账,微信转账。现有两个手机,一个手机开支付宝,另一个开微信,假设现有余额3000,支付宝和微信同时提款2000。 如果没有线程同步控制,账户就会变为-1000,这种情况不可以出现。
该例子通过代码实现如下:
package com.thread;
public class Demo03 {
public static void main(String[] args) {
//定义账户对象
Acount a = new Acount();
User u_wechat = new User(a,2000);
User u_zhi = new User(a,2000);
//多线程对象
Thread wechat = new Thread(u_wechat,"微信");
Thread zhi = new Thread(u_zhi,"支付宝");
wechat.start();
zhi.start();
}
}
class Acount{
public static int money = 3000; //全局变量,所有线程共享
/**
* 提款,判断账户余额是否充足
* 多线程调用这个方法,就有问题,线程共享资源时,一个线程在执行这个方法没有完毕时,另一个线程又开始执行这个方法。
* @param m
*/
public void drawing(int m){
String name = Thread.currentThread().getName();
if (money<m){
System.out.println(name+"操作,账户金额不足:" + money);
}else {
System.out.println(name + "操作,账户原有金额: " + money);
System.out.println(name + "操作,取款金额: " + m);
money -= m;
System.out.println(name + "操作,取款后的金额: " + money);
}
}
}
class User implements Runnable{
Acount acount;
int money;
public User(Acount acount, int money){
this.acount = acount;
this.money = money;
}
@Override
public void run() {
acount.drawing(money);
}
}
/*运行结果:
支付宝操作,账户原有金额: 3000
微信操作,账户原有金额: 3000
支付宝操作,取款金额: 2000
支付宝操作,取款后的金额: 1000
微信操作,取款金额: 2000
微信操作,取款后的金额: -1000
*/
造成问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
2 synchonized同步锁!!
通过synchronized同步锁来完成!
只需在上述案例中Acount类的drawing方法前加上synchronized
即可!即:
public synchronized void drawing(int m){}
结果显示:
{...}
/*
微信支付操作,账户原有金额: 3000
微信支付操作,取款金额: 2000
微信支付操作,取款后的金额: 1000
支付宝支付操作,账户金额不足:1000
*/
以上问题解决。
synchronized同步锁注意!!!
-
synchronized放在普通方法前,仅能锁当前对象(实例),不同对象有不同的锁。
-
synchronized放在静态方法前,锁整个类,所有类对象公用一个锁。
-
也可对代码块加入同步锁。实现如下:
public void drawing2(int m, Acount a){ //使用this锁代码块是代表当前的对象,如果在其他方法中也有synchronized(this)代码块使用的都是同一个同步锁 synchronized(this){//表示对整个代码块加锁 ... } synchronized(a){ //锁传入的对象,并对代码块加锁 ... }; }
3 线程的死锁问题
-
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
例:线程a0,需要执行f0;线程a1,需要执行f1;f0和f1都有同步锁的方法,现在出现一个问题:a0调用f1方法但一直没有执行完f1,a1调用f0方法但一直没有执行完f0。导致a0和a1线程都在等待对方释放方法,而对方都不释放,这样就形成了线程的死锁。
-
解决办法
专门的算法、原则,比如加锁的顺序一致。
尽量减少同步资源的定义,尽量避免锁未释放的场景。
六、线程通信
1 线程通信方法
注:以下三种方法只能用在有同步锁的方法或代码块中!
方法名称 | 功能 |
---|---|
wait() | 另当前线程挂起并放弃CPU、同步资源,使别的线程可以访问并修改共享资源,而当前线程排队等候再次对资源的访问。 |
notify() | 唤醒正在排队等待同步资源的线程中优先级最高者结束等待。 |
notifyAll() | 唤醒正在排队等待资源的所有线程结束等待。 |
现在想要实现以下功能:
如果是微信操作的,先不执行,等待支付宝操作,支付宝操作完成后,微信在继续操作。
方法案例展示:
package com.thread;
public class Demo03 {
public static void main(String[] args) {
//定义账户对象
Acount a = new Acount();
User u_wechat = new User(a,2000);
User u_zhi = new User(a,2000);
//多线程对象
Thread wechat = new Thread(u_wechat,"微信");
Thread zhi = new Thread(u_zhi,"支付宝");
wechat.start();
zhi.start();
}
}
class Acount{
public static int money = 3000; //全局变量,所有线程共享
public void drawing(int m, Acount a){
synchronized (a){ //对实例加同步锁
String name = Thread.currentThread().getName();
//如果是微信操作的,先不执行,等待支付宝操作,支付宝操作完成后,微信在继续操作。
if(name.equals("微信")){
try {
a.wait();//当前进程进入等待状态!
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (name.equals("支付宝")) {
try {
a.notify(); //唤醒当前优先级最高的线程,进入就绪状态!
// a.notifyAll(); //唤醒所有线程,进入就绪状态!
} catch (Exception e) {
e.printStackTrace();
}
}
if (money<m){
System.out.println(name+"操作,账户金额不足:" + money);
}else {
System.out.println(name + "操作,账户原有金额: " + money);
System.out.println(name + "操作,取款金额: " + m);
money -= m;
System.out.println(name + "操作,取款后的金额: " + money);
}
}
}
}
class User implements Runnable{
Acount acount;
int money;
public User(Acount acount, int money){
this.acount = acount;
this.money = money;
}
@Override
public void run() {
acount.drawing(money,acount);
}
}
/*运行结果:
支付宝操作,账户原有金额: 3000
支付宝操作,取款金额: 2000
支付宝操作,取款后的金额: 1000
微信操作,账户金额不足:1000
*/
2 生产者/消费者问题
**问题描述:**生产者将产品交给店员,而消费者从店员处取走产品,店员一次只能持有固定数量的产品(20),如果生产者试图生产更多的产品,店员会叫生产者停一下;如果店中有空位放产品了在通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
此处有两个问题:
-
生产者比消费者更快时,消费者会漏掉一些数据没有取到。
-
消费者比生产者快时,消费者会取相同的数据。
代码实现:(此处使用两个匿名内部类实现!)
package com.thread;
/**
* 生产者与消费者
*/
public class Test3 {
public static void main(String[] args) {
//店员
Clerk c = new Clerk();
//消费时不生产,生产时不消费
//生产者线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized (c){
while (true){ //无限循环代表无限的生产次数
if (c.productNum == 0) { //产品数量为0,开始生产
System.out.println("产品数量为0,开始生产");
while (c.productNum < 4){
System.out.println("库存:" + c.productNum++);
}
System.out.println("产品数为:" + c.productNum + ",结束生产");
c.notify(); //唤醒消费者线程
}else {
try {
c.wait(); //生产者线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}, "生产者").start();
//消费者线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized (c){
while (true) { //无限循环代表无限的生产与消费
if (c.productNum != 0) { //产品数为4,开始消费
c.notify(); //唤醒生产者线程
System.out.println("产品数为4,开始消费");
while (c.productNum > 0) {
System.out.println("库存:" + c.productNum--);
}
System.out.println("产品数为:" + c.productNum + ",结束消费");
} else {
try {
c.wait(); //消费者线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}, "消费者").start();
}
}
class Clerk{
public static int productNum;
}
写在最后
行百里者半九十!
To Demut and Dottie!