【并发编程】线程的创建,计时器,线程的同步锁


今天又重新把Java中的多线程和并发运行巩固了一次,关于这一点,我稍微总结一点基础的东西。

线程的创建

首先线程的创建有两种方式:

package Multithreading;

public class NewThreadDemo {
	public static void main(String[] args) {

		/**
		 * 第一种方法
		 */
		// 在Thread中创建一个子类
		Thread thread = new Thread() {
			public void run() {
				while (true) {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("1:" + Thread.currentThread().getName());
				}
			}
		};
		thread.start();

		/**
		 * 第二种方法
		 */
		// 创建一个runnable对象,将线程运行的代码封装起来
		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("2:" + Thread.currentThread().getName());
				}
			}
		});
		thread2.start();

		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("runnable:"
							+ Thread.currentThread().getName());
				}
			}
		}) {
			public void run() {
				while (true) {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("Thread:"
							+ Thread.currentThread().getName());
				}
			}
		}.start();
	}
}


两种方法中,一种是直接new 一个Thread,第二种是在线程中创建一个Runnable对象,将要执行的操作封装到Runnable中。

我们通常用的是第二种方式,因为它更体现了面向对象的编程原理。我们把操作转入一个对象中,而线程运行的时候,直接调用这个对象,而不是调用一堆操作。

代码块的第45行后的程序,当start后,程序开始寻找线程Thread,找到后,会执行Thread的run函数,而不是runnable对象里的run函数。所以输出的结果会是:Thread:Thread-2。而不是:runnable:Thread-2。(这里可以进入Thread的源代码里面去研究,这里就不深究了,知道是这样子就行了。)

线程创建还有一种方式, 将线程与要执行的任务分开:

package Day05;

public class RunnableDemo {
	public static void main(String[] args) {
		Runnable p1 = new PersonR1();
		Runnable p2 = new PersonR2();
		
		Thread t1 = new Thread(p1);
		Thread t2 = new Thread(p2);
		
		t1.start();
		t2.start();
	}
}

class PersonR1 implements Runnable{
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println("who are you?"+i+"次");
		}
	}	
}

计时器

在线程里面,我们还会运用到计时器这东西,关于计时器比较简单,一段小代码就可以清楚它的使用:

package Multithreading;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TraditionalTimerTest {
	private static int count = 0;
	public static void main(String[] args) {
//		new Timer().schedule(new TimerTask() {
//			public void run() {
//				System.out.println("bomoing!!");
//			}
//		}, 10000,3000);
		
		class MyTimerTask extends TimerTask{
			@Override
			public void run() {
				count = (count+1)%2;
				System.out.println("bombing!");
				new Timer().schedule(new MyTimerTask(), 2000+2000*count);
			}
		}
		
		new Timer().schedule(new MyTimerTask(), 2000);
		
		while (true) {
			System.out.println(new Date().getSeconds());
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
以上程序是构造一个连环炸弹的程序,并且炸弹爆炸的间隔时间是2s和4s轮流的,先2s,再4s,然后又2s,再4s。

这样我们先定义一个计时器,然后创建一个MyTimerTask()类,程序运行到计时器时,2s后调用MyTimerTask()这个对象,然后在MyTimerTask()这个对象中进行4s和2s的轮流。

如何让他们轮流?

我们可以定义一个标签count=0,每次执行“bombing!”后加1,然后count = (count+1)%2,这样count的值总是01010101……的循环,就可以控制2s和4s的行为。

线程的同步锁

然后就是关于线程同步的问题,也是线程的核心问题,我把代码先贴出来:

package Multithreading;

/**
 * 线程互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。
 * 但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
 */
public class TraditionalThreadSynchronized {
	public static void main(String[] args) {
		/**
		 * 内部类的重要特点就是:可以访问外部类的成员变量。 而我能访问外部成员变量,一定是外部类有了实体对象,而在main方法中是静态方法,
		 * 而在静态方法执行的时候可以不用创建实体对象,就矛盾了。 main函数运行的时候,没有外部类的实体对象, final Outputer
		 * outputer = new Outputer();创建了的内部类成员,
		 * 而这个内部类成员可以访问外部类成员变量,可是此时没有外部类的实体对象,那么这个内部类的对象所访问的外部类对象是什么? 所以这里报错
		 */
		// final Outputer outputer = new Outputer();//在静态方法中不能new内部类的实体对象
		// new Thread(new Runnable() {
		// 	@Override
		// 	public void run() {
		// 		while (true) {
		// 			try {
		// 				Thread.sleep(10);
		// 			} catch (InterruptedException e) {
		// 				e.printStackTrace();
		// 			}
		// 			outputer.output("qiudong");
		// 		}
		// 	}
		// }).start();

		new TraditionalThreadSynchronized().init();
	}

	// 要想创建内部类的实体对象,就必须有一个外部类的实体对象
	private void init() {
		final Outputer outputer = new Outputer();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					outputer.output1("Andy");
				}
			}
		}).start();

		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					outputer.output1("Tom");
				}
			}
		}).start();
	}

    /**
     * 方法内部加上线程锁,线程的锁只能锁同一个对象,如果有static方法那么线程锁必须为Class,不能为this
     */
	static class Outputer {//此处添加static后,此类就是外部类了,不是内部类了
		public void output1(String name) {
			//System.out.println(this);//Multithreading.TraditionalThreadSynchronized$Outputer@1d63d35
			//System.out.println(Outputer.class);//class Multithreading.TraditionalThreadSynchronized$Outputer
			int len = name.length();
			/**
			 * synchronized使用排它性的原理,括号里的对象相当于门栓,如果这里的对象使用name,
			 * 行不行?不行!
			 * 因为A线程进来,关上了门栓,其他的线程就进不来,只有等到A线程将门栓打开,其他线程才能进去。
			 * 而如果使用name对象,Andy线程来了,它进入这个房间,关上了门栓,而此时Tom线程也来了,可是它检查的是name门栓,
			 * 而实际上没有限定一定要进入这个房间,它可能到隔壁的房间检查门栓,发现是开着的,也进去了。
			 * 这就没有起到排它性的作用。所以这里的对象要使用同一个对象,而name并不相同。
			 */
			synchronized (Outputer.class) {//synchronized参数可以为this,指的是当前对象,但是和static方法不能实现互斥,实现互斥只能为同一个对象.
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();
			}
		}
		
		//另外一种互斥的方法,直接把锁加在方法上,默认把synchronized加载方法上那么他的互斥对象为this
		//(只能有一个synchronized,多个会引起死锁现象)
		/**
		 * output1和output2是可以同步的
		 */
		public synchronized void output2(String name) {
			int len = name.length();
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();
		}
		
		//static方法的线程锁
		public static synchronized void output3(String name) {//静态的东西非要在外部类中定义,需要在Outputer类之前添加static,使之成为外部类
			int len = name.length();
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();
		}
	}
}


