Android开发手记(27) Java多线程的操作

Java中常用的有关线程的操作有,判断线程是否启动、线程强制执行、线程休眠、线程中断、线程让步、线程同步等。下面就一一举例。

首先,我们新建一个MyThread类实现Runnable接口。基于此接口进行线程的相关操作。

class MyThread implements Runnable {
	public void run() {
		for (int i = 0; i < 3; i++) {
			System.out.println(Thread.currentThread().getName() + " 运行 " + i);
		}
	}
}

1、判断线程是否启动

判断线程是否启动很简单,通过Thread.isAlive()方法即可。

public class Test {
	public static void main(String[] args) {
		MyThread mt = new MyThread();
		Thread t = new Thread(mt);

		System.out.println("线程" + (t.isAlive() ? "已" : "未") + "启动");
		t.start();
		System.out.println("线程" + (t.isAlive() ? "已" : "未") + "启动");
	}
}

输出入下:

线程未启动
线程已启动
Thread-0 运行 0
Thread-0 运行 1
Thread-0 运行 2

2、线程强制执行

线程强制执行可以通过Thread.join()实现。我们首先来看一个没有强制执行的例子

public class Test {
	public static void main(String[] args) {
		MyThread mt1 = new MyThread();
		MyThread mt2 = new MyThread();
		Thread t1 = new Thread(mt1);
		Thread t2 = new Thread(mt2);

		try {
			t1.start();
			// t1.join();
			t2.start();
			// t2.join();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

程序输入如下:

Thread-0 运行 0
Thread-1 运行 0
Thread-0 运行 1
Thread-1 运行 1
Thread-0 运行 2
Thread-1 运行 2

我们发现,两个线程是并行运行的,因此,执行的结果为两个线程分别执行取得的输出然后在按照时间插入到输出流中。我们将join注释掉之后,发现程序的执行结果变为:

Thread-0 运行 0
Thread-0 运行 1
Thread-0 运行 2
Thread-1 运行 0
Thread-1 运行 1
Thread-1 运行 2

可以看出,执行的结果是线程1开始执行,等线程1执行完毕之后线程2才开始执行。

3、线程休眠

线程休眠通过Thread.sleep()实现。这是一个静态方法,所以需要通过Thread类来调用,而不是通过实例化之后的变量调用。另外,调用sleep()可能抛出InterruptedException异常,我们我们需要将其放在try-catch块中来捕获此异常。

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

		MyThread mt = new MyThread();
		Thread t = new Thread(mt);

		long stime = System.currentTimeMillis();
		t.start();
		System.out.println(System.currentTimeMillis() - stime);
		try {
			Thread.sleep(1000);
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println(System.currentTimeMillis() - stime);
	}
}

程序执行结果为:

0
Thread-0 运行 0
Thread-0 运行 1
Thread-0 运行 2
1000

使用System.currentTimeMiles()来获取程序运行的时间。我们发现:从开始执行到结束,花了1秒钟的时间,这个时间是main Thread休眠的时间。

4、线程中断

上一小节提到,线程在sleep过程中可以被打断并抛出InterruptedException异常,这个线程中断行为可以通过Thread.interrupt()实现。首先我们将时间统计移动到MyThread内部,然后在MyThread内每输出一次运行情况进行一次休眠操作。

class MyThread implements Runnable {
	public void run() {
		long stime = System.currentTimeMillis();
		System.out.println(System.currentTimeMillis() - stime);
		for (int i = 0; i < 3; i++) {
			System.out.println(Thread.currentThread().getName() + " 运行 " + i);
			try {
				Thread.sleep(1000);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		System.out.println(System.currentTimeMillis() - stime);
	}
}

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

		MyThread mt = new MyThread();
		Thread t = new Thread(mt);

		t.start();
		t.interrupt();
	}
}

程序输出为:

0
Thread-0 运行 0
java.lang.InterruptedException: sleep interrupted
Thread-0 运行 1	at java.lang.Thread.sleep(Native Method)
	at MyThread.run(Test.java:9)
	at java.lang.Thread.run(Thread.java:745)

Thread-0 运行 2
2002

程序从开始运行到结束,花了2秒左右的时间,但是明明在MyThread内休眠了3次,每次一秒,应该运行3秒左右。这是因为在线程第一次休眠的时候,在main线程内通过Thread.interrupt对第一次休眠进行了打断,所以这次休眠时间几乎为0,剩余的两次休眠没有被打断,所以总共执行时间为2秒左右。

5、线程让步

有时候,线程在执行过程中,需要等待其余线程的执行结果,才能继续自身的运算,这时候就需要进行线程让步。线程的让步可以通过Thread.yield来实现。

class MyThread implements Runnable {
	public void run() {
		for (int i = 0; i < 3; i++) {
			System.out.println(Thread.currentThread().getName() + " 运行 " + i);
			Thread.yield();
		}
	}
}

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

		MyThread mt1 = new MyThread();
		MyThread mt2 = new MyThread();
		Thread t1 = new Thread(mt1);
		Thread t2 = new Thread(mt2);

		t1.start();
		t2.start();

	}
}

