您的位置:首页 > 编程语言 > Java开发

多线程

2015-11-10 10:14 447 查看
第一节:线程简介

一.线程定义:

    程序执行的一条路径,一个进程中可以包含多条线程。多线程并发可以提高程序效率,可以同时完成多项任务

二多线程并发和并行的区别:

    并行:多个任务同时运行。

    并发:多个任务能同时请求运行。

三.多线程实现方式:

    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("鸦");

d410
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...拿到筷子右等待筷子左

总结:因为两条线成都哪有对方要执行对象的锁,都在等待对方释放锁。所以称为死锁。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 多线程