Java线程是一项非常基本和重要的技术,在偏底层和偏技术的Java程序中不可避免地要使用到Java线程技术,特别是android手机程序和游戏开发中,多线程成了必不可少的一项重要技术。但是,很多Java程序员对Java线程技术的了解都仅停留在初级阶段,在项目中一旦涉及到多线程时往往就表现得糟糕至极,所以,软件公司常常使用Java线程技术来考察面试者的基本功和判断其编码水平的高低。
本套视频教程是专门为了帮助那些已经学习和了解过、但掌握得并不是很深入的人们提高java线程技术而讲解的,所以,Java线程初学者学习本视频教程时可能会比较吃力,可能必须耐心学习多遍才能渐入佳境,但是,你一旦掌握了其中的内容,你对Java线程技术的了解将会相当出众!
01. 传统线程技术回顾
02. 传统定时器技术回顾
03. 传统线程互斥技术
04. 传统线程同步通信技术
05. 线程范围内共享变量的概念与作用
06. ThreadLocal类及应用技巧
07. 多个线程之间共享数据的方式探讨
08. java5原子性操作类的应用
09. java5线程并发库的应用
10. Callable与Future的应用
11. java5的线程锁技术
12. java5读写锁技术的妙用
13. java5条件阻塞Condition的应用
14. java5的Semaphere同步工具
15. java5的CyclicBarrier同步工具
16. java5的CountDownLatch同步工具
17. java5的Exchanger同步工具
18. java5阻塞队列的应用
19. java5同步集合类的应用
20. 空中网挑选实习生的面试题1
21. 空中网挑选实习生的面试题2
22. 空中网挑选实习生的面试题3
23. 源代码与资料
01. 传统线程技术回顾
传统是相对于JDK1.5而言的
传统线程技术与JDK1.5的线程并发库
线程就是程序的一条执行线索/线路。
创建线程的两种传统方式
1. 创建Thread的子类,覆盖其中的run方法,运行这个子类的start方法即可开启线程
Thread thread = new Thread()
{ @Override
public void run()
{
while (true)
{
获取当前线程对象 获取线程名字
Thread.currentThread() threadObj.getName()
让线程暂停,休眠,此方法会抛出中断异常InterruptedException
Thread.sleep(毫秒值);
}
}
};
thread.start();
2. 创建Thread时传递一个实现Runnable接口的对象实例
Thread thread =new Thread(new Runnable()
{
public void run()
{}
});
thread.start();
问题:下边的线程运行的是Thread子类中的方法还是实现Runnable接口类的方法
new Thread(
b、传递实现Runnable接口的对象
new Runnable()
{
publicvoid run()
{}
}
){
a、覆盖Thread子类run方法
public void run(){}
}.start();
分析:new Thread(Runnable.run()){run()}.start();
子类run方法实际就是覆盖父类中的run方法,如果覆盖了就用子类的run方法,不会再找Runnable中的run方法了,所以运行的是子类中的run方法
总结:
由Thread类中的run方法源代码中看出,两种传统创建线程的方式都是在调用Thread对象的run方法,如果Thread对象的run方法没有被覆盖,并且像上边的问题那样为Thread对象传递了一个Runnable对象,就会调用Runnable对象的run方法。
多线程并不一定会提高程序的运行效率。举例:一个人同时在三张桌子做馒头
多线程下载:并不是自己电脑快了,而是抢到更多服务器资源。例:服务器为一个客户分配一个20K的线程下载,你用多个线程,服务器以为是多个用户就分配了多个20K的资源给你。
02. 传统定时器技术回顾
传统定时器的创建:直接使用定时器类Timer
a、过多长时间后炸
newTimer().schedule(TimerTask定时任务, Date time定的时间);
b、过多长时间后炸,以后每隔多少时间再炸
newTimer().schedule(TimerTask定时任务, Long延迟(第一次执行)时间, Long间隔时间);
TimerTask与Runnable类似,有一个run方法
Timer是定时器对象,到时间后会触发炸弹(TimerTask)对象
示例:
newTimer().schedule(
new TimerTask()定时执行的任务
{
public void run()
{
SOP(“bombing”);
}
显示计时信息
while (true)
{
SOP(newDate().getSeconds());
Thread.sleep(1000);
}
},
10 定好的延迟时间,10秒以后执行任务
);
问题:2秒后炸,爆炸后每隔3秒再炸一次
定时器2秒后炸,炸弹里还有定时器(每3秒炸一次)
classMyTimerTask extends TimerTask 这就是准备用的子母弹
{
public void run()
{
本身就是一颗炸弹
SOP(bombing);
内部子弹
new Timer().schedule(
new MyTimerTask(), 2000
);
}
}
放置子母弹,2秒后引爆
newTimer().schedule(new MyTimerTask(), 2000);
问题延伸:
上面的问题延伸,母弹炸过后,子弹每隔3秒炸一次,再每隔8秒炸一次
1、在MyTimerTask内部定义一个静态变量记录炸弹号,在run方法内将炸弹号加1,每次产生新炸弹,号码就会加1,根据炸弹号判断是3秒炸还是8秒炸。
注意:内部类中不能声明静态变量
定义一个静态变量private static count = 0;
在run方法内部:count=(count+1)%2;
将定时器的时间设置为:2000+2000*count
2、用两个炸弹来完成,A炸弹炸完后启动定时器安装B炸弹,B炸弹炸完后也启动一个定时器安装A炸弹。
定时器还可以设置具体时间,如某年某月某日某时……可以设置周一到周五做某事,自己设置的话需要换算日期时间,可以使用开源工具quartz来完成。
03. 传统线程互斥技术
线程安全问题例子:银行转账
同一个账户一边进行出账操作(自己交学费),另一边进行入账操作(别人给自己付款),线程不同步带来的安全问题
示例:逐个字符的方式打印字符串
class Outputer
{
public void output(String name)
{
int len =name.length();
for (int i=0; i<len; i++)
SOP(name.charAt(i));逐个字符打印
SOP();换行
}
}
public void test()
{
Outputeroutputer = new Outputer();
newThread(
new Runnable()
{
public void run()
{
Thread.sleep(100);
outputer.output(“zhangxiaoxiang”);
}
}).start();
newThread(
new Runnable()
{
public void run()
{
Thread.sleep(100);
outputer.output(“lihuoming”);
}
}).start();
}
注意:
内部类不能访问局部变量,要访问需加final
静态方法中不能创建内部类的实例对象
打印结果发现的问题:线程不同步所致,两个线程都在使用同一个对象
互斥方法:
a、同步代码块
synchronized(lock){}
b、同步方法
方法返回值前加synchronized
同步方法上边用的锁就是this对象
静态同步方法使用的锁是该方法所在的class文件对象
使用synchronized关键字实现互斥,要保证同步的地方使用的是同一个锁对象
public synchronized void output(String name)
{
int len =name.length();
这里就不要再加同步了,加上极易出现死锁
for (int i=0; i<len; i++)
SOP(name.charAt(i));逐个字符打印
SOP();换行
}
04. 传统线程同步通信技术
面试题,子线程10次与主线程100次来回循环执行50次
下面是我刚看完面试题就暂停视频自己试着写的代码,还可以,结果完成要求了
在单次循环结束后让这个刚结束循环的线程休眠,保证另一个线程可以抢到执行权。
public class ThreadInterViewTest
{
/**
* 刚看到面试题没看答案之前试写
* 子线程循环10次,回主线程循环100次,
* 再到子线程循环10次,再回主线程循环100次
* 如此循环50次
*/
publicstatic void main(String[] args)
{
intnum = 0;
while(num++<50)
{
newThread(new Runnable()
{
@Override
public void run()
{
circle("子线程运行", 10);
}
}).start();
try
{
//加这句是保证上边的子线程先运行,刚开始没加,主线程就先开了
Thread.sleep(2000);
}catch (InterruptedException e)
{
e.printStackTrace();
}
circle("主线程", 100);
}
}
publicstatic synchronized void circle(String name, int count)
{
for(int i=1; i<=count; i++)
{
System.out.println(name+"::"+i);
}
try
{
Thread.sleep(5000);
}catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
张老师讲的方法:
1、将子线程和主线程中要同步的方法进行封装,加上同步关键字实现同步
2、两个线程间隔运行,添加一个标记变量进行比较以实现相互通信,加色的部分
wait notify notifyAll wait会抛出异常
class Business
{
private boolean bShouleSub = true;
publicsynchronized void sub()
{
if (bShouleSub)
{
for (int i=1; i<11; i++)
SOP(sub+i);
bShouldSub= false;
this.notify();
}
else
this.wait();
}
publicsynchronized void main()
{
if (!bShouldSub)
{
for (int i=1; i<101; i++)
SOP(main+i);
bShouldSub= true;
this.notify();
}
else
this.wait();
}
}
经验:要用到共同数据(包括同步锁)或相同算法的多个方法要封装在一个类中
锁是上在代表要操作的资源类的内部方法中的,而不是上在线程代码中的。这样写出来的类就是天然同步的,只要使用的是同一个new出来的对象,那么这个对象就具有同步互斥特性
判断唤醒等待标记时使用while增加程序健壮性,防止伪唤醒