多线程编程除了在现实工作中经常会被应用到,而且在面试的时候不少的面试官也喜欢问这些多线程的东西来作为他们考量一个面试者基础水平的标准.可见它存在的实用性,那么我觉得作为一个程序员学习者来说,当然得好好学一学,本随笔将会从我自己的角度讲一下我个人对多线程的理解以及应用。
那么从概念出发,在windows中进程作为基本的运行单位,一个进程中可以包含多个线程,且必须要有一个也只能有一个主线程。
进程:正在运行的程序,负责了这个程序的内存空间分配,代表了内存中的执行区域。
线程:就是在一个进程中负责一个执行路径。
多线程:就是在一个进程中多个执行路径同时执行。
那么回归代码,在Java程序中的main函数即主线程的入口。
note:如果程序只有一个线程(主线程)执行所有操作代码,那么它就叫做单线程程序。
在进行编码之前,我们需要了解
多线程的好处:
1. 解决了一个进程里面可以同时运行多个任务(执行路径)。
2. 提供资源的利用率,而不是提供效率。
多线程的弊端:
1.降低了一个进程里面的线程的执行频率。
2.对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。
3.公有变量的同时读或写。当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,发生线程安全问题。
4.线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。
一:创建线程
打开JDK看一下Thread的几个构造方法:
Thread() 分配新的 Thread 对象。 |
Thread(Runnable target) 分配新的 Thread 对象。 |
Thread(Runnable target,String name) 分配新的 Thread 对象。 |
Thread(String name) 分配新的 Thread 对象。 |
在java中创建线程有两种方式,一种是继承Thread类,重写Run()方法。另一种是实现Runnable接口,实现Run()方法。
<1>继承Thread类方式
继承Thread类,重写Run()方法。
我们可以举一个小例子,两人同时去银行拿钱。
public class myThread extends Thread{
static int i=5000;
public myThread(String name){
super(name);
}
@Override
public void run() {
while(true){
if(i>0){
System.out.println(Thread.currentThread().getName()+"取了1000块钱");
i=i-100;
System.out.println("余额为:"+i);
}
else{
System.out.println("余额不足");
break;
}
}
}
public static void main(String[] args) {
myThread m1=new myThread("线程A");
myThread m2=new myThread("线程B");
m1.start();
m2.start();
}
}
结果为:
线程B取了1000块钱
线程A取了1000块钱
余额为:4000
余额为:3000
线程B取了1000块钱
线程A取了1000块钱
余额为:2000
余额为:1000
线程B取了1000块钱
线程A取了1000块钱
余额为:0
余额为:-1000
余额不足
余额不足
这明显不是我们希望看到的数据,那么在这里出现一个了问题:线程不安全。
<2>实现Runnable接口
在jdk api中我们看到Thread有的构造函数中使用Runnable类型形参,我们可以使用oop多态的这种特征,传递我们实现了Runnable接口的子类,从而构造Thread对象。
public class myThread2 implements Runnable {
static int i=5000;
@Override
public void run() {
while(true){
if(i>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"取了1000元");
i=i-1000;
System.out.println("余额:"+i);
}
else{
System.out.println("余额不足");
break;
}
}
}
public static void main(String[] args) {
myThread2 m=new myThread2();
Thread t1=new Thread(m,"线程A");
Thread t2=new Thread(m,"线程B");
t2.start();
t1.start();
}
}
同理结果还是继续会出现线程不安全:
线程A取了1000元
线程B取了1000元
余额:4000
余额:3000
线程B取了1000元
线程A取了1000元
余额:2000
余额:1000
线程B取了1000元
线程A取了1000元
余额:0
余额:-1000
余额不足
余额不足
二:线程安全
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
在java程序,我们可以使用锁对象来解决线程安全问题。
<1>使用synchronized关键字
public class myThread extends Thread{
static int i=5000;
static Object o=new Object();
public myThread(String name){
super(name);
}
@Override
public void run() {
while(true){
synchronized(o){//锁必须是同一对象
if(i>0){
System.out.println(Thread.currentThread().getName()+"取了1000块钱");
i=i-100;
System.out.println("余额为:"+i);
}
else{
System.out.println("余额不足");
break;
}
}
}
}
public static void main(String[] args) {
myThread m1=new myThread("线程A");
myThread m2=new myThread("线程B");
m1.start();
m2.start();
}
}
上述代码中,使用了synchronized光键字的代码块处理会发生线程安全的地方,这个叫做同步代码块。另外也有一种方式叫做同步函数。
package yang.zhiran;
public class mySynchronized implements Runnable {
static int count=100;
@Override
public void run() {
/*
while(true){
synchronized(this){
if(count>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("OpeatorName"+Thread.currentThread().getName()+",Now Count Value:"+count);
}
else{
System.out.println("Count < 0");
break;
}
}
}
*/
Func();
}
public synchronized void Func(){
while(true){
if(count>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("OpeatorName"+Thread.currentThread().getName()+",Now Count Value:"+count);
}
else{
System.out.println("Count < 0");
break;
}
}
}
/**
* @param args
*/
public static void main(String[] args) {
mySynchronized ms=new mySynchronized();
Thread t1=new Thread(ms,"ThreadA");
Thread t2=new Thread(ms,"ThreadB");
t1.start();
t2.start();
}
}
但是这种方式会导致线程执行时只有方法结束时才能释放锁对象,在这里的话,只有count的值小于等于0的时候锁才会释放。
三:线程同步
除了上述synchronized的同步代码块和同步函数的方式,还有其他的三种.
1.使用volatile光键字
volatile的作用是使用时的变量值将会直接来自内存。
2.使用ThreadLocal<T>
static ThreadLocal<Integer> count=new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 100;
}
};
3.ReentrantLock类(效率较低)
package yang.zhiran;
import java.util.concurrent.locks.ReentrantLock;
public class myReentrantLock implements Runnable{
static int count=100;
ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while(true){
lock.lock();
if(count>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("OpeatorName"+Thread.currentThread().getName()+",Now Count Value:"+count);
}
else{
System.out.println("Count < 0");
break;
}
if(lock.isLocked()){lock.unlock();}
}
}
/**
* @param args
*/
public static void main(String[] args) {
myReentrantLock ms=new myReentrantLock();
Thread t1=new Thread(ms,"ThreadA");
Thread t2=new Thread(ms,"ThreadB");
t1.start();
t2.start();
}
}
四:Thread类
<1>线程的状态
创建:新创建了一个线程对象。
可运行:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取cpu的执行权。
运行:就绪状态的线程获取了CPU执行权,执行程序代码。
阻临时塞: 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
死亡:线程执行完它的任务时。
<2>线程常见方法
Thread(String name) 初始化线程的名字
getName() 返回线程的名字
setName(String name) 设置线程对象名
sleep() 线程睡眠指定的毫秒数。
getPriority() 返回当前线程对象的优先级 默认线程的优先级是5
setPriority(intnewPriority) 设置线程的优先级 虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10 ,最小的1 , 默认是5)。
currentThread() 返回CPU正在执行的线程的对象
join(): 等待线程结束
yield(): 资源谦让
Object.wait();设置本状态为等待状态
Object.notify():执行完本线程的任务之后会随机唤醒一个等待状态下的线程
public void run(){
synchronized(object){
object.notify();
}
}
public void run(){
synchronized(object){
object.wait();
}
}
守护线程的设置:
Thread t=new MyThread();
t.setDaemon(true);//设置为守护线程
t.start();
守护线程在后台默默的完成一些系统性的服务,比如垃圾回收线程就可以理解为守护线程
当一个java应用内,只有守护线程时,java虚拟机会自然退出.
<3>线程生命周期
1. 正常终止 当线程的run()执行完毕,线程死亡。
2. 使用标记停止线程
注意:Stop方法已过时,就不能再使用这个方法。
如何使用标记停止线程停止线程。
中断线程:
public void Thread.interrupt();//中断线程
public boolean Thread.isInterrupted();//判断是否被中断
public static boolean Thread.interrupted();//判断是否被中断,并清除当前中断状态
在线程处于睡眠状态下的中断:
public void run(){
while(true){
if(Thread.currentThread().isInterrupted()){
System.out.println("Interrupted");
break;
}
try{
Thread.sleep(2000);
}catch(InterruptedException e){
System.ot.println("Interrupted When sleep");
//被catch捕获之后要再次设置中断状态
Thread.currentThread().interrupt();
}
Thread.yield();
}
}