输出结果为:

Thread-0 运行 0
Thread-1 运行 0
Thread-0 运行 1
Thread-1 运行 1
Thread-0 运行 2
Thread-1 运行 2

6、线程同步

Java中线程同步可以通过同步方法和同步代码块来完成:

(1)同步方法

我们首先来观察一个程序的执行结果

class MyThread implements Runnable {

	private int Tic = 5;
	@Override
	public void run() {
		for (int i = 0; i < 3; i++)
			if (Tic > 0)
				System.out.println(Thread.currentThread().getName() + " Ticket:" + Tic--);
	}
}

public class Test {
	public static void main(String[] args) {
		MyThread mt = new MyThread();
		for (int i = 0; i < 3; i++) {
			new Thread(mt).start();
		}
	}
}

执行结果为:

Thread-1 Ticket:5
Thread-2 Ticket:3
Thread-0 Ticket:4
Thread-2 Ticket:1
Thread-1 Ticket:2

我们发现,其结果是3个线程不分先后并行执行,当线程0运行的时候,线程2依然可以访问到方法体内部,反之依然。有时候(比如卖票系统)我们需要在一个线程运行的时候,另一个线程无法进入到正在运行的方法体内部,这时候我们就需要进行线程的同步与互斥操作。通过在方法前面加入synchronized可以指定一个方法为同步方法。

class MyThread implements Runnable {

	private int Tic = 5;
	@Override
	public synchronized void run() {
		for (int i = 0; i < 3; i++)
			if (Tic > 0)
				System.out.println(Thread.currentThread().getName() + " Ticket:" + Tic--);
	}
}

此时程序的运行结果为:

Thread-0 Ticket:5
Thread-0 Ticket:4
Thread-0 Ticket:3
Thread-2 Ticket:2
Thread-2 Ticket:1

可以看出,在增加了同步之后,票是一张一张被卖出去的,这样就可以有效的避免重票的情况。

(2)同步代码块

我们将上述代码改为如下。依然可以实现相同的功能

class MyThread implements Runnable {

	private int Tic = 5;

	@Override
	public void run() {
		synchronized (this) {
			for (int i = 0; i < 3; i++)
				if (Tic > 0)
					System.out.println(Thread.currentThread().getName() + " Ticket:" + Tic--);
		}
	}
}

(3)注意

1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。

2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的,

3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。

6、生产者消费者问题

class BreadContainer {
	public static final int maxNum = 300;
	private int num;

	public BreadContainer() {
	}

	public BreadContainer(int num) {
		this.num = num;
	}

