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 个,资源充足无需生产,转入等待。

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

  1. Android开发手记(26) Java多线程的实现

    随着多核CPU的发展,多线程编程显得越来越重要,本文将对Java中的多线程编程进行一些简单的探讨. 1.继承Thread类 Java中,线程运行的基本单位是Thread,所以,我们可以通过继承Thre ...

  2. Android开发手记(30) 触摸及手势操作

    触摸操作在现在智能手机系统中起到举足轻重的作用,本文将对安卓中的触摸以及一些简单手势的操作进行简单的介绍. 1.触摸 首先是关于触摸的判断,有两种方法可以判断的触摸操作. (1)setOnTouchL ...

  3. Android 开发中三种多线程

    在开发工程中线程可以帮助我们提高运行速度,Android开发中我知道的线程有四个一个是老生长谈的Thread,第二个是asyncTask,第三个:TimetTask,第四个是Looper,四个多线程各 ...

  4. Android 开发手记一NDK编程实例

    在Android上,应用程序的开发,大部分基于Java语言来实现.要使用c或是c++的程序或库,就需要使用NDK来实现.NDK是Native Development Kit的简称.它是一个工具集,集成 ...

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

    转载:http://www.linuxidc.com/Linux/2014-03/97561.htm Android开发中,经常会在Java代码与Jni层之间传递数组(byte[]),一个典型的应用是 ...

  6. Android开发手记(17) 数据存储二 文件存储数据

    Android为数据存储提供了五种方式: 1.SharedPreferences 2.文件存储 3.SQLite数据库 4.ContentProvider 5.网络存储 本文主要介绍如何使用文件来存储 ...

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

    我们在遍历文件夹的时候由于涉及到SD卡相关操作,所以我们需要添加如下权限: <uses-permission android:name="android.permission.WRIT ...

  8. Android开发手记(14) 使用MediaPlayer播放mp3

    1.获取MediaPlayer实例 (1)可以直接通过new或者create方式: 调用setDataSource和create的区别是,create时已经执行了MediaPlayer.prepare ...

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

    所谓的高并发除了在架构上的高屋建瓴,还得需要开发人员在具体业务开发中注重自己的每一行代码.每一个细节,面子有的同时,更重要的还是要有里子. 面对性能,我们一定要有自己的工匠精神,不可以对任何一行代码妥 ...

随机推荐

  1. Windows Azure 配置SSTP

    方法參考下面文章的步驟. 这个成功.http://freevpnba.com/windows-azure-sstp-vpn/ 这个没成功,不知道为什么.http://diaosbook.com/pos ...

  2. Flyer

    hdu4768:http://acm.hdu.edu.cn/showproblem.php?pid=4768 题意:给你1--2^32个位置,然后有m个操作,每次操作给你3个数 a,b,c,然后在a, ...

  3. VSFTP被动模式

    搞了几个弯路,各种办法都试了. 动静最小的,还是定义端口. 还有虚拟用户,配置太多,只适用于小范围吧.又要pam.d,又要chroot之类的,nologin也必不可少. ~~~~~~~~~ 限制被动模 ...

  4. Java OAuth开发包资料

    原文地址:http://www.oschina.net/project/tag/307/oauth?lang=19&sort=time

  5. httpclient 超时设置

    最近项目客户反应超时经常出现:现已经总结超时设置: 使用是apache的HttpClient: DefaultHttpClient:请求超时httpclient.getParams().setPara ...

  6. EAFP和LBYL 两种防御性编程风格

    EAFP:Easier to ask for forgiveness than permission 获得事后原理总是比事先得到许可要容易的多. 这个EAFP在python中表现的比较多.EAFP,T ...

  7. 14.6.11 Configuring Optimizer Statistics for InnoDB 配置优化统计信息用于InnoDB

    14.6.11 Configuring Optimizer Statistics for InnoDB 配置优化统计信息用于InnoDB 14.6.11.1 Configuring Persisten ...

  8. Solr 多核(MultiCore)配置

    Solr Multicore意义        Solr Multicore 是 solr 1.3 的新特性.其目的一个solr实例,可以有多个搜索应用.< xmlnamespace prefi ...

  9. REST client 基于浏览器的测试工具

    以前在开发webservice服务,都是自己基于HTTP协议,自己写一个测试程序来进行测试,最近在研究RestFul,对以前webservice服务进行了重构,总结了不少经验,今天就给大家介绍下几款R ...

  10. Linux学习笔记23——取消线程

    一 相关函数 1 发送终止信号 #include <pthread.h> int pthread_cancel(pthread_t thread); 2 设置取消状态 #include & ...