关于Java多线程和并发运行的学习(一)

来源:互联网 发布:苏亚星多媒体教学软件 编辑:程序博客网 时间:2024/05/16 10:57


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


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

<span style="font-size:14px;">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) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("1:" + Thread.currentThread().getName());}}};thread.start();/** * 第二种方法 */// 创建一个runnable对象,将线程运行的代码封装起来Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {while (true) {try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("2:" + Thread.currentThread().getName());}}});thread2.start();new Thread(new Runnable() {@Overridepublic void run() {while (true) {try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("runnable:"+ Thread.currentThread().getName());}}}) {public void run() {while (true) {try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("Thread:"+ Thread.currentThread().getName());}}}.start();}}</span>

两种方法中,一种是直接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+"次");}}}


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

<span style="font-size:14px;">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{@Overridepublic 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) {// TODO Auto-generated catch blocke.printStackTrace();}}}}</span>


以上程序是构造一个连环炸弹的程序,并且炸弹爆炸的间隔时间是2s和4s轮流的,先2s,再4s,然后又2s,再4s。

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

如何让他们轮流?

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


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

<span style="font-size:14px;">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) {// // TODO Auto-generated catch block// e.printStackTrace();// }// outputer.output("qiudong");// }// }// }).start();new TraditionalThreadSynchronized().init();}// 要想创建内部类的实体对象,就必须有一个外部类的实体对象private void init() {final Outputer outputer = new Outputer();new Thread(new Runnable() {@Overridepublic void run() {while (true) {try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}outputer.output1("Andy");}}}).start();new Thread(new Runnable() {@Overridepublic void run() {while (true) {try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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$Outputerint 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();}}}</span>

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


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

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

               */


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

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

<span style="font-size:14px;">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 blocke.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 blocke.printStackTrace();}}for (int j = 1; j <= 100; j++) {System.out.println("main thread sequece of " + j + ",loop of " + i);}bShouldSub = true;this.notify();}}</span>


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



0 0
原创粉丝点击