1:sleep(n):让线程等待n毫秒后开始运行
2:join()函数,让使用该函数的线程执行完之后,整个程序才能继续往下执行。可谓是优先级拉满了。
例题:
编程要求
请仔细阅读右侧代码,根据方法内的提示,在Begin - End
区域内进行代码补充,具体任务如下:
- 创建自定义线程,实现求第
num
项斐波那契数列的值num
从0
开始,并且在main
函数中获取子线程最终计算的结果。
测试说明
补充完代码后,点击测评,平台会对你编写的代码进行测试,当你的结果与预期输出一致时,即为通过。
输入:5
输出:子线程计算结果为:5
输入:8
输出:子线程计算结果为:21
输入:10
输出:子线程计算结果为:55
package step2;
import java.util.Scanner;
public class Task {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
//请在此添加实现代码
/********** Begin **********/
MyThread t=new MyThread(num);
t.start();
try {
t.join();
}catch(Exception e)
{
e.printStackTrace();
}
System.out.print("子线程计算结果为:"+t.jieguo);
/********** End **********/
}
}
//请在此添加实现代码
/********** Begin **********/
class MyThread extends Thread {
private int num;
protected int jieguo=0;
int a=1,b=1;
public MyThread(int num)
{
this.num=num;
}
public void run()
{
int a=1,b=1,c=0;
if(this.num==1||this.num==2)
{
this.jieguo=1;
}
else
{
num=num-2;
while(num!=0)
{
num=num-1;
c=a+b;
a=b;
b=c;
}
this.jieguo=c;
}
}
}
/********** End **********/
这个题只用join就足够了,主线程想要执行最后的输出语句,就必须等待着子线程执行完,这就是join函数的魅力,一般的sleep函数主要用于让整个程序执行放慢,比如我做过那个C++炸弹人小游戏,我就用了sleep函数,就是为了避免电脑执行太快,敌人可以瞬间到达你的身边。
二、通过Lock类来实现多线程的同步
lock是一个接口,:lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()
方法是用来获取锁的,unlock()
方法是用来释放锁的。
package step3;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Task {
public static void main(String[] args) {
final Insert insert = new Insert();
Thread t1 = new Thread(new Runnable() {
public void run() {
insert.insert(Thread.currentThread());
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
insert.insert(Thread.currentThread());
}
});
Thread t3 = new Thread(new Runnable() {
public void run() {
insert.insert(Thread.currentThread());
}
});
// 设置线程优先级
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.NORM_PRIORITY);
t3.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
t3.start();
}
}
class Insert {
public static int num;
Lock lock = new ReentrantLock();
// 在这里定义Lock
public void insert(Thread thread) {
/********* Begin *********/
lock.lock(); 如果使用(lock.tryLock()则会导致仅有一个线程使用完锁)
System.out.println(thread.getName()+"得到了锁");
try {
for (int i = 0; i < 5; i++) {
num++;
System.out.println(num);
}
}catch(Exception e)
{
}finally {
System.out.println(thread.getName()+"释放了锁");
lock.unlock();
}
/********* End *********/
}
}
如果为了让线程挨个完成,可以用join()函数,因此也可以这样写:
package mytest;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class task {
public static void main(String[] args) {
final Insert insert = new Insert();
Thread t1 = new Thread(new Runnable() {
public void run() {
insert.insert(Thread.currentThread());
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
insert.insert(Thread.currentThread());
}
});
Thread t3 = new Thread(new Runnable() {
public void run() {
insert.insert(Thread.currentThread());
}
});
// 设置线程优先级
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.NORM_PRIORITY);
t3.setPriority(Thread.MIN_PRIORITY);
t1.start();
try {
t1.join();
}catch(Exception e)
{
}
t2.start();
try {
t2.join();
}catch(Exception e)
{
}
t3.start();
try {
t3.join();
}catch(Exception e)
{
}
}
}
class Insert {
public static int num;
// 在这里定义Lock
public void insert(Thread thread) {
/********* Begin *********/
System.out.println(thread.getName()+"获得了锁");
try {
for (int i = 0; i < 5; i++) {
num++;
System.out.println(num);
}
}catch(Exception e)
{
}finally {
System.out.println(thread.getName()+"释放了锁");
}
/********* End *********/
}
}
此处并未讨论synchronized同步锁的使用,因为是java内置的很难看到过程,所以非必要用lock就好。
三、synchronized+volatile实现变量的可见性
package step4;
public class Task {
public volatile int inc = 0;
//请在此添加实现代码
/********** Begin **********/
public synchronized void increase() { //这里加上synchronized就能保证
一段时间内只有一个线程访问该方法
inc++;
}
/********** End **********/
public static void main(String[] args) {
final Task test = new Task();
for (int i = 0; i < 10; i++) {
new Thread() {
public void run() {
for (int j = 0; j < 1000; j++)
test.increase();
};
}.start();
}
while (Thread.activeCount() > 1) // 保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
四、
请仔细阅读右侧代码,在 Begin-End
区域内进行代码补充,使线程依照先后顺序依次输出JavaThread+线程名
。
提示:我们知道线程的执行结果是随机的,什么时候执行线程是看哪一个线程抢占到了CPU
的资源,现在请你利用所学知识使多个线程开启之后依照先后顺序执行。
package step1;
public class Task {
public static void main(String[] args) throws Exception {
/********* Begin *********/
//在这里创建线程, 开启线程
Object a=new Object();
Object b=new Object();
Object c=new Object();
MyThread AA= new MyThread("AA",c,a);
MyThread BB= new MyThread("BB",a,b);
MyThread CC= new MyThread("CC",b,c);
AA.start();
Thread.sleep(10);
BB.start();
Thread.sleep(10);
CC.start();
Thread.sleep(10);
System.exit(0); //一定要加,需要终止进程,终止线程,不然程序一直不结束
/********* End *********/
}
}
class MyThread extends Thread {
/********* Begin *********/
String threadName;
Object prev;
Object self;
public MyThread(String threadName,Object prev,Object self)
{
this.threadName=threadName;
this.prev=prev;
this.self=self;
}
public void run() {
int count = 5;
while(count > 0){
synchronized(prev)
{
synchronized(self)
{
System.out.println("Java Thread" + this.threadName);
count--;
self.notify();
}
try
{
prev.wait();
}catch(Exception e)
{
e.printStackTrace();
}
}
}
}
/********* End *********/
}
这一段多线程java代码的使用需要带一点操作系统的理解,阻塞线程与释放线程。
为了使用Object类的wait()方法与notify方法,创建了三个对象类并且创建了三个线程,并将三个Object类引入其中。
重头戏在run方法的定义这里,synchronized(prev)与synchronized(self),这里就是就是将三个对象类的对象作为锁的对象,首先是AA线程,它对应的是synchronized(c)与synchronized(a),当最里面的synchronized(self)代码块结束后执行prev.wait(),于是对象c被锁住了,然后再是BB线程对应的是synchronized(a)与synchronized(b),然后同上对象a被锁住了,但是c通过notify被释放了,因此,此时由于仅有a被锁住,所以线程AA与线程BB均不能调用run方法,仅有线程CC可以运行,因为,它需要的对象是b和c,最后运行完再锁住b,然后仅有线程AA可以运行,就这样在相互牵制的作用下,三个线程实现了往复运行。
如果学了操作系统的信号量一章,则对这段代码理解起来就比较容易,因为信号量的互斥原理就是这段代码的原理。
五、第2关:售票窗口
创建一个站台类Station
,继承Thread
,重写run
方法,在run
方法里面执行售票操作(即如果票没卖完就一直卖)!售票要使用同步锁:即有一个站台卖这张票时,其他站台要等这张票卖完!
相关知识可以参考实训:Java高级特性 - 多线程基础(3)线程同步
测试说明
本关执行代码已经提前写好如下,不需要你重新编写运行代码:
public static void main(String[] args) {
//实例化站台对象,并为每一个站台取名字
Station station1=new Station();
Station station2=new Station();
Station station3=new Station();
// 让每一个站台对象各自开始工作
station1.start();
station2.start();
station3.start();
}
测试输入:无
; 预期输出:
卖出了第20张票
卖出了第19张票
卖出了第18张票
卖出了第17张票
卖出了第16张票
卖出了第15张票
卖出了第14张票
卖出了第13张票
卖出了第12张票
卖出了第11张票
卖出了第10张票
卖出了第9张票
卖出了第8张票
卖出了第7张票
卖出了第6张票
卖出了第5张票
卖出了第4张票
卖出了第3张票
卖出了第2张票
卖出了第1张票
票卖完了
package step2;
/********* Begin *********/
//定义站台类,实现卖票的功能。
public class Station extends Thread {
private static int ticket = 20;
static Object a= new Object();
public void run() {
while(ticket>=0)
{
synchronized(a)
{
if(ticket>0)
{System.out.println("卖出了第"+ticket+"张票");
ticket--;}
else if(ticket==0)
{
System.out.println("票卖完了");
System.exit(0);
}
}
}
}
}
/********* End *********/
public class task
{
public static void main(String[] args) {
//实例化站台对象,并为每一个站台取名字
Mythread task1=new Mythread();
Mythread task2=new Mythread();
Mythread task3=new Mythread();
// 让每一个站台对象各自开始工作
task1.start();
task2.start();
task3.start();
}
/********* End *********/
}
这个题分析可得到,ticket为20张,共有三个线程对它进行修改,因此它必须被看成共享变量,所以ticket必须用static修饰,不然的话,每个线程都读一个20就是60了,这就是static的魅力,为了及时可见ticket的变化,因此必须让三个线程对其进行同步访问修改,这里我没有采用lock,因为没有必要那么复杂,synchronized足够应付,只需要创建一个对象类对其加锁即可,而且必须是static类型,不然的话就是三个对象a,每个线程都有一个独自的对象类a,根本锁不住,必须用static才能让三个线程共用一个对象类a,才能达到互斥访问的目的,唯一的a给哪个线程用,它才能执行该方法。
System.exit(0);这个是必须加的,不然程序一直不停止,线程没被杀死。