第1章 Java 多线程技能
标签: Java多线程编程
《Java多线程编程核心技术》 个人笔记
本章关键技术点:
- 线程如何启动
- 如何使线程暂停
- 如何使线程停止
- 线程的优先级
- 线程安全的相关问题
进程和多线程的概念及线程的优点
- 进程(Process):是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体
- **线程:**可以理解为是在进程中独立运行的子任务。
- 使用多线程(异步)的优点:可以在同一时间内运行更多不同种类的任务。
- 单任务的特点:排队执行,也就是同步。缺点是CPU利用率低。
使用多线程
public static void main(String[] args) {
//获取当前线程的名字
System.out.println(Thread.currentThread().getName());
}
-
实现多线程的方式主要有两种,一种是继承Thread类,另一种是实现Runnable接口
-
在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。
-
Thread.java类中的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法,随机异步执行
-
如果调用thread.run()则是同步执行,此线程是交给main()主线程来调用run()方法,所以必须等run()中的代码执行完才能执行后面的代码
-
执行的start()的顺序并不代表线程的启动顺序
-
也就是start()方法是异步,run()则只是像普通方法一样,是同步的
-
根据Thread.java的构造函数,构造函数支持传入一个Runnable接口的对象,也可以传入一个Thread类的对象,所以完全可以将一个Thread对象中的run()方法交给其他线程进行调用
public static void main(String[] args) {
Runnable runnable=new MyRunnable();
Thread thread=new Thread(runnable);
thread.start();
System.out.println("main!");
}
- 自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,这在多个线程之间进行交互时是很重要的一个技术点。
- 不共享数据的情况(每个线程有各自的count变量)
public class MyThread extends Thread {
private int count = 5;
public MyThread(String name) {
super();
this.setName(name);
}
@Override
public void run() {
super.run();
while (count > 0) {
count--;
System.out.println("由" + this.currentThread().getName()
+ " 计算,count=" + count);
}
}
}
public class Run {
public static void main(String[] args) {
MyThread a = new MyThread("A");
MyThread b = new MyThread("B");
MyThread c = new MyThread("C");
a.start();
b.start();
c.start();
}
}
- 共享数据的情况(多个线程可以访问同一个变量)
public class MyThread extends Thread {
private int count=5;
@Override
synchronized public void run() {
super.run();
count--;
System.out.println("由 "+this.currentThread().getName()+" 计算㣬count="+count);
}
}
public class Run {
public static void main(String[] args) {
MyThread mythread=new MyThread();
Thread a=new Thread(mythread,"A");
Thread b=new Thread(mythread,"B");
Thread c=new Thread(mythread,"C");
Thread d=new Thread(mythread,"D");
Thread e=new Thread(mythread,"E");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
- 如果有多个线程同时访问,那么一定会出现非线程安全。(输出的顺序不是4,3,2,1,0)
- 通过在run方法前加入synchronized关键字,使多个线程在执行run方法时,以排队的方式进行处理。
- synchronized可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”
currentThread()方法
- currentThread()方法可以返回代码段正在被哪个线程调用的信息
public class CountOperate extends Thread {
public CountOperate() {
System.out.println("CountOperate---begin");
System.out.println("Thread.currentThread().getName()="
+ Thread.currentThread().getName());
System.out.println("this.getName()=" + this.getName());
System.out.println("CountOperate---end");
}
@Override
public void run() {
System.out.println("run---begin");
System.out.println("Thread.currentThread().getName()="
+ Thread.currentThread().getName());
System.out.println("this.getName()=" + this.getName());
System.out.println("run---end");
}
}
public class Run {
public static void main(String[] args) {
CountOperate c = new CountOperate();
Thread t1 = new Thread(c);
t1.setName("A");
t1.start();
}
}
/*----结果-----
CountOperate---begin
Thread.currentThread().getName()=main
this.getName()=Thread-0
CountOperate---end
run---begin
Thread.currentThread().getName()=A
this.getName()=Thread-0
run---end
*/
isAlive()方法
- 方法isAlive()的功能是判断当前的线程是否处于活动状态。活动状态就是线程已经启动且尚未终止。线程处于正在正在运行或准备开始运行的状态,就认为线程是“存活”的。
sleep()方法
方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”(this.currentThread()返回的线程)休眠(暂停执行)。
getId()方法
- 取得线程的唯一标识
停止线程
- Thread.stop()方法是不安全的,已经被废弃。
- 在java中有以下3中方法可以终止正在运行的线程:
- 可以使用退出标识,是线程正常退出,也就是当run方法完成后线程终止
- 使用stop(),但该方法不安全,已被废弃,同被废弃的还有suspend,resume
- 使用interrupt()方法中断线程
停止不了的线程
- interrupt()方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程
判断线程是否是停止状态
- this.interrupted()方法:测试当前线程是否已经中断,当前线程是指运行this.interrupted()方法的线程。执行后具有将状态标志清除为false的功能
- this.isInterrupted():测试线程Thread对象是否已经是中断状态,但不清除状态标识。
能停止的线程 ———— 异常法
- 可在线程中用for语句来判断一下线程是否是停止状态,如果是则中断for循环,继续执行for后面的语句
public class MyThread extends Thread {
@Override
public void run() {
super.run();
for (int i = 0; i < 500000; i++) {
if (this.interrupted()) {
System.out.println("已经是停止状态了,我要退出了!");
break;
}
System.out.println("i=" + (i + 1));
}
System.out.println("我被输出,如果此代码是for又继续输出,线程并未停止!");
}
}
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(2000);
thread.interrupt();
} catch (InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
System.out.println("end!");
}
- 如果不想让for后面的语句继续运行,则可以 try catch :
public void run() {
super.run();
try {
for (int i = 0; i < 500000; i++) {
if (this.interrupted()) {
System.out.println("已经是停止状态了,我要退出了!");
throw new InterruptedException();
}
System.out.println("i=" + (i + 1));
}
System.out.println("我在for下面");
} catch (InterruptedException e) {
System.out.println("进入MyThread.java类中run方法中的catch");
e.printStackTrace();
}
}
在沉睡中停止
- 如果在sleep状态下停止某一线程,会进入catch语句,并且清除停止状态值,使之变成false.
public class MyThread extends Thread {
@Override
public void run() {
super.run();
try {
System.out.println("run begin");
Thread.sleep(200000);
System.out.println("run end");
} catch (InterruptedException e) {
System.out.println("在沉睡中停止,进入catch!"+this.isInterrupted());
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(200);
thread.interrupt();
} catch (InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
System.out.println("end!");
}
- 如果先interrupte在sleep会报异常
能停止的异常————暴力停止
- 使用stop()停止线程则是非常暴力的
方法stop()与java.lang.ThreadDeath异常
- 方法stop()已经作废,因为如果强制停止则有可能使一些清理性的工作得不到完成。另外一个情况就是对锁定的对象进行了“解锁”,导致数据得不到同步的处理
释放锁的不良后果
- 使用stop()方法释放锁将会给数据造成不一致的结果。
使用return停止线程
- 将方法interrupt()与return结合也能实现停止线程的效果。
- 不过还是建议使用“抛异常”的方法来实现线程停止,因为在catch块中还可以将异常向上抛,使线程停止的事件得以传播
public class MyThread extends Thread {
@Override
public void run() {
while (true) {
if (this.isInterrupted()) {
System.out.println("ֹͣ停止!");
return;
}
System.out.println("timer=" + System.currentTimeMillis());
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread t=new MyThread();
t.start();
Thread.sleep(2000);
t.interrupt();
}
暂停线程
suspend与resume方法的使用
- suspend()方法暂停线程,resume()方法回复线程使用
suspend与resume方法的缺点————独占
- 使用suspend与resume方法时,如果使用不当,极易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象
- 还有一种独占锁的情况也要格外注意:当程序运行到println()方法内部时,同步锁未被释放,这导致当前PrintStream对象的println()方法一直呈现“暂停”状态,并且“锁未释放”,而main()方法中的代码System.out.println()迟迟不能打印
suspend与resume方法的缺点————不同步
- 在使用suspend和resume方法时也容易出现因为线程的暂停而导致数据不同步的情况。
public void setValue(String u, String p) {
this.username = u; //这句跟下面
if (Thread.currentThread().getName().equals("a")) {
System.out.println("ͣ停止a线程");
Thread.currentThread().suspend();
}
//这句应该须要同步才行,但是在执行这句之前已经停止了,导致不同步
this.password = p;
}
yield方法
- yield方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片
public void run() {
long beginTime = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 50000000; i++) {
//Thread.yield();
count = count + (i + 1);
}
long endTime = System.currentTimeMillis();
System.out.println("用时" + (endTime - beginTime) + "毫秒");
}
线程的优先级
- 在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
- 设置线程优先级有助于帮助“线程规划期”确定下一次选择哪一个线程来优先执行。
- 设置线程的优先级使用setPriority()方法
- 在java中,线程的优先级分为1~10这十个等级
优先级具有继承特性
- 线程的优先级具有继承性,比如A线程启动B线程,则B线程 的优先级与A一样
优先级具有规则性
- 高优先级的线程总是大部分先执行完
优先级具有随机性
- 也就是说,优先级较高的线程不一定每一次都先执行完
守护线程
- java有两种线程,用户线程和守护线程
- 守护线程是一种特殊的线程,它的特性有“陪伴”的含义,当进程中不存在非守护线程了,则守护线程自动销毁。(如垃圾回收线程)
- Daemon的作用是为其线程的运行提供遍历服务,守护线程最典型的应用是GC(垃圾回收器),它就是一个很称职的守护者。
public class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
try {
while (true) {
i++;
System.out.println("i=" + (i));
Thread.sleep(1000);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.setDaemon(true); //设置守护线程
thread.start();
Thread.sleep(5000);
System.out.println("我离开thread对象也不再打印了");
//此时main函数运行结束,守护线程也就自动销毁了
} catch (InterruptedException e) {
e.printStackTrace();
}
}