最近阅读了高洪岩 著《Java多线程编程核心技术》,然后将内容与笔记记录下来,写成博客,书中主要是代码实践:
第一章——java多线程基本概念以及常用api
首先需要了解进程的概念:是操作系统结构的基础:是一次程序的执行;是一个程序及其数据在处理机上顺序执行时所发生的活动;是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。进程间的内存是独立的,可以将运行在内存中的exe程序当成是一个进程。
线程的概念:进程中独立运行的子任务。例如:迅雷进行下载任务就是最经典的多线程。
public class ThreadDemo01 {
public static void main(String[] args) {
//获取当前线程的名字
System.out.println(Thread.currentThread().getName());
}
}
运行结果:main
类的main方法是主线程。每个类中的main方法都是主线程。
实现多线程编程的方法有俩种:
- 继承Thread类:Thread类的结构【public class Thread implements Runnable】
public class ThreadDemo01 {
public static void main(String[] args) {
Thread th = new MyThread();
th.start();//启动线程,该方法可以被cpu进行调度
System.out.println("运行结束");
System.out.println("运行很快结束");
}
}
//实现多线程的第一种方法
class MyThread extends Thread{
@Override
public void run() {
System.out.println("myThread");
}
}
运行结果: 运行结束
运行很快结束
myThread
使用多线程技术时,代码的运行结果与代码的执行顺序活着调用顺序是无关的。
- 实现Runnable接口
public class ThreadDemo01 {
public static void main(String[] args) {
Thread th = new Thread(new MyThread2());
th.start();//启动线程,该方法可以被cpu进行调度
System.out.println("运行结束");
System.out.println("运行很快结束");
}
}
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("mythread2");
}
}
使用继承Thread类的方法来实现多线程是有缺陷的,java是单继承,如果一个类需要继承其他的父类,那么就无法通过继承实现多线程了,所以可以通过实现接口来实现多线程。run()方法就是线程体。
实例变量与线程安全
自定义线程类中的实例变量针对其他线程可以有共享和不共享之分,这在多线程进行交互时是很重要的一点。下面通过代码验证数据的共享与不共享:
- 数据不共享
/**
* 线程中数据不共享的情况
* @author acer
*/
public class ThreadDemo02 {
public static void main(String[] args) {
MyThread a = new MyThread("A");
MyThread b = new MyThread("B");
MyThread c = new MyThread("C");
MyThread d = new MyThread("D");
MyThread e = new MyThread("E");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
class MyThread extends Thread{
private int count = 5;
public MyThread(String name) {
this.setName(name);//为线程设置名字
}
@Override
public void run() {
super.run();
while(count>0){
count --;
System.out.println("由"+this.currentThread().getName()+"计算count:"+count);
}
}
}
运行结果:
由A计算count:4
由B计算count:4
由E计算count:4
由A计算count:3
由B计算count:3
由A计算count:2
由A计算count:1
由A计算count:0
由E计算count:3
由E计算count:2
由E计算count:1
由E计算count:0
由B计算count:2
由B计算count:1
由B计算count:0
由D计算count:4
由D计算count:3
由D计算count:2
由D计算count:1
由D计算count:0
由C计算count:4
由C计算count:3
由C计算count:2
由C计算count:1
由C计算count:0
此类一共创建了5个独立的线程,每个线程都有自己的count,所以在进行操作的时候不是数据共享的。数据共享就是启动的线程都操作同一个数据。
- 数据共享的情况
/**
* 线程中数据共享的情况
* @author acer
*/
public class ThreadDemo02 {
public static void main(String[] args) {
MyThread myThread = new MyThread("thread1");
Thread a = new Thread(myThread,"A");
Thread b = new Thread(myThread,"B");
Thread c = new Thread(myThread,"C");
a.start();
b.start();
c.start();
}
}
class MyThread extends Thread{
private int count = 5;
@Override
public void run() {
super.run();
while(count>0){
count --;
System.out.println("由"+this.currentThread().getName()+"计算count:"+count);
}
}
}
运算结果:
由A计算count:2
由C计算count:2
由B计算count:2
由C计算count:0
由A计算count:1
此类是数据共享的实现情况。它们使用的都是同一个count;A,C,B打印的都是同一个值,说明A,C,B都同时处理了count.产生了“非线程安全的问题”。
什么是非线程安全问题:多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改,值不同步的情况,进而影响程序的执行流程。
这样的可以是一个场景:三个售货员,同时进行售货。必须每一个售货员买完一个货品后其他销售员才可以在新的剩余物品数上继续减1.这就需要使多个线程之间进行同步,也就是按照顺序排队的方式进行减1操作
如何按照代码模拟以上场景的实现呢:
public class ThreadDemo02 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread c = new Thread(myThread,"C");
Thread d = new Thread(myThread,"D");
Thread e = new Thread(myThread,"E");
Thread a = new Thread(myThread,"B");
Thread b = new Thread(myThread,"A");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
class MyThread extends Thread{
private int count = 5;
@Override
synchronized public void run() {
super.run();
while(count>0){
count --;
System.out.println("由"+this.currentThread().getName()+"计算count:"+count);
}
}
}
运算结果:
由B计算count:4
由B计算count:3
由B计算count:2
由B计算count:1
由B计算count:0
上面的这段代码在run方法前加上了synchronized关键字,使得多个线程在执行run方法时,以排队的方式进行处理,当一个线程调用run方法前,先判断run方法有没有被上锁,如果上锁,说明其他线程正在调用run方法,必须等待其他线程调用run方法结束后才可以执行run方法。
下面以代码模拟一下登录的非线程安全实现:
/**
* 模拟login登录方法的servlet
* @author acer
*
*/
public class ThreadDemo03 {
private static String usernameRef;
private static String passwordRef;
public static void doPost(String username,String password) throws InterruptedException{
usernameRef = username;
if(username.equals("a")){
Thread.sleep(4000);
}
passwordRef = password;
System.out.println("username="+usernameRef+" password="+password);
}
}
public class Client03 {
public static void main(String[] args) {
ALogin a = new ALogin();
a.start();
BLogin b = new BLogin();
b.start();
}
}
class ALogin extends Thread{
@Override
public void run() {
try {
ThreadDemo03.doPost("a", "aa");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class BLogin extends Thread{
@Override
public void run() {
try {
ThreadDemo03.doPost("b", "bb");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果:username=b password=bb
username=b password=aa
若要将他修改为线程安全的话,只需要在登录的方法前添加同步[synchronized]关键字。这样就可以实现排队请求登录方法。
线程中常用的方法:
- currentThread()方法[静态方法]:返回对当前正在执行的线程的引用。
- isAlive()方法:测试线程是否处于活动状态。【活动状态指的是线程已经启动且尚未终止,线程处于正在运行或准备开始运行的状态】
/**
* 测试isAlive方法
* @author acer
*/
public class ThreadDemo05 {
public static void main(String[] args) {
MyThread1 th = new MyThread1();
System.out.println("begin =="+th.isAlive());
th.start();
System.out.println("end=="+th.isAlive());
}
}
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("run=="+this.isAlive());
}
}
运算结果: begin ==false
end==true
run==true
- sleep()方法:让正在执行的线程休眠【线程让出CPU的调度】。
- getId()方法:返回该线程的标识符。是取得线程的唯一标识。
/**
* 测试isAlive方法
* @author acer
*/
public class ThreadDemo05 {
public static void main(String[] args) throws InterruptedException {
MyThread1 th = new MyThread1();
th.start();
System.out.println(Thread.currentThread().getName()+" "+th.getId());
}
}
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("run=="+this.isAlive());
System.out.println(Thread.currentThread().getName()+" "+this.getId());
}
}
运算结果: main 10
run==true
Thread-0 10
停止线程:停止线程意味着在线程处理完任务之前停掉正在执行的操作。停止一个线程可以使用Thread.stop()方法,但是最好不要使用,因为它是不安全的。大多数停止一个线程的方法使用Thread.interrupt()方法就是线程体。这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
interrupt()方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。
public class ThreadDemo06 {
public static void main(String[] args) throws InterruptedException {
Thread th = new MyThread3();
Thread thr = new Thread(th);
thr.start();
thr.sleep(2000);
thr.interrupt();
}
}
class MyThread3 extends Thread{
@Override
public void run() {
for(int i=0;i<5000;i++){
System.out.println("i="+(i+1));
}
}
}
运行结果是:输出5000个数。
所以他不是真正的停止了线程。而是为线程添加了一个标记。
如何判断线程的状态是不是停止的,Thread.java中提供了俩种方法:
1、this.interrupted():测试当前线程是否已经中断。[public static boolean interrupted()]
2、this.isInterrupted();测试线程是否已经中断。[public boolean isInterrupted()]
使用stop()方法停止线程
package useStopMethodThreadTest;
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) {
e.printStackTrace();
}
}
}
package useStopMethodThreadTest;
public class Client {
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();//启动线程
/*
* 让主线程休眠8秒,然后休眠结束后,停止thread线程
*/
Thread.sleep(8000);
thread.stop();//停止线程
}
}
运行结果:
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
线程执行到第八秒的时候直接停止。
方法stop()已经被作废,如果强制让线程停止,则可能使一些清理性的工作的不到完成。另外一个情况就是对锁定的对象进行了解锁,导致数据得不到同步的处理,出现数据不一致的问题。stop()方法释放锁会造成数据不一致的结果。出现这样的结果,程序处理的数据就有可能受到破坏,最终导致程序的错位执行。
- 通过return与interrupt()结合使用也能实现停止线程的效果:
package userReturnInterrupt;
import java.util.Date;
public class MyThread extends Thread {
@Override
public void run() {
while(true) {
if(this.isInterrupted()) {
System.out.println("线程停止了");
return;
}
System.out.println("timer="+new Date(System.currentTimeMillis()));
}
}
}
public class Client {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();
Thread.sleep(2000);
t.interrupt();
}
}
通过interrupt()为线程打上停止的标签,然后判断,如果有停止标签的话,直接停止线程。
暂停线程:暂停线程意味着此线程还可以恢复运行,在java多线程中,可以使用suspend()方法暂停线程,使用resume()方法回复线程;
package suspend_resume_test;
public class MyThread extends Thread{
private int i;
public void setI(int i) {
this.i = i;
}
public int getI(){
return i;
}
@Override
public void run() {
while(true) {
i++;
}
}
}
package suspend_resume_test;
public class Client {
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(5000);
/*
* A段
*/
thread.suspend();
System.out.println("A="+System.currentTimeMillis()+" i= "+ thread.getI());
Thread.sleep(5000);
System.out.println("A="+System.currentTimeMillis()+" i= "+ thread.getI());
/*
* B段
*/
thread.resume();
Thread.sleep(5000);
/*
* C段
*/
thread.suspend();
System.out.println("B="+System.currentTimeMillis()+" i= "+ thread.getI());
Thread.sleep(5000);
System.out.println("B="+System.currentTimeMillis()+" i= "+ thread.getI());
}
}
运行结果:
A=1489646284529 i= -1691394392
A=1489646289530 i= -1691394392
B=1489646294531 i= 912641430
B=1489646299532 i= 912641430
在使用suspend()和resumne()方法时,如果使用不当,极易造成公共的同步对象的独占,使得其他对象无法访问公共同步对象。
package suspend_resume_deal_lock;
public class SynchronizedObject {
synchronized public void printString(){
System.out.println("begin");
if(Thread.currentThread().getName().equals("a")){
System.out.println("a线程永远suspend了");
Thread.currentThread().suspend();//当前线程暂停
}
System.out.println("end");
}
}
package suspend_resume_deal_lock;
public class Client {
public static void main(String[] args) throws InterruptedException {
final SynchronizedObject object = new SynchronizedObject();
Thread thread1 = new Thread(){
@Override
public void run() {
object.printString();
}
};
thread1.setName("a");
thread1.start();
thread1.sleep(2000);
Thread thread2 = new Thread(){
@Override
public void run() {
System.out.println("thread2方法启动了,但是进入不了object的printString方法");
System.out.println("因为printString()被a线程锁定并且永远的暂停了");
object.printString();
}
};
thread2.start();
}
}
运行结果: begin
a线程永远suspend了
thread2方法启动了,但是进入不了object的printString方法
因为printString()被a线程锁定并且永远的暂停了
使用suspend()和resumne()方法有一个缺点,那就是不同步:
package suspend_resume_nosameValue;
public class MyObject {
private String username = "1";
private String password = "11";
public void setValue(String str1,String str2) {
this.username = str1;
if(Thread.currentThread().getName().equals("a")) {
System.out.println("暂停a线程");
Thread.currentThread().suspend();
}
this.password = str2;
}
public void printValue(){
System.out.println(username+" "+password);
}
}
package suspend_resume_nosameValue;
public class Client {
public static void main(String[] args) throws InterruptedException {
final MyObject object = new MyObject();
Thread thread1 = new Thread(){
@Override
public void run() {
object.setValue("a", "aa");
}
};
thread1.setName("a");
thread1.start();
Thread.sleep(2000);
Thread thread2 = new Thread(){
@Override
public void run() {
object.printValue();
}
};
thread2.start();
}
}
运行结果:暂停a线程
a 11
- yield()方法:作用是放弃当前CPU的资源,将他让给其他的任务去占用CPU执行时间,但放弃的时间不确定,有可能是刚刚放弃,马上又获得CPU时间片
package yield;
public class MyThread extends Thread{
long begin = System.currentTimeMillis();
long count;
@Override
public void run() {
for(int i=0;i<50000000;i++){
//Thread.yield();
count += i;
}
long end = System.currentTimeMillis();
System.out.println(end-begin);
}
}
package yield;
public class Client {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
线程的优先级:在操作系统中,线程可以划分优先级,优先级得到的线程的到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。 在java中线程的优先级划分为10个等级。如果小于1或大于10,会抛出IllegalArgumentException().
1.线程优先级的继承特性:线程的优先级具有继承特性:例如线程A启动线程B。则B与A的线程优先级是一样的。
2.线程优先级具有规则性:虽然使用setPriority()方法可以设置线程优先级,但还没有看到设置优先级带来的效果
3.优先级具有随机性:优先级高的线程不一定每次都最先执行完。
守护线程
在java线程中有俩种线程:一种是用户线程,另一种是守护线程
当进程中不存在非守护线程,则守护线程自动销毁。典型的守护线程就是垃圾回收线程。任何一个非守护线程没有结束,守护线程就存在,只有当最后一个非守护线程结束时,守护线程才随着JVM一同结束工作。