	public synchronized void produceBread(int pdcNum, String producer) {
		while(num + pdcNum > maxNum){
			System.out.println(producer + " 要生产 " + pdcNum +" 个,资源充足无需生产,转入等待。");
			try{
				this.wait();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		num += pdcNum;
		System.out.println(producer + " 生产了 " + pdcNum + " 个,现有 " + num + " 个");
		this.notifyAll();
	}

	public synchronized void consumerBread(int csmNum, String consumer) {
		while (csmNum > num) {
			System.out.println(consumer + " 要消费 " + csmNum + " 个,资源不足无法消费,转入等待。");
			try {
				this.wait();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		num -= csmNum;
		System.out.println(csmNum + " 消费了 " + csmNum + " 个,现有 " + num + " 个");
		this.notifyAll();
	}

}

class Producer extends Thread {
	private int pdcNum;
	private BreadContainer bc;

	public Producer() {

	}

	public Producer(int pdcNum, BreadContainer bc, String producer) {
		this.pdcNum = pdcNum;
		this.bc = bc;
		this.setName(producer);
	}

	public void run() {
		bc.produceBread(pdcNum, this.getName());
	}
}

class Consumer extends Thread {
	private int csmNum;
	private BreadContainer bc;

	public Consumer() {

	}

	public Consumer(int csmNum, BreadContainer bc, String consumer) {
		this.csmNum = csmNum;
		this.bc = bc;
		this.setName(consumer);
	}

	public void run() {
		bc.consumerBread(csmNum, this.getName());
	}
}

public class Test {
	public static void main(String[] args) {
		BreadContainer bc = new BreadContainer(50);
		Producer p1 = new Producer(50, bc, "p1");
		Producer p2 = new Producer(200, bc, "p2");
		Producer p3 = new Producer(290, bc, "p3");
		Consumer c1 = new Consumer(70, bc, "c1");
		Consumer c2 = new Consumer(80, bc, "c2");

		c1.start();
		c2.start();
		p1.start();
		p2.start();
		p3.start();
	}
}

输出结果为:

c1 要消费 70 个,资源不足无法消费,转入等待。
p3 要生产 290 个,资源充足无需生产,转入等待。
p2 生产了 200 个,现有 250 个
p1 生产了 50 个,现有 300 个
80 消费了 80 个,现有 220 个
p3 要生产 290 个,资源充足无需生产,转入等待。
70 消费了 70 个,现有 150 个
p3 要生产 290 个,资源充足无需生产,转入等待。
时间: 2024-10-07 05:46:00

Android开发手记(27) Java多线程的操作的相关文章

Android开发实践:Java层与Jni层的数组传递

Android开发中,经常会在Java代码与Jni层之间传递数组(byte[]),一个典型的应用是Java层把需要发送给客户端的数据流传递到Jni层,由Jni层的Socket代码发送出去,当然,Jni层也需要把从Socket接收到的数据流返回给Java层.我简单地总结了一下,从Java层到Jni层,从Jni层到JAVA层,各有3种传递方式,下面用代码示例简单地介绍一下. 示例代码的主要文件有两个,一个是Native.java,是Java层的类:另一个是Native.c,是JNI层的文件,关键的地

Android 开发中三种多线程

在开发工程中线程可以帮助我们提高运行速度,Android开发中我知道的线程有四个一个是老生长谈的Thread,第二个是asyncTask,第三个:TimetTask,第四个是Looper,四个多线程各有个的有点,Thread的运行速度是最快的,AsyncTask的规范性是最棒的,其它两个也有自己的优点. 1.Thread与Handler组合,比较常见 Handler主要是帮助我们来时时更新UI线程 这里在后天加载100张图片,然后没加载完成一个用handler 返回给UI线程一张图片并显示 最后

转:Android开发实践:Java层与Jni层的数组传递

Android开发中,经常会在Java代码与Jni层之间传递数组(byte[]),一个典型的应用是Java层把需要发送给客户端的数据流传递到Jni层,由Jni层的Socket代码发送出去,当然,Jni层也需要把从Socket接收到的数据流返回给Java层.我简单地总结了一下,从Java层到Jni层,从Jni层到JAVA层,各有3种传递方式,下面用代码示例简单地介绍一下. 示例代码的主要文件有两个,一个是Native.java,是Java层的类:另一个是Native.c,是JNI层的文件,关键的地

Android开发手记(21) 遍历文件夹

我们在遍历文件夹的时候由于涉及到SD卡相关操作,所以我们需要添加如下权限: <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> 首先,需要检查SD卡挂载状态: boolean sdCard

你应该这样去开发接口:Java多线程并行计算

所谓的高并发除了在架构上的高屋建瓴,还得需要开发人员在具体业务开发中注重自己的每一行代码.每一个细节,面子有的同时,更重要的还是要有里子. 面对性能,我们一定要有自己的工匠精神,不可以对任何一行代码妥协! 今天和大家分享在业务开发中如何降低接口响应时间的一个小技巧,也是大家日常开发中比较普遍存在的一个问题,即如何提高程序的并行计算能力? 本文主要包含以下内容: 顺序执行很慢 线程池+Future并行计算 使用Java8的CompletableFuture 使用Guava的ListenableFu

Android开发手记(29) 基于Http的LaTeX数学公式转换器

本文将讲解如何通过codecogs.com和Google.com提供的API接口来将LaTeX数学函数表达式转化为图片形式.具体思路如下: (1)通过EditText获取用户输入的LaTeX数学表达式,然后对表达式格式化使之便于网络传输. (2)将格式化之后的字符串,通过Http请求发送至codecogs.com或者Google.com. (3)获取网站返回的数据流,将其转化为图片,并显示在ImageView上. 具体过程为: 1.获取并格式化LaTeX数学表达式 首先,我们在这个网站输入LaT

Android开发手记(31) 使用MediaRecorder录音

使用Android手机的时候,有时我们会用到录音功能,本文简单的介绍了如何使用MediaRecorder通过手机自带麦克风进行录音. 首先,既然是录音,我们需要录音和写外存的权限: <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECORD_AUDIO&q

Android开发手记(15) 拨打电话和收发短信

1.Intent简介 Android组价之间的通信,由Intent来协助完成.Intent负责对应用中一次操作的动作.动作涉及数据.附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给调用的组件,并完成组件的调用. Intent可以启动一个Activity,也可以启动一个Service,还可以发起一个广播Broadcasts.分别通过startActivity();startService();startBroadcasts();来执行操作. 在

Android开发手记(16) 数据存储一 SharedPreferences

SharedPreferences是Android中最容易理解的数据存储技术,实际上SharedPreferences处理的就是一个key-value(键值对)SharedPreferences常用来存储一些轻量级的数据.这类似于C++中Map的数据存储方式(实际上在最后生成的.xml文件内,就是以Map格式存储的). 获取SharedPreferences的两种方式: 1.调用Context对象的getSharedPreferences()方法 2.调用Activity对象的getPrefer