代码中就解释的很清楚了。


今天的最后一个问题是是一道编程面试题,线程之间的通信,即wait和notify的使用。

题目是:/*
               * 题目:在子程序里面运行10次,然后在主程序里运行100次,
               * 然后再回到子程序里面运行10次,再回到主程序里面运行100次……如此反复运行50次。
               * 写出程序。

               */

这里的设计思路就是一个主线程main,一个子线程sub,把其中相同的同步锁放在一个类中,然后在线程中调用这个类的对象。

程序与解说都在代码里面:

package Multithreading;
/**
 * 设计原则,把相同的设计到一个类中去,让他们自己管理自己的状态,
 * 不要让外部的线程去管理。
 * Lock比传统线程模型中的synchronized方式更加面向对象,
 * 锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,
 * 它们必须用同一个Lock对象。锁是上在  代表要操作的资源的类的内部方法中,
 * 而不是线程代码中!!
 * 读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,
 * 这是jvm自己控制的,你只要上好相应的锁即可。
 * 如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;
 * 如果你的代码修改数据,只能有一个人在写,且不能同时读,那就上写锁。
 * 总之,读的时候上读锁,写的时候上写锁。
 */
public class TraditionalThreadCommunication {
	public static void main(String[] args) {
		final Business business = new Business();
		new Thread(new Runnable() {//线程只是调用包装好的Business类
			public void run() {
				for (int i = 1; i <= 50; i++) {
					business.sub(i);
				}
			}
		}).start();

		for (int i = 1; i <= 50; i++) {
			business.main(i);
		}
	}
}

//锁是上在  代表要操作的资源的类的内部方法中
class Business {
	private boolean bShouldSub = true;
	public synchronized void sub(int i) {
		while(!bShouldSub){//此处也可以用if,不过while更好,while是判断两次,程序更加健硕
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		for (int j = 1; j <= 10; j++) {
			System.out.println("sub thread sequece of " + j + ",loop of " + i);
		}
		bShouldSub = false;
		/**
		 * wait 与 notify实现线程间的通信
		 * 经验:要用到共同数据(包括同步锁)或共同算法的若干个方法应该归在同一个类身上,
		 * 这种设计正好体现了高聚类和程序的健壮性
		 */
		this.notify();
	}

	public synchronized void main(int i) {
		while (bShouldSub) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		for (int j = 1; j <= 100; j++) {
			System.out.println("main thread sequece of " + j + ",loop of " + i);
		}
		bShouldSub = true;
		this.notify();
	}
}


微总结:今天主要巩固了线程的创建,线程中的计时器,线程的同步锁,线程间的通信。都是比较基础的东西。里面涉及一些语法基础要注意。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值