第一节:线程简介
一.线程定义:
程序执行的一条路径,一个进程中可以包含多条线程。多线程并发可以提高程序效率,可以同时完成多项任务
二多线程并发和并行的区别:
并行:多个任务同时运行。
并发:多个任务能同时请求运行。
三.多线程实现方式:
a.继承Thread
b.实现Runnable。将Runnable对象传递个Thread.
四.两种方式在源代码的区别:
继承Thread:调用对象start()方法时,程序会自动调用重写的run()方法。
实现Runnable:将Runnable对象传递给Thread后,调用Thread的start()方法时会自动掉用传入的Runnable对象run()方法。
五.两种实现多线程方法的优缺点:
继承Thread:
优点:可以直接调用Thread的start方法。代码简单
缺点:如果一个类有其他的父类就不能继承Thread
实现Runnable:
优点:该类可以有其他的父类还能成为一个线程,接口可以多实现。
缺点:必须捕获当前线程才能调用Thread的方法。代码复杂。
第二节:线程的实现
通过匿名内部类继承Thread和实现Runnable
class Demo01_MultiThread{
public static void main(String[] args) {
//通过匿名内部类继承Thread生线程
new Thread(){
public void run(){
for (int i = 0; i < 100; i++) {
//如果i是20的倍数就在下一行打印。避免单行打印过多。
if(i % 20 == 0 && i != 0){
System.out.println();
}
System.out.print(i + "a ");
//增加线程执行难度,增加切换线程的可能性
int j = i * i * i * i;
}
}
//调用Thread对象的start方法
}.start();
//通过匿名内部类实现Runnable生成一个线程,再将匿名对象传递给Thread对象
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//如果i是20的倍数就在下一行打印。避免单行打印过多。
if(i % 20 == 0 && i != 0){
System.out.println();
}
System.out.print(i + "bbb ");
//增加线程执行难度,增加切换线程的可能性
int j = i * i * i * i;
}
}
//调用Thread对象的start方法
}).start();
}
}
可能输出结果:
0a 1a 2a 3a 4a 5a 6a 7a 8a 9a 10a 11a 12a 13a 14a 15a 16a 17a 18a 19a
20a 21a 22a 23a 24a 25a 26a 27a 28a 29a 30a 31a 32a 33a 34a 35a 36a 37a 38a 39a
40a 41a 42a 43a 44a 45a 46a 47a 48a 49a 50a 51a 52a 53a 54a 55a 56a 57a 58a 59a
60a 61a 62a 63a 64a 65a 66a 67a 68a 69a 70a 71a 72a 73a 74a 75a 76a 77a 78a 79a
80a 81a 82a 83a 84a 85a 86a 87a 88a 89a 90a 91a 92a 93a 94a 95a 96a 97a 98a 99a 0bbb 1bbb 2bbb 3bbb 4bbb 5bbb 6bbb 7bbb 8bbb 9bbb 10bbb 11bbb 12bbb 13bbb 14bbb 15bbb 16bbb 17bbb 18bbb 19bbb
20bbb 21bbb 22bbb 23bbb 24bbb 25bbb 26bbb 27bbb 28bbb 29bbb 30bbb 31bbb 32bbb 33bbb 34bbb 35bbb 36bbb 37bbb 38bbb 39bbb
40bbb 41bbb 42bbb 43bbb 44bbb 45bbb 46bbb 47bbb 48bbb 49bbb 50bbb 51bbb 52bbb 53bbb 54bbb 55bbb 56bbb 57bbb 58bbb 59bbb
60bbb 61bbb 62bbb 63bbb 64bbb 65bbb 66bbb 67bbb 68bbb 69bbb 70bbb 71bbb 72bbb 73bbb 74bbb 75bbb 76bbb 77bbb 78bbb 79bbb
80bbb 81bbb 82bbb 83bbb 84bbb 85bbb 86bbb 87bbb 88bbb 89bbb 90bbb 91bbb 92bbb 93bbb 94bbb 95bbb 96bbb 97bbb 98bbb 99bbb
总结:因为是两条线程在随机输出。所以结果很乱,再次执行,是这个结果的可能性也很小。
第三节:线程的方法
二.sleep(long millis)和sleep(long millis, long nanos)
因为windows不太支持纳秒。所以一般只调用第一个方法。
class Demo02_Sleep{
public static void main(String[] args) {
new Thread(){
public void run(){
for(int i = 10; i >= 0; i--){
System.out.println("倒计时:" + i);
//调用Thread的sleep(long millis)使线程减慢速度执行
//因为父类的run方法没有抛出异常,所以重写的方法也不能抛出异常
try{
this.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
//调用Thread对象的start方法
}.start();
}
}
可能输出结果:
倒计时:10
倒计时:9
倒计时:8
倒计时:7
倒计时:6
倒计时:5
倒计时:4
倒计时:3
倒计时:2
倒计时:1
倒计时:0
总结:因为有了sleep(long mills)的牵制,所以会以读秒的形式输出。
三.一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出.
通过setDaemon(boolean b)设置。不过只能调用start以前开始设置。
class Demo03_Daemon {
public static void main(String[] args) {
Thread thread1 = new Thread() {
public void run() {
this.setName("守护线程");
for (int i = 0; i < 1000; i++) {
System.out.print(getName() + i +"\t\t");
}
}
};
Thread thread2 = new Thread() {
public void run() {
this.setName("正常线程");
for (int i = 0; i < 3; i++) {
System.out.print(getName() + i + "\t\t");
}
}
};
thread1.setDaemon(true);
// 开启两个线程
thread2.start();
thread1.start();
}
}
可能输出结果:
正常线程0 正常线程1 守护线程0 守护线程1 守护线程2 守护线程3 正常线程2 守护线程4 守护线程5 守护线程6 守护线程7 守护线程8 守护线程9 守护线程10 守护线程11 守护线程12 守护线程13 守护线程14 守护线程15 守护线程16 守护线程17 守护线程18 守护线程19
总结:输出守护线程的个数可能不同,但是都是在正常线程结束后结束。
四.调用其他线程的join()方法, 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续 调用其他线程的join(long millis):当前线程等待join的调用线程执行完执行,或者millis毫秒后值后执行 调用其他线程的join(long millis, int* nanos):因为windows不太支持纳秒,一般不调用此方法。class Demo4_Join {
class Demo04_Join {
public static void main(String[] args) {
final Thread t1 = new Thread() {
public void run() {
this.setName("线程一");
for (int i = 0; i < 10; i++) {
System.out.print(getName() + " ");
}
}
};
new Thread() {
public void run() {
this.setName("线程二");
for (int i = 0; i < 10; i++) {
if (i == 2) {
try {
// 插队指定的时间,过了指定时间后,两条线程交替执行
t1.join(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(getName() + " ");
}
}
}.start();
t1.start();
}
}
可能执行结果:线程二 线程二 线程一 线程一 线程一 线程一 线程一 线程一 线程一 线程一 线程一 线程一 线程二 线程二 线程二 线程二 线程二 线程二 线程二 线程二
总结:引文在线程二运行中加入了线程1.所以线程二肯定等着线程一执行完才能执行完。
五.yield()线程是给虚拟机一个提示,该线程执行了一部分可以先让出资源给其他线程使用。仅仅只是个提示.
六.setProperty()可以设置线程的优先级,如果某个线程优先级高则有更多的可能获得资源执行。因为执行性差,这里不再演示
第四节:同步代码块
一.同步代码块来源: 当多个线程同时访问同一资源时,该资源可能只能被一个或若干个线程调用而不是所有线程, 此时需要将共享资源的代码块进行同步这就是同步代码块
同步代码块定义: 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块 多个同步代码块如果使用相同的锁对象,那么他们就是同步的.
class Demo05_Syschronized {
// 建立一个同步对象
static Object d = new Object();
/*
* 同步代码块的调用
*/
public static void main(String[] args) {
new Thread() {
public void run() {
while (true) {
print1();
}
}
}.start();
new Thread() {
public void run() {
while (true) {
print2();
}
}
}.start();
}
/*
* 同步方法
*/
public static void print1() {
// 锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
synchronized (d) {
System.out.print("枯");
System.out.print("藤");
System.out.print("老");
System.out.print("树");
System.out.print("昏");
System.out.print("鸦");
System.out.print("\r\n");
}
}
/*
* 同步方法
*/
public static void print2() {
// 锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
synchronized (d) {
System.out.print("小");
System.out.print("桥");
System.out.print("流");
System.out.print("水");
System.out.print("人");
System.out.print("家");
System.out.print("\r\n");
}
}
}
可能执行结果:
枯藤老树昏鸦
枯藤老树昏鸦
枯藤老树昏鸦
小桥流水人家
小桥流水人家
小桥流水人家
小桥流水人家
小桥流水人家
小桥流水人家
小桥流水人家
小桥流水人家
小桥流水人家
小桥流水人家
小桥流水人家
小桥流水人家
如果不使用同步代码块,下方的输出可能会是这样的:
小桥流水人家
小桥流水人家
小桥流水人家
小桥流水人藤老树昏鸦
枯藤老树昏鸦
枯家
小桥流水人家
小桥流水人家
小桥流水人家
小桥流水人家
小桥流水人藤老树昏鸦
枯藤老树昏鸦
枯藤老树昏家
小桥流水人家
小桥流水人家
小桥流水人家
总结:因为两个线程相对于另一个线程都是独立。所以会执行一次至少输出条完整的句子。不过两个线程并没有固定排序。所以两个句子是随机显示的。
二.如果希望线程间能通信,能交替执行。则使用Object类的wait()、notify()、notifyAll()方法
这里使用了任意对象,用来确定任意对象都可以作为该线程的锁对象。
在同步代码块中,用那个对象锁就用哪个对象释放锁
sleep和wait的区别
sleep方法必须传入时间参数。在这个时间参数执行完之后线程才能继续执行
wait方法可以传入参数也可以不传入。带有时间参数的如果时间到达或者被notify、notifyAll唤醒。
sleep方法不释放锁。wait方法在同步代码块中释放锁。
这里因为只是两个线程调用同一个对象锁。所以不用担心两个线程都处于等待状态或者突然停止
对于同一同步锁调用更多线程时
1.不使用notifyAll时,线程可能都会等待。
2.不使用while循环,线程可能乱序执行。
class Demo06_Syschronized {
// 建立一个同步对象
static Object d = new Object();
// 判断获取锁和放弃锁的标志
static int flag = 1;
/*
* 同步代码块的调用
*/
public static void main(String[] args) {
new Thread() {
public void run() {
try {
while (true) {
print1();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
new Thread() {
public void run() {
try {
while (true) {
print2();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
/*
* 同步方法
*/
public static void print1() throws InterruptedException {
// 锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
synchronized (d) {
// 确定当前方法所在线程是否符合获得锁的条件
if (flag != 1) {
d.wait();
}
System.out.print("枯");
System.out.print("藤");
System.out.print("老");
System.out.print("树");
System.out.print("昏");
System.out.print("鸦");
System.out.print("\r\n");
// 改变方法所在 线程获得锁的条件
flag = 2;
// 唤醒等待的线程
d.notify();
}
}
/*
* 同步方法
*/
public static void print2() throws InterruptedException {
// 锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
synchronized (d) {
// 确定当前方法所在线程符合获得锁的条件
if (flag != 2) {
d.wait();
}
System.out.print("小");
System.out.print("桥");
System.out.print("流");
System.out.print("水");
System.out.print("人");
System.out.print("家");
System.out.print("\r\n");
// 改变方法所在 线程获得锁的条件
flag = 1;
// 唤醒等待的线程
d.notify();
}
}
}
可能执行结果:
枯藤老树昏鸦
小桥流水人家
枯藤老树昏鸦
小桥流水人家
枯藤老树昏鸦
小桥流水人家
枯藤老树昏鸦
小桥流水人家
枯藤老树昏鸦
总结:因为对两条线添加等待,唤醒机制。所以可以不断地依次输出。
三. Java5.0添加新的特性:ReentrantLock类代替了同步语句块。
通过newCondition()方法获得Condition对象代替了wait()、notify()方法。
Condition优势是可以不调用while循环判断而按照正确的顺序执行线程。
class Demo08_Syschronized {
//定义并且初始化锁对象
private static ReentrantLock lock = new ReentrantLock();
//因为需要三个线程顺序执行,所以需要三个Condition对象
private static Condition condition1 = lock.newCondition();
private static Condition condition2 = lock.newCondition();
private static Condition condition3 = lock.newCondition();
//便于开始执行时确定顺序
private static int flag = 1;
/*
* 同步代码块的调用
*/
public static void main(String[] args) {
//打印“枯藤老树昏鸦”的线程,必须首先执行
new Thread() {
public void run() {
try {
while (true) {
print1();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
//打印“小桥流水人家”的线程,第二个执行
new Thread() {
public void run() {
try {
while (true) {
print2();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
//打印“古道西风瘦马”的线程,第三个执行。
new Thread() {
public void run() {
try {
while (true) {
print3();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
/*
* 同步方法
*/
public static void print1() throws InterruptedException {
lock.lock();
// 确定当前方法所在线程是否符合获得锁的条件
//这只是用于开始,接下来的循环是Condition对象自己选择。
if (flag != 1) {
condition1.await();
}
System.out.print("枯");
System.out.print("藤");
System.out.print("老");
System.out.print("树");
System.out.print("昏");
System.out.print("鸦");
System.out.print("\r\n");
// 改变方法所在 线程获得锁的条件
flag = 2;
// 唤醒等待的线程
condition2.signal();
lock.unlock();
}
/*
* 同步方法
*/
public static void print2() throws InterruptedException {
// 锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
lock.lock();
// 确定当前方法所在线程符合获得锁的条件
if (flag != 2) {
condition2.await();
}
System.out.print("小");
System.out.print("桥");
System.out.print("流");
System.out.print("水");
System.out.print("人");
System.out.print("家");
System.out.print("\r\n");
// 改变方法所在 线程获得锁的条件
flag = 3;
// 唤醒等待的线程
condition3.signal();
lock.unlock();
}
/*
* 同步方法
*/
public static void print3() throws InterruptedException {
// 锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
lock.lock();
// 确定当前方法所在线程符合获得锁的条件
if (flag != 3) {
condition3.await();
}
System.out.print("古");
System.out.print("道");
System.out.print("西");
System.out.print("风");
System.out.print("瘦");
System.out.print("马");
System.out.print("\r\n");
// 改变方法所在 线程获得锁的条件
flag = 1;
// 唤醒等待的线程
condition1.signal();
lock.unlock();
}
}
可能执行结果:
枯藤老树昏鸦
小桥流水人家
古道西风瘦马
枯藤老树昏鸦
小桥流水人家
古道西风瘦马
枯藤老树昏鸦
小桥流水人家
古道西风瘦马
枯藤老树昏鸦
小桥流水人家
总结:因为有三个Condition对象获取、释放锁,所以形成了三个有序的线程。
第五节:死锁
当甲线程有a锁,想要获得b锁时。刚好乙线程有b锁,想要获得a锁时就可能发生死锁现象 避免死锁: 尽量避免同步语句的嵌套调用
class Demo07_DeadLock {
// 同步锁
private static String s1 = "筷子左";
// 同步锁
private static String s2 = "筷子右";
public static void main(String[] args) {
new Thread() {
public void run() {
while (true) {
synchronized (s1) {
System.out
.println(getName() + "...拿到" + s1 + "等待" + s2);
synchronized (s2) {
System.out.println(getName() + "...拿到" + s2 + "开吃");
}
}
}
}
}.start();
new Thread() {
public void run() {
while (true) {
synchronized (s2) {
System.out
.println(getName() + "...拿到" + s2 + "等待" + s1);
synchronized (s1) {
System.out.println(getName() + "...拿到" + s1 + "开吃");
}
}
}
}
}.start();
}
}
可能执行结果:
Thread-0...拿到筷子左等待筷子右
Thread-1...拿到筷子右等待筷子左
总结:因为两条线成都哪有对方要执行对象的锁,都在等待对方释放锁。所以称为死锁。