【Java之】多线程学习笔记
最近在学习thinking in java(第三版),本文是多线程这一章的学习总结。
-----------------------------------------------------------------------------------------------------------------------------------------------------------
内容:
创建线程的两种方法,继承Thread和implements Runnable接口。
设置线程的优先权Priorities。
后台线程。
线程的join方法。
介绍两种匿名内部类的实现。
有响应的用户界面实例。
不正确的访问资源AlwaysEven.java。
解决资源共享问题的方法:synchronized。1,同步方法。2,同步方法块。DualSynch.java
线程阻塞的四个原因。
线程时间的协作,wait()和notify(),notifyAll(),厨师和服务员的例子。
------------------------------------------------------正文开始---------------------------------------------------------------------
1,创建线程的两种方法。
- 继承Thread类:
代码:
public class SimpleThread extends Thread { private int countDown = 5; private static int threadCount = 0; public SimpleThread(){ super("" + ++threadCount );//参数是线程名 start(); } public String toString(){ return "#" + getName() + " : " + countDown; } public void run(){ while(true){ System.out.println(this); if(--countDown == 0) return; } }
public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub for(int i = 0; i < 5; i++){ new SimpleThread();
}
}
}
通过getName()得到线程名,System.out.println(this);时this返回的是toString()返回的字符串。
整个步骤:在main函数中,首先调用构造器来构造对象,在构造器中调用了start()方法来配置线程,然后由线程执行机制调用run()。如果不调用start(),线程永远不会启动。(start也可以不在构造器中调用。)
注:在main()中创建若干个Thread对象的时候,并没有获得他们中的任何一个的引用。对于普通对象而言这会使它成为垃圾回收器要回收的目标,但对于Thread对象就不会了。每个Thread对象都需要“注册”自己,所以实际上在某个地方存在着对它的引用,垃圾回收器只有在线程离开了run()并且死亡之后才能把它清理掉。
- implements Runnable接口:
如果你的类已经继承了其他的类,在这种情况下,就不可能同时还继承Thread(Java不支持多重继承,但是支持多实现)。这时可以实现Runnable接口来达到目的。
代码:
public class RunnableThread implements Runnable { private int countDown = 5;
public String toString(){ return "#" + Thread.currentThread().getName() + " : " + countDown; }
public void run(){ while(true){ System.out.println(this); if(--countDown == 0) return; }
}
public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub for(int i = 1; i <= 5; i++){ new Thread(new RunnableThread(),""+i).start(); }
}
}
由于不是继承Thread类,所以调用getName()需要通过调用Thread.currentThread()方法。
2,设置线程的优先权Priorities:
优先权告诉调度程序线程的重要性如何。如果有许多线程被阻塞,那么调度程序将倾向于让优先权最高的线程先执行。
代码:
public class SimplePriorities extends Thread {
private int countDown = 5;
// private volatile double d = 0;
public SimplePriorities(int priority){
setPriority(priority);
start();
}
public String toString(){
return super.toString() + " : " + countDown;
}
public void run(){
while(true){
System.out.println(this);
if(--countDown == 0) return;
}
}
public static void main(String[] args){
// TODO Auto-generated method stub
new SimplePriorities(Thread.MAX_PRIORITY);
for(int i = 0; i < 5; i++){
new SimplePriorities(Thread.MIN_PRIORITY);
}
}
} 运行结果:
Thread[Thread-0,10,main] : 5
Thread[Thread-0,10,main] : 4
Thread[Thread-0,10,main] : 3
Thread[Thread-0,10,main] : 2
Thread[Thread-0,10,main] : 1
Thread[Thread-2,1,main] : 5
Thread[Thread-4,1,main] : 5
Thread[Thread-2,1,main] : 4
Thread[Thread-5,1,main] : 5
Thread[Thread-5,1,main] : 4
Thread[Thread-1,1,main] : 5
Thread[Thread-4,1,main] : 4
Thread[Thread-4,1,main] : 3
Thread[Thread-1,1,main] : 4
Thread[Thread-5,1,main] : 3
Thread[Thread-5,1,main] : 2
Thread[Thread-5,1,main] : 1
Thread[Thread-2,1,main] : 3
Thread[Thread-1,1,main] : 3
Thread[Thread-1,1,main] : 2
Thread[Thread-1,1,main] : 1
Thread[Thread-4,1,main] : 2
Thread[Thread-3,1,main] : 5
Thread[Thread-3,1,main] : 4
Thread[Thread-4,1,main] : 1
Thread[Thread-2,1,main] : 2
Thread[Thread-3,1,main] : 3
Thread[Thread-2,1,main] : 1
Thread[Thread-3,1,main] : 2
Thread[Thread-3,1,main] : 1
在上面代码中,使用super.toString()用来打印线程的名称,优先级,线程所属的‘线程组’。
可以自己指定优先级别(1-10),不过为了可移植性,一般使用MAX_PRIORITY(10),MIN_PRIORITY(1),NORM_PRIORITY(5),主线程的优先级默认是5。
3,后台线程:
后台线程又称守护线程,只有所有非后台线程结束,程序才终止,也就是说,后台线程是伴随着非后台线程的,非后台线程不存在了,自然后台线程也就结束了。Main()就是一个非后台线程。
代码:
public class SimpleDaemons extends Thread{ public SimpleDaemons() {
// TODO Auto-generated constructor stub
setDaemon(true);
start();
}
@Override public void run() { // TODO Auto-generated method stub
while(true){
try {
sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this+"---"+this.isDaemon());
// return;
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i = 0; i < 10; i++)
new SimpleDaemons();
}
}
必须在start()前调用setDaemon()方法,才能把它设置为后台线程。
上述代码执行后没有结果,因为当主线程结束,程序就终止了,后台线程在要打印之前就已经结束了。如果把setDaemon()注释掉,再执行的结果完全不一样,将是无限循环的打印出线程信息,因为当主线程结束后,在主线程中创建的非后台线程还没有结束,程序并没有终止。
4,线程的join方法。
一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续执行。Join()方法也可以带上一个超时参数(毫秒),这样如果目标线程在这段时间到期时还没有结束的话,join()方法总能返回。
我觉得join()方法就是线程的插队嘛…
代码:
class MySleeper extends Thread{
public MySleeper(String name){
super(name);
start();
}
public void run(){
try {
sleep(1500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(getName()+" has awakened."); }
} class MyJoiner extends Thread{
private MySleeper mysleeper; public MyJoiner(String name, MySleeper mysleeper){
super(name);
this.mysleeper = mysleeper;
start();
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
mysleeper.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(getName()+" join completed.");
} } public class MyJoining { public static void main(String[] args) {
// TODO Auto-generated method stub
MySleeper
mysleepy = new MySleeper("Sleepy");
MyJoiner
dopey = new MyJoiner("Dopey",mysleepy);
}
} 运行结果:(1.5s后)
Sleepy has awakened.
Dopey join completed.
上面代码中在MyJoiner的run方法中调用了mysleeper.join(),即表明MySleeper线程插入到了MyJoiner线程之前去了,当前者执行完后再执行后者。
5,介绍两种匿名内部类的实现:
代码:
class InnerThread {
//继承自Thread的匿名内部类
private int countDown = 5;
private Thread t;
//构造函数
public InnerThread(String name){
t= new Thread(name){
public void run(){
while(true){
System.out.println(this);
if(--countDown == 0) return;
try {
sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public String toString(){
return getName()+": "+countDown;
}
};
t.start();
}
} class InnerRunnable{
//实现Runnable接口的匿名内部类
private int countDown = 5;
private Thread t;
//构造函数
public InnerRunnable(String name){
t = new Thread(new Runnable(){
public void run(){
while(true){
System.out.println(this);
if(--countDown == 0) return;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public String toString(){
return Thread.currentThread().getName()+": "+countDown;
}
},name);
t.start();
}
} public class Anonymous { public static void main(String[] args) {
// TODO Auto-generated method stub
//使用匿名内部类
new InnerThread("InnerThread");
new InnerRunnable("InnerRunnable");
}
} 执行结果:
InnerThread: 5
InnerRunnable: 5
InnerThread: 4
InnerRunnable: 4
InnerThread: 3
InnerRunnable: 3
InnerThread: 2
InnerRunnable: 2
InnerThread: 1
InnerRunnable: 1
6,有响应的用户界面实例。
下面的这个例子,是一个利用多线程实现的一边专注于运算,一边得带控制台输入的多线程应用。如果是单线程,将无法做到。
代码:
class UnresponsiveUI {
private volatile double d = 1;
public UnresponsiveUI() throws Exception {
while(d>0){
d = d +(Math.PI+Math.E) / d;
}
System.in.read();
}
} public class ResponsiveUI extends Thread{
private static volatile double d = 1;
public ResponsiveUI(){
setDaemon(true);
start();
}
public void run(){
while(true)
d = d + (Math.PI+Math.E) / d;
} public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
// new UnresponsiveUI();
new ResponsiveUI();
// Thread.sleep(3000);//主线程休眠
System.in.read(); System.out.println(d);
}
} 运行结果:
输入a
打印出
24706.75604567532
执行后,任意输入一个数,就会打印出在ResponsiveUI中d的计算结果值。
如果去掉// new UnresponsiveUI();的注释,上面代码在UnresponsiveUI中将永远无法执行System.in.read();
7,不正确的访问资源:
当多个线程使用同一个资源时,会发生资源冲突问题。你永远不会知道线程是何时运行的,想象一下你坐在桌子旁边,手里有一把叉子,准备叉起盘子里的最后一块食物,当叉子碰到食物的时候,它突然消失了(因为你的线程被挂起,另一个线程跑进来偷走了食物)。
下面这个例子可以说明问题:
代码:
public class AlwaysEven {
private int i;
public void next(){
i++;i++;
}
public int getValue(){
return i;
} public static void main(String[] args) {
// TODO Auto-generated method stub
final AlwaysEven ae = new AlwaysEven();
new Thread("Watcher"){
public void run(){
int val = ae.getValue();
if(val % 2 != 0){
System.out.println(val);
System.exit(0);
}
}
}.start();
while(true){
ae.next();
}
}
}
从代码上看返回值显然都会是偶数,但是我的执行结果却是返回了1691.
8,解决资源共享问题的方法:synchronized。
既然多个线程操作同一个资源会出现意想不到的问题,那么解决办法是什么呢?
答案就是synchronized。
一般来说,共享资源一般是以对象形式存在的内存片断,也有可能是文件等其他形式。总之要控制对共享资源的访问,需要把它包装进一个对象。
每个对象都含有单一的锁,这个锁本身就是对象的一部分。当在对象上调用synchronized方法的时候,此对象都被加锁,这时该对象上的其他synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。
除了给synchronized方法外,如果你只是希望防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法,可以使用同步控制块来实现。形式如下:
Synchronized(syncObject) {
//code
}
在进入此段代码前,必须得到syncObject对象的锁。如果其他线程已经得到这个锁,那么就得等到锁被释放以后才能进入。
同步方法和同步代码块代码实例:
public class DualSynch {
private Object syncObject = new Object();
public synchronized void f(){
System.out.println("Inside f()");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
System.out.println("Leaving f()");
}
public void g(){
synchronized (syncObject) {
System.out.println("inside g()");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Leaving g()");
}
} public static void main(String[] args) {
// TODO Auto-generated method stub
final DualSynch ds = new DualSynch();
new Thread(){
public void run(){
ds.f();
}
}.start();
ds.g();
}
} 运行结果:
inside g()
Inside f()
Leaving f()
(等待约10秒)
Leaving g()
注:如果你将g()改成使用synchronized方法而不是synchronized (syncObject)块,你会发现执行结果是先输出
inside g()
过10秒后再输出
Leaving g()
Inside f()
Leaving f()
另外,同步代码块的方式会比同步方法速度快一点,所以宁愿使用同步代码块而不是对整个方法进行同步控制。
9,线程阻塞的四个原因:
- sleep()
- wait()会将线程挂起,直到线程得到了notify()或notifyAll(),线程才会进入就绪状态。
- 线程在等待某个输入/输出。
- 线程试图在某个对象上调用其同步控制方法,但是对象锁不可用。
10,线程时间的协作,wait()和notify(),notifyAll(),厨师和服务员的例子:
在了解了线程之间可能存在的相互冲突以及怎样避免冲突之后,下一步是学习怎样使线程之间相互协作。这种协作通过Object的方法wait()和notify()来实现的。
注意sleep()和wait()的区别:
在wait()期间的对象锁是释放的,sleep()的时候锁并没有释放。
注意只能在同步控制方法或同步控制块里调用wait()、notify()和notifyAll()。
厨师与服务员的例子:
考虑某个餐馆,有一个厨师和一个服务员。服务员必须等待厨师准备好食物。当厨师准备好食物的时候,他通知服务员,后者将得到食物,然后回去继续等待。这是一个线程协作的极好的例子:厨师代表了生产者,服务员代表了消费者。
下面是模拟这个场景的代码:
class Order {
private static int i = 0;
private int count = i++;
public Order(){
if(count == 10){
System.out.println("Out of food, closing");
System.exit(0);
}
}
public String toString(){
return "Order "+count;
}
} class Waiter extends Thread{
private Restaurant restaurant;
public Waiter(Restaurant r){
restaurant = r;
start();
}
public void run(){
while(true){
while(restaurant.order == null)
synchronized (this) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("Waiter got "+ restaurant.order);
restaurant.order = null;
}
}
} class Chef extends Thread{
private Restaurant restaurant;
private Waiter waiter;
public Chef(Restaurant r,Waiter w){
restaurant = r;
waiter = w;
start();
}
public void run(){
while(true){
if(restaurant.order == null){
restaurant.order = new Order();
System.out.println("Order up! ");
synchronized (waiter) {
waiter.notify();
}
}
try {
sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
} public class Restaurant {
Order order;
public static void main(String[] args) {
// TODO Auto-generated method stub
Restaurant restaurant = new Restaurant();
Waiter waiter = new Waiter(restaurant);
Chef chef = new Chef(restaurant,waiter);
}
} 运行结果:
Order up!
Waiter got Order 0
Order up!
Waiter got Order 1
Order up!
Waiter got Order 2
Order up!
Waiter got Order 3
Order up!
Waiter got Order 4
Order up!
Waiter got Order 5
Order up!
Waiter got Order 6
Order up!
Waiter got Order 7
Order up!
Waiter got Order 8
Order up!
Waiter got Order 9
Out of food, closing
注意对notify()的调用必须首先获取Waiter对象waiter的锁。这样能保证如果两个线程试图在同一个对象上调用notify()时也不会互相冲突。
关于线程协作的例子,PipedWriter和PipedReader管道输入和输出是另外一个例子,允许不同的线程分别控制同一个管道的输入和输出,具体代码就不贴上来了。
另外Synchronized也会出现问题:死锁。
因为线程可以阻塞,且对象可以具有同步控制方法用以防止别的线程在锁还没有释放的时候就访问这个对象;所以就可能出现这种情况:某个线程在等待另一个线程,而后者又等待别的线程,这样一直下去,直到这个链条上的线程又在等待第一个线程释放锁。这得到了一个相互等待的死循环,没有哪个线程能继续。
经典死锁问题:五个哲学家就餐问题。
【Java之】多线程学习笔记的更多相关文章
- java进阶-多线程学习笔记
多线程学习笔记 1.什么是线程 操作系统中 打开一个程序就是一个进程 一个进程可以创建多个线程 现在系统中 系统调度的最小单元是线程 2.多线程有什么用? 发挥多核CPU的优势 如果使用多线程 将计算 ...
- java多线程学习笔记——详细
一.线程类 1.新建状态(New):新创建了一个线程对象. 2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中, ...
- JAVA多线程学习笔记(1)
JAVA多线程学习笔记(1) 由于笔者使用markdown格式书写,后续copy到blog可能存在格式不美观的问题,本文的.mk文件已经上传到个人的github,会进行同步更新.github传送门 一 ...
- Java多线程学习笔记(一)——多线程实现和安全问题
1. 线程.进程.多线程: 进程是正在执行的程序,线程是进程中的代码执行,多线程就是在一个进程中有多个线程同时执行不同的任务,就像QQ,既可以开视频,又可以同时打字聊天. 2.线程的特点: 1.运行任 ...
- 多线程学习笔记九之ThreadLocal
目录 多线程学习笔记九之ThreadLocal 简介 类结构 源码分析 ThreadLocalMap set(T value) get() remove() 为什么ThreadLocalMap的键是W ...
- 《深入理解Java虚拟机》学习笔记
<深入理解Java虚拟机>学习笔记 一.走近Java JDK(Java Development Kit):包含Java程序设计语言,Java虚拟机,JavaAPI,是用于支持 Java 程 ...
- Java:NIO 学习笔记-3
Java:NIO 学习笔记-3 根据 黑马程序员 的课程 JAVA通信架构I/O模式,做了相应的笔记 3. JAVA NIO 深入剖析 在讲解利用 NIO 实现通信架构之前,我们需要先来了解一下 NI ...
- JAVA GUI编程学习笔记目录
2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...
- Java安全防御学习笔记V1.0
Java安全防御学习笔记V1.0http://www.docin.com/p-766808938.html
- java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)
java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...
随机推荐
- nc命令总结
1.远程拷贝文件从server1拷贝文件到server2上.需要先在server2上,用nc激活监听,server2上运行: 引用 [root@hatest2 tmp]# nc -l 1234 > ...
- 转:基础总结篇之一:Activity生命周期
熟悉javaEE的朋友们都了解servlet技术,我们想要实现一个自己的servlet,需要继承相应的基类,重写它的方法,这些方法会在合适的时间被servlet容器调用.其实android中的Acti ...
- 在国内使用cnpm代替npm
npm是Node.js的模块依赖管理工具,由于使用npm安装包是从国外服务器下载,在国内很容易受到网络的影响,速度非常慢,因此可以选用cnpm.cnpm可以使用淘宝团队提供的淘宝npm镜像,你可以用此 ...
- 【转】android 电池(二):android关机充电流程、充电画面显示
关键词:android 电池关机充电 androidboot.mode charger关机充电 充电画面显示 平台信息:内核:linux2.6/linux3.0系统:android/android4. ...
- winform —— listview创建表及简单的增删改查
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...
- winform —— 界面
winform 界面简单介绍 窗体事件:理解为委托变量,指向哪个函数,就执行哪个函数.窗体:显示窗体的过程是一个通过模板造对象的过程.先走构造函数,构造函数中的InitializeComponent( ...
- oracle取分组的前N条数据
select * from(select animal,age,id, row_number()over(partition by animal order by age desc) row_num ...
- Struts2中使用Session的两种方法
在Struts2里,如果需要在Action中使用到session,可以使用下面两种方式: 通过ActionContext 类中的方法getSession得到 Action实现org.apache.st ...
- PHP基础之 file_get_contents() 函数
定义和用法 file_get_contents() 函数把整个文件读入一个字符串中. 和 file() 一样,不同的是 file_get_contents() 把文件读入一个字符串. file_get ...
- 正确合理的建立MYSQL数据库索引
写在前面:索引对查询的速度有着至关重要的影响,理解索引也是进行数据库调优的起点.考虑如下情况,假设数据库中一个表有10^6条记录,DBMS的页面大小为4K,并存储100条记录.如果没有索引,查询将对整 ...