线程的创建
线程:程序中单个顺序的流控制称为线程
一个进程中可以含有多个线程
在操作系统中可以查看线程数
如:在Windows中,在任务管理器,右键,选择列,选中“线程数”
一个进程中的多个线程
分享CPU(并发的或以时间片的方式)
图示如下:共享内存(如多个线程访问同一对象)
Java从语言级别支持多线程
如:Object中wait()
, notify()
,java.lang中的类 Thread
线程体---- run()方法来实现的。
线程启动后,系统就自动调用run()方法。参考相关书:
所以我们自己调用run是不会产生新的线程的。
-
通常,run()方法执行一个时间较长的操作
如一个循环
显示一系列图片
下载一个文件
创建线程
1. 通过继承Thread类创建线程可以实现新线程:
class MyThread extends Thread {
public void run() {
for(int i=0;i<100;i++) {
System.out.print (" " + i);
public void run() { ...}
}
//生成新线程
Thread thread = new Thread(mytask); thread.start();
}
}
然而,并不推荐上述方式。参阅了《Introduction to Java Programming》:
This approach is, however, not recommended because it mixes the task and the mechanism of running the task. Separating the task from the thread is a preferred design.
因为Thread中还有其他的方法,这样可能会混淆了Thread中固有的机制。
Thread类的方法很多,如下:
所以,更好的方式是重新定义一个类来实现抽象类Runnable类,然后采用基于接口的对象注入的方式新建一个Thread的对象。如第二个方法。
2. 通过向Thread()构造方法传递Runnable对象来创建线程
class MyTask implements Runnable {
public void run() { ...}
}
//生成新线程
Thread thread = new Thread(mytask); thread.start();
}
图示如下:
2.1 匿名类及Lambda表达式
使用示例:
public class TestThread4Anonymous {
public static void main(String args[]) {
//匿名类
new Thread(){
public void run() {
for(int i=0; i<10; i++)
System.out.println(i);
}
}.start();
//Lambda表达式
new Thread( ( ) -> {
for(int i=0; i<10; i++)
System.out.println(" "+ i);
} ).start();
}
}
线程的控制
对线程的基本控制
线程的启动:
thread.start()
线程的结束:常用的方法是设定一个标记变量,结束相应的循环及方法。
暂时阻止线程的执行,比如在线程中用暂停方法:
thread.sleep( 1000 );
线程的优先级
调用设定线程的优先级setPriority( int priority)
方法,默认有以下三种MIN_PRIORITY
,MAX_PRIORITY
,NORM_PRIORITY
后台线程
线程有两种:
一类是普通线程(非Daemon线程)
在Java程序中,若还有非Demon线程,则整个程序就不会结束一类是Daemon线程(守护线程,后台线程)
使用setDaemon(true);
如果普通线程结束了,则后台线程自动终止。
注:垃圾回收线程是后台线程
代码
public class daemon {
public static void main(String args[]) {
//创建子线程
Runnable t = new MyThread2();
Thread thread = new Thread(t);
thread.setDaemon(true);
thread.start();
System.out.println( "主线程开始运行." );
try{
Thread.sleep(500);
}
catch(InterruptedException ex){}
System.out.println("主线程结束,子线程由于设置为守护线程,所以也应该提前结束.");
}
}
class MyThread2 implements Runnable {
public void run() {
for(int i=0; i<10; i++ ){
System.out.println( "子线程应该循环10次,当前的第"+i+"次");
try{ Thread.sleep(100); }
catch(InterruptedException ex){}
}
}
}
输出结果:
主线程开始运行.
子线程应该循环10次,当前的第0次
子线程应该循环10次,当前的第1次
子线程应该循环10次,当前的第2次
子线程应该循环10次,当前的第3次
子线程应该循环10次,当前的第4次
主线程结束,子线程由于设置为守护线程,所以也应该提前结束.
可以从上面看出,守护线程即使在运行中,也应该随着主线程的结束而提前结束。
守护线程的使用:
thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
在Daemon线程中产生的新线程也是Daemon的。
守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
线程的同步
为什么线程需要同步:
同时运行的线程需要共享数据、就必须考虑其它线程的状态与行为,这时就需要实现同步。
Java实现流程:
Java引入了对象互斥锁的概念,来保证共享数据操作的完整性。
每个对象都对应于一个monitor(监视器),它上面 一个称为“互斥锁(lock, mutex)”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized
用来与对象的互斥锁联系。
synchronized
的用法(2种)
-
对代码片断:
synchronized(对象){ 。。。。}
-
对某个方法:
synchronized
放在方法声明中,如public synchronized void push(char c ){ 。。。。}
以上相当于对
synchronized(this)
, 表示整个方法为同步方法。
线程的同步控制
使用wait()
方法可以释放对象锁,使用notify()
或notifyAll()
可以让等待的一个或所有线程进入就绪状态。
Java里面可以将wait()
和notify()
放在synchronized里面,是因为Java是这样处理的:
在
synchronized
代码被执行期间,线程调用对象的wait()
方法,会释放对象锁标志,然后进入等待状态,然后由其它线程调用notify()
或者notifyAll()
方法通知正在等待的线程。
并发的类
JDK1.5中增加了更多的类,以便更灵活地使用锁机制
Lock接口、ReentrantLock类,如下:
ReadWriteLock接口、ReentrantReadWriteLock类
如.writeLock()
,.lock()
,.readLock()
,.unlock()
,这些方法。
并发API(线程池)
Concurrent包
java.util.concurrent包中增加了一些方便的类
-
常用于很少写入而读取频繁的对象
CopyOnWriteArrayList
、CopyOnWriteArraySet
-
ConcurrentHashMap
putIfAbsent()
,remove()
,replace()
ArrayBlockingQueue
使用线程池
线程池相关的类
ExecutorService 接口、ThreadPoolExecutor 类
Executors 工具类
他们同样位于Concurrent接口的实现下面,如下:
常见的用法
ExecutorService pool = Executors.newCachedThreadPool();
使用其
execute( Runnable r)
方法
代码
import java.util.concurrent.*;
/** 创建线程池示例
*/
public class ExecutorDemo {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService executor = Executors.newCachedThreadPool();
// 提交任务到线程池
executor.execute(new PrintChar('a', 4));
executor.execute(new PrintChar('b', 2));
executor.execute(new PrintNum(2));
//关闭线程池
executor.shutdown();
}
}
class PrintChar implements Runnable {
private char charToPrint; // The character to print
private int times; // The times to repeat
/** 给定打印次数,打印特定字符
*/
public PrintChar(char c, int t) {
charToPrint = c;
times = t;
}
输出结果
aaaabb 1 2
流式操作及并行流
这里是指Java8新增的函数式思想,并不是具体的文件输入输出流。使得某些编程语句更加流畅的表达。
比如,对数组进行流化:
Arrays.stream(a)
.filter( i -> i>20 )
.map(i->i*i)
.sorted()
.distinct()
.limit(10)
.max();
stream的操作种类
流操作分成两类:
-
中间的 -中间的操作保持流打开状态,并允许后续的操作。
如:
filter
sorted
limit
map
-
末端的 - 末端的操作必须是对流的最终操作。
如:
max
min
count
forEach
findAny
流步骤
从某个源头获得一个流。
执行一个或更多的中间的操作。
执行一个末端的操作。
如何得到流
对于数组
Arrays.stream(ary)
对于collection (包括List)
用
list.stream()
对于Map,没有流,但提供了类似的方法
如
map.putIfAbsent
map.computeIfPresent
map.merge
代码
import java.util.*;
class UseStream
{
public static void main(String[] args)
{
List<Integer> a = Arrays.asList(1,2,5,7,3);
System.out.println(
a.stream()
.mapToInt(i->(int)i)
.filter( i -> i>2 )
.map( i -> i*i )
.sorted()
.distinct()
.limit(10)
.max()
);
}
}
运行结果:
OptionalInt[49]
流的并行计算(略)
只需将上面代码的.stream()
换成 .parallelStream()
。
其他都不变,就可以实现并行计算
可以说,stream就是为并行运算而生的,它封装的并行计算的大量内部